From 287a01d4f306dc6a8063cc5660a0672f594b75e6 Mon Sep 17 00:00:00 2001 From: Michael Salihi Date: Tue, 25 May 2021 23:12:15 +0200 Subject: [PATCH 1/5] Add new SAP application example link --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9c3ed9e8..afe232c3 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,7 @@ All examples are in https://github.com/metosin/reitit/tree/master/examples * https://www.learnreitit.com/ * Lipas, liikuntapalvelut: https://github.com/lipas-liikuntapaikat/lipas * Implementation of the Todo-Backend API spec, using Clojure, Ring/Reitit and next-jdbc: https://github.com/PrestanceDesign/todo-backend-clojure-reitit +* Ping CRM, a single page app written in Clojure Ring, Reitit, Integrant and next.jdbc: https://github.com/prestancedesign/clojure-inertia-pingcrm-demo ## More info From 056c70d2698a292d85126535fab9faab167fcbfe Mon Sep 17 00:00:00 2001 From: Dieter Komendera Date: Thu, 24 Jun 2021 14:07:23 +0200 Subject: [PATCH 2/5] Enrich request for pedestal/routing-interceptor default-queue This ensures requests handled by the default queue also have access to the router per the injected :reitit.core/router key on the request. --- modules/reitit-http/src/reitit/http.cljc | 7 +++++-- test/clj/reitit/pedestal_test.clj | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/modules/reitit-http/src/reitit/http.cljc b/modules/reitit-http/src/reitit/http.cljc index fa4c26fc..be2d49e3 100644 --- a/modules/reitit-http/src/reitit/http.cljc +++ b/modules/reitit-http/src/reitit/http.cljc @@ -89,7 +89,8 @@ default-interceptors (->> interceptors (map #(interceptor/into-interceptor % nil (r/options router)))) default-queue (interceptor/queue executor default-interceptors) - enrich-request (ring/create-enrich-request inject-match? inject-router?)] + enrich-request (ring/create-enrich-request inject-match? inject-router?) + enrich-default-request (ring/create-enrich-default-request inject-router?)] {:name ::router :enter (fn [{:keys [request] :as context}] (if-let [match (r/match-by-path router (:uri request))] @@ -101,7 +102,9 @@ context (assoc context :request request) queue (interceptor/queue executor (concat default-interceptors interceptors))] (interceptor/enqueue executor context queue)) - (interceptor/enqueue executor context default-queue))) + (let [request (enrich-default-request request router) + context (assoc context :request request)] + (interceptor/enqueue executor context default-queue)))) :leave (fn [context] (if-not (:response context) (assoc context :response (default-handler (:request context))) diff --git a/test/clj/reitit/pedestal_test.clj b/test/clj/reitit/pedestal_test.clj index 86211927..8a0ea30b 100644 --- a/test/clj/reitit/pedestal_test.clj +++ b/test/clj/reitit/pedestal_test.clj @@ -41,3 +41,25 @@ (:io.pedestal.http/service-fn))] (is (= "ok" (:body (io.pedestal.test/response-for service :get "/ok")))) (is (= 500 (:status (io.pedestal.test/response-for service :get "/fail")))))) + +(deftest pedestal-inject-router-test + (let [check-router (fn [r] (when-not (:reitit.core/router r) + (throw (ex-info "Missing :reitit.core/router!" {})))) + interceptor {:name ::needs-router + :enter (fn [{:as context :keys [request]}] + (check-router request) + context)} + router (pedestal/routing-interceptor + (http/router + ["" + ["/ok" (fn [r] (check-router r) {:status 200, :body "ok"})]]) + nil + {:interceptors [interceptor]}) + service (-> {:io.pedestal.http/request-logger nil + :io.pedestal.http/routes []} + (io.pedestal.http/default-interceptors) + (pedestal/replace-last-interceptor router) + (io.pedestal.http/create-servlet) + (:io.pedestal.http/service-fn))] + (is (= "ok" (:body (io.pedestal.test/response-for service :get "/ok")))) + (is (= "Not Found" (:body (io.pedestal.test/response-for service :get "/not-existing")))))) From 478ee18a32e99ee6ff5b16a8cc784c9c87ebb2cb Mon Sep 17 00:00:00 2001 From: Phil Hofmann Date: Tue, 27 Jul 2021 18:33:17 +0200 Subject: [PATCH 3/5] improve some docs --- doc/ring/RESTful_form_methods.md | 12 ++++++------ doc/ring/coercion.md | 8 ++++---- doc/ring/compiling_middleware.md | 6 +++--- doc/ring/content_negotiation.md | 12 ++++++------ doc/ring/data_driven_middleware.md | 22 +++++++++++----------- doc/ring/default_handler.md | 2 +- doc/ring/default_middleware.md | 4 ++-- doc/ring/dynamic_extensions.md | 4 ++-- doc/ring/exceptions.md | 10 +++++----- doc/ring/ring.md | 10 +++++----- 10 files changed, 45 insertions(+), 45 deletions(-) diff --git a/doc/ring/RESTful_form_methods.md b/doc/ring/RESTful_form_methods.md index 5faaeb3c..61f6bfd7 100644 --- a/doc/ring/RESTful_form_methods.md +++ b/doc/ring/RESTful_form_methods.md @@ -1,10 +1,10 @@ # RESTful form methods -When designing RESTful applications you will be doing a lot of "PATCH" and "DELETE" request, but most browsers don't support methods other than "GET" and "POST" when it comes to submitting forms. +When designing RESTful applications you will be doing a lot of "PATCH" and "DELETE" request, but most browsers don't support methods other than "GET" and "POST" when it comes to submitting forms. There is a pattern to solve this (pioneered by Rails) using a hidden "_method" field in the form and swapping out the "POST" method for whatever is in that field. -We can do this with middleware in reitit like this: +We can do this with middleware in reitit like this: ```clj (defn- hidden-method [request] @@ -18,20 +18,20 @@ We can do this with middleware in reitit like this: :wrap (fn [handler] (fn [request] (if-let [fm (and (= :post (:request-method request)) ;; if this is a :post request - (hidden-method request))] ;; and there is a "_method" field + (hidden-method request))] ;; and there is a "_method" field (handler (assoc request :request-method fm)) ;; replace :request-method (handler request))))}) ``` -And apply the middleware like this: +And apply the middleware like this: ```clj (reitit.ring/ring-handler (reitit.ring/router ...) (reitit.ring/create-default-handler) - {:middleware + {:middleware [reitit.ring.middleware.parameters/parameters-middleware ;; needed to have :form-params in the request map reitit.ring.middleware.multipart/multipart-middleware ;; needed to have :multipart-params in the request map wrap-hidden-method]}) ;; our hidden method wrapper ``` -(NOTE: This middleware must be placed here and not inside the route data given to `reitit.ring/handler`. +(NOTE: This middleware must be placed here and not inside the route data given to `reitit.ring/handler`. This is so that our middleware is applied before reitit matches the request with a specific handler using the wrong method.) diff --git a/doc/ring/coercion.md b/doc/ring/coercion.md index a8ddae6f..faed3a8b 100644 --- a/doc/ring/coercion.md +++ b/doc/ring/coercion.md @@ -39,7 +39,7 @@ Responses are defined in route data under `:responses` key. It's value should be Below is an example with [Plumatic Schema](https://github.com/plumatic/schema). It defines schemas for `:query`, `:body` and `:path` parameters and for http 200 response `:body`. -Handler can access the coerced parameters can be read under `:parameters` key in the request. +Handlers can access the coerced parameters via the `:parameters` key in the request. ```clj (require '[reitit.coercion.schema]) @@ -71,7 +71,7 @@ Defining a coercion for a route data doesn't do anything, as it's just data. We ### Full example -Here's an full example for applying coercion with Reitit, Ring and Schema: +Here is a full example for applying coercion with Reitit, Ring and Schema: ```clj (require '[reitit.ring.coercion :as rrc]) @@ -150,7 +150,7 @@ Invalid response: ## Pretty printing spec errors -Spec problems are exposed as-is into request & response coercion errors, enabling pretty-printers like [expound](https://github.com/bhb/expound) to be used: +Spec problems are exposed as is in request & response coercion errors. Pretty-printers like [expound](https://github.com/bhb/expound) can be enabled like this: ```clj (require '[reitit.ring :as ring]) @@ -216,7 +216,7 @@ Spec problems are exposed as-is into request & response coercion errors, enablin ### Optimizations -The coercion middleware are [compiled against a route](compiling_middleware.md). In the middleware compilation step the actual coercer implementations are constructed for the defined models. Also, the middleware doesn't mount itself if a route doesn't have `:coercion` and `:parameters` or `:responses` defined. +The coercion middlewares are [compiled against a route](compiling_middleware.md). In the middleware compilation step the actual coercer implementations are constructed for the defined models. Also, the middleware doesn't mount itself if a route doesn't have `:coercion` and `:parameters` or `:responses` defined. We can query the compiled middleware chain for the routes: diff --git a/doc/ring/compiling_middleware.md b/doc/ring/compiling_middleware.md index 1e100f3b..f9404cc9 100644 --- a/doc/ring/compiling_middleware.md +++ b/doc/ring/compiling_middleware.md @@ -1,12 +1,12 @@ # Compiling Middleware -The [dynamic extensions](dynamic_extensions.md) is a easy way to extend the system. To enable fast lookups into route data, we can compile them into any shape (records, functions etc.) we want, enabling fast access at request-time. +The [dynamic extensions](dynamic_extensions.md) are an easy way to extend the system. To enable fast lookup of route data, we can compile them into any shape (records, functions etc.), enabling fast access at request-time. -But, we can do much better. As we know the exact route that middleware/interceptor is linked to, we can pass the (compiled) route information into the middleware at creation-time. It can do local reasoning: extract and transform relevant data just for it and pass the optimized data into the actual request-handler via a closure - yielding much faster runtime processing. Middleware can also decide not to mount itself by returning `nil`. Why mount a `wrap-enforce-roles` middleware for a route if there are no roles required for it? +But, we can do much better. As we know the exact route that a middleware/interceptor is linked to, we can pass the (compiled) route information into the middleware at creation-time. It can do local reasoning: Extract and transform relevant data just for it and pass the optimized data into the actual request-handler via a closure - yielding much faster runtime processing. A middleware can also decide not to mount itself by returning `nil`. (E.g. Why mount a `wrap-enforce-roles` middleware for a route if there are no roles required for it?) To enable this we use [middleware records](data_driven_middleware.md) `:compile` key instead of the normal `:wrap`. `:compile` expects a function of `route-data router-opts => ?IntoMiddleware`. -To demonstrate the two approaches, below are response coercion middleware written as normal ring middleware function and as middleware record with `:compile`. +To demonstrate the two approaches, below is the response coercion middleware written as normal ring middleware function and as middleware record with `:compile`. ## Normal Middleware diff --git a/doc/ring/content_negotiation.md b/doc/ring/content_negotiation.md index 3c73202d..fb696c10 100644 --- a/doc/ring/content_negotiation.md +++ b/doc/ring/content_negotiation.md @@ -1,8 +1,8 @@ # Content Negotiation -Wrapper for [Muuntaja](https://github.com/metosin/muuntaja) middleware for content-negotiation, request decoding and response encoding. Takes explicit configuration via `:muuntaja` key in route data. Emit's [swagger](swagger.md) `:produces` and `:consumes` definitions automatically based on the Muuntaja configuration. +Wrapper for [Muuntaja](https://github.com/metosin/muuntaja) middleware for content negotiation, request decoding and response encoding. Takes explicit configuration via `:muuntaja` key in route data. Emits [swagger](swagger.md) `:produces` and `:consumes` definitions automatically based on the Muuntaja configuration. -Negotiates a request body based on `Content-Type` header and response body based on `Accept`, `Accept-Charset` headers. Publishes the negotiation results as `:muuntaja/request` and `:muuntaja/response` keys into the request. +Negotiates a request body based on `Content-Type` header and response body based on `Accept` and `Accept-Charset` headers. Publishes the negotiation results as `:muuntaja/request` and `:muuntaja/response` keys into the request. Decodes the request body into `:body-params` using the `:muuntaja/request` key in request if the `:body-params` doesn't already exist. @@ -87,7 +87,7 @@ Server: Jetty(9.2.21.v20170120) ## Changing default parameters -The current JSON formatter used by `reitit` already have the option to parse keys as `keyword` which is a sane default in Clojure. However, if you would like to parse all the `double` as `bigdecimal` you'd need to change an option of the [JSON formatter](https://github.com/metosin/jsonista) +The current JSON formatter used by `reitit` already has the option to parse keys as `keyword` which is a sane default in Clojure. However, if you would like to parse all the `double` as `bigdecimal` you'd need to change an option of the [JSON formatter](https://github.com/metosin/jsonista) ```clj @@ -102,7 +102,7 @@ The current JSON formatter used by `reitit` already have the option to parse key Now you should change the `m/instance` installed in the router with the `new-muuntaja-instance`. -You can find more options for [JSON](https://cljdoc.org/d/metosin/jsonista/0.2.5/api/jsonista.core#object-mapper) and [EDN]. +Here you can find more options for [JSON](https://cljdoc.org/d/metosin/jsonista/0.2.5/api/jsonista.core#object-mapper) and EDN. ## Adding custom encoder @@ -125,9 +125,9 @@ The example below is from `muuntaja` explaining how to add a custom encoder to p ``` -## Adding all together +## Putting it all together -If you inspect `m/default-options` it's only a map, therefore you can compose your new muuntaja instance with as many options as you need it. +If you inspect `m/default-options` you'll find it's only a map. This means you can compose your new muuntaja instance with as many options as you need. ```clj (def new-muuntaja diff --git a/doc/ring/data_driven_middleware.md b/doc/ring/data_driven_middleware.md index e11be838..2d310ff4 100644 --- a/doc/ring/data_driven_middleware.md +++ b/doc/ring/data_driven_middleware.md @@ -1,19 +1,19 @@ # Data-driven Middleware -Ring [defines middleware](https://github.com/ring-clojure/ring/wiki/Concepts#middleware) as a function of type `handler & args => request => response`. It's relatively easy to understand and enables good performance. Downside is that the middleware-chain is just a opaque function, making things like debugging and composition hard. It's too easy to apply the middleware in wrong order. +Ring [defines middleware](https://github.com/ring-clojure/ring/wiki/Concepts#middleware) as a function of type `handler & args => request => response`. It is relatively easy to understand and allows for good performance. A downside is that the middleware chain is just a opaque function, making things like debugging and composition hard. It is too easy to apply the middlewares in wrong order. Reitit defines middleware as data: -1. Middleware can be defined as first-class data entries -2. Middleware can be mounted as a [duct-style](https://github.com/duct-framework/duct/wiki/Configuration) vector (of middleware) -4. Middleware can be optimized & [compiled](compiling_middleware.md) against an endpoint -3. Middleware chain can be transformed by the router +1. A middleware can be defined as first-class data entries +2. A middleware can be mounted as a [duct-style](https://github.com/duct-framework/duct/wiki/Configuration) vector (of middlewares) +4. A middleware can be optimized & [compiled](compiling_middleware.md) against an endpoint +3. A middleware chain can be transformed by the router ## Middleware as data -All values in the `:middleware` vector in the route data are expanded into `reitit.middleware/Middleware` Records with using the `reitit.middleware/IntoMiddleware` Protocol. By default, functions, maps and `Middleware` records are allowed. +All values in the `:middleware` vector of route data are expanded into `reitit.middleware/Middleware` Records by using the `reitit.middleware/IntoMiddleware` Protocol. By default, functions, maps and `Middleware` records are allowed. -Records can have arbitrary keys, but the following keys have a special purpose: +Records can have arbitrary keys, but the following keys have special purpose: | key | description | | ---------------|-------------| @@ -22,13 +22,13 @@ Records can have arbitrary keys, but the following keys have a special purpose: | `:wrap` | The actual middleware function of `handler & args => request => response` | `:compile` | Middleware compilation function, see [compiling middleware](compiling_middleware.md). -Middleware Records are accessible in their raw form in the compiled route results, thus available for inventories, creating api-docs etc. +Middleware Records are accessible in their raw form in the compiled route results, and thus are available for inventories, creating api-docs, etc. For the actual request processing, the Records are unwrapped into normal functions and composed into a middleware function chain, yielding zero runtime penalty. ### Creating Middleware -The following produce identical middleware runtime function. +The following examples produce identical middleware runtime functions. ### Function @@ -77,7 +77,7 @@ The following produce identical middleware runtime function. :handler handler}}]]))) ``` -All the middleware are applied correctly: +All the middlewares are applied correctly: ```clj (app {:request-method :get, :uri "/api/ping"}) @@ -86,7 +86,7 @@ All the middleware are applied correctly: ## Compiling middleware -Middleware can be optimized against an endpoint using [middleware compilation](compiling_middleware.md). +Middlewares can be optimized against an endpoint using [middleware compilation](compiling_middleware.md). ## Ideas for the future diff --git a/doc/ring/default_handler.md b/doc/ring/default_handler.md index 40fda56b..890dcb9c 100644 --- a/doc/ring/default_handler.md +++ b/doc/ring/default_handler.md @@ -1,6 +1,6 @@ # Default handler -By default, if no routes match, `nil` is returned, which is not valid response in Ring: +By default, if no routes match, `nil` is returned, which is not a valid response in Ring: ```clj (require '[reitit.ring :as ring]) diff --git a/doc/ring/default_middleware.md b/doc/ring/default_middleware.md index 18f29d9d..eba975ae 100644 --- a/doc/ring/default_middleware.md +++ b/doc/ring/default_middleware.md @@ -4,7 +4,7 @@ [metosin/reitit-middleware "0.5.13"] ``` -Any Ring middleware can be used with `reitit-ring`, but using data-driven middleware is preferred as they are easier to manage and in many cases, yield better performance. `reitit-middleware` contains a set of common ring middleware, lifted into data-driven middleware. +Any Ring middleware can be used with `reitit-ring`, but using data-driven middleware is preferred as they are easier to manage and in many cases yield better performance. `reitit-middleware` contains a set of common ring middleware, lifted into data-driven middleware. * [Parameter Handling](#parameters-handling) * [Exception Handling](#exception-handling) @@ -32,7 +32,7 @@ See [Content Negotiation](content_negotiation.md). Wrapper for [Ring Multipart Middleware](https://github.com/ring-clojure/ring/blob/master/ring-core/src/ring/middleware/multipart_params.clj). Emits swagger `:consumes` definitions automatically. Expected route data: - + | key | description | | -------------|-------------| | `[:parameters :multipart]` | mounts only if defined for a route. diff --git a/doc/ring/dynamic_extensions.md b/doc/ring/dynamic_extensions.md index 3f922317..1308dde1 100644 --- a/doc/ring/dynamic_extensions.md +++ b/doc/ring/dynamic_extensions.md @@ -1,8 +1,8 @@ # Dynamic Extensions -`ring-handler` injects the `Match` into a request and it can be extracted at runtime with `reitit.ring/get-match`. This can be used to build ad-hoc extensions to the system. +`ring-handler` injects the `Match` into a request and it can be extracted at runtime with `reitit.ring/get-match`. This can be used to build ad hoc extensions to the system. -Example middleware to guard routes based on user roles: +This example shows a middleware to guard routes based on user roles: ```clj (require '[reitit.ring :as ring]) diff --git a/doc/ring/exceptions.md b/doc/ring/exceptions.md index d3a45fa2..4fea7d17 100644 --- a/doc/ring/exceptions.md +++ b/doc/ring/exceptions.md @@ -4,7 +4,7 @@ [metosin/reitit-middleware "0.5.13"] ``` -Exceptions thrown in router creation can be [handled with custom exception handler](../basics/error_messages.md). By default, exceptions thrown at runtime from a handler or a middleware are not caught by the `reitit.ring/ring-handler`. A good practise is a have an top-level exception handler to log and format the errors for clients. +Exceptions thrown in router creation can be [handled with custom exception handler](../basics/error_messages.md). By default, exceptions thrown at runtime from a handler or a middleware are not caught by the `reitit.ring/ring-handler`. A good practice is to have a top-level exception handler to log and format errors for clients. ```clj (require '[reitit.ring.middleware.exception :as exception]) @@ -36,7 +36,7 @@ A preconfigured middleware using `exception/default-handlers`. Catches: ### `exception/create-exception-middleware` -Creates the exception-middleware with custom options. Takes a map of `identifier => exception request => response` that is used to select the exception handler for the thrown/raised exception identifier. Exception identifier is either a `Keyword` or a Exception Class. +Creates the exception-middleware with custom options. Takes a map of `identifier => exception request => response` that is used to select the exception handler for the thrown/raised exception identifier. Exception identifier is either a `Keyword` or an Exception Class. The following handlers are available by default: @@ -55,7 +55,7 @@ The handler is selected from the options map by exception identifier in the foll 2) Class of exception 3) `:type` ancestors of exception ex-data 4) Super Classes of exception -5) The ::default handler +5) The `::default` handler ```clj ;; type hierarchy @@ -94,7 +94,7 @@ The handler is selected from the options map by exception identifier in the foll (def app (ring/ring-handler (ring/router - ["/fail" (fn [_] (throw (ex-info "fail" {:type ::failue})))] + ["/fail" (fn [_] (throw (ex-info "fail" {:type ::failure})))] {:data {:middleware [exception-middleware]}}))) (app {:request-method :get, :uri "/fail"}) @@ -102,6 +102,6 @@ The handler is selected from the options map by exception identifier in the foll ; => {:status 500, ; :body {:message "default" ; :exception clojure.lang.ExceptionInfo -; :data {:type :user/failue} +; :data {:type :user/failure} ; :uri "/fail"}} ``` diff --git a/doc/ring/ring.md b/doc/ring/ring.md index 3f282f0a..b80c51eb 100644 --- a/doc/ring/ring.md +++ b/doc/ring/ring.md @@ -11,8 +11,8 @@ Read more about the [Ring Concepts](https://github.com/ring-clojure/ring/wiki/Co ## `reitit.ring/ring-router` `ring-router` is a higher order router, which adds support for `:request-method` based routing, [handlers](https://github.com/ring-clojure/ring/wiki/Concepts#handlers) and [middleware](https://github.com/ring-clojure/ring/wiki/Concepts#middleware). - - It accepts the following options: + +It accepts the following options: | key | description | | ----------------------------------------|-------------| @@ -53,7 +53,7 @@ Given a `ring-router`, optional default-handler & options, `ring-handler` functi | key | description | | ------------------|-------------| -| `:middleware` | Optional sequence of middleware that wrap the ring-handler" +| `:middleware` | Optional sequence of middlewares that wrap the ring-handler | `:inject-match?` | Boolean to inject `match` into request under `:reitit.core/match` key (default true) | `:inject-router?` | Boolean to inject `router` into request under `:reitit.core/router` key (default true) @@ -91,7 +91,7 @@ The router can be accessed via `get-router`: # Request-method based routing -Handlers can be placed either to the top-level (all methods) or under a specific method (`:get`, `:head`, `:patch`, `:delete`, `:options`, `:post`, `:put` or `:trace`). Top-level handler is used if request-method based handler is not found. +Handlers can be placed either to the top-level (all methods) or under a specific method (`:get`, `:head`, `:patch`, `:delete`, `:options`, `:post`, `:put` or `:trace`). Top-level handler is used if request-method based handler is not found. By default, the `:options` route is generated for all paths - to enable thing like [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing). @@ -196,7 +196,7 @@ Top-level middleware, applied before any routing is done: (ring/router ["/api" {:middleware [[mw :api]]} ["/get" {:get handler}]]) - nil + nil {:middleware [[mw :top]]})) (app {:request-method :get, :uri "/api/get"}) From 20b7cabed744c423d950f27746b5d87f5c6fbb3e Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Tue, 3 Aug 2021 08:46:51 +0300 Subject: [PATCH 4/5] Fix Malli encoding,, #498 --- .../src/reitit/coercion/malli.cljc | 23 ++++----- test/cljc/reitit/ring_coercion_test.cljc | 50 ++++++++++++++++++- 2 files changed, 59 insertions(+), 14 deletions(-) diff --git a/modules/reitit-malli/src/reitit/coercion/malli.cljc b/modules/reitit-malli/src/reitit/coercion/malli.cljc index 23ec65b9..ee4a4bdc 100644 --- a/modules/reitit-malli/src/reitit/coercion/malli.cljc +++ b/modules/reitit-malli/src/reitit/coercion/malli.cljc @@ -34,7 +34,7 @@ (def json-transformer-provider (-provider (mt/json-transformer))) (def default-transformer-provider (-provider nil)) -(defn- -coercer [schema type transformers f encoder {:keys [validate enabled options]}] +(defn- -coercer [schema type transformers f {:keys [validate enabled options]}] (if schema (let [->coercer (fn [t] (let [decoder (if t (m/decoder schema options t) identity) @@ -48,7 +48,6 @@ (-explain [_ value] (explainer value))))) {:keys [formats default]} (transformers type) default-coercer (->coercer default) - encode (or encoder (fn [value _format] value)) format-coercers (some->> (for [[f t] formats] [f (->coercer t)]) (filter second) (seq) (into {})) get-coercer (cond format-coercers (fn [format] (or (get format-coercers format) default-coercer)) default-coercer (constantly default-coercer))] @@ -66,14 +65,14 @@ value)) ;; encode: decode -> validate -> encode (fn [value format] - (if-let [coercer (get-coercer format)] - (let [transformed (-decode coercer value)] + (let [transformed (-decode default-coercer value)] + (if-let [coercer (get-coercer format)] (if (-validate coercer transformed) - (encode transformed format) + (-encode coercer transformed) (let [error (-explain coercer transformed)] (coercion/map->CoercionError - (assoc error :transformed transformed))))) - value))))))) + (assoc error :transformed transformed)))) + value)))))))) ;; ;; swagger @@ -106,11 +105,13 @@ ;; public api ;; +;; TODO: this is much too compöex (def default-options {:transformers {:body {:default default-transformer-provider :formats {"application/json" json-transformer-provider}} :string {:default string-transformer-provider} - :response {:default default-transformer-provider}} + :response {:default default-transformer-provider + :formats {"application/json" json-transformer-provider}}} ;; set of keys to include in error messages :error-keys #{:type :coercion :in :schema :value :errors :humanized #_:transformed} ;; schema identity function (default: close all map schemas) @@ -176,10 +177,8 @@ (seq error-keys) (select-keys error-keys) encode-error (encode-error))) (-request-coercer [_ type schema] - (-coercer (compile schema options) type transformers :decode nil opts)) + (-coercer (compile schema options) type transformers :decode opts)) (-response-coercer [_ schema] - (let [schema (compile schema options) - encoder (-coercer schema :body transformers :encode nil opts)] - (-coercer schema :response transformers :encode encoder opts))))))) + (-coercer (compile schema options) :response transformers :encode opts)))))) (def coercion (create default-options)) diff --git a/test/cljc/reitit/ring_coercion_test.cljc b/test/cljc/reitit/ring_coercion_test.cljc index 3e38029e..fd5448f7 100644 --- a/test/cljc/reitit/ring_coercion_test.cljc +++ b/test/cljc/reitit/ring_coercion_test.cljc @@ -207,6 +207,24 @@ (let [{:keys [status]} (app invalid-request2)] (is (= 500 status)))))))) +(let [app (ring/ring-handler + (ring/router + ["/plus" {:get {:parameters {:query [:map [:x :int]]} + :responses {200 {:body [:map + [:total [:int {:encode/json str + :min 0}]]]}} + :handler (fn [{{{:keys [x]} :query} :parameters}] + {:status 200 + :body {:total (* x x)}})}}] + {:data {:middleware [rrc/coerce-request-middleware + rrc/coerce-response-middleware] + :coercion malli/coercion}}))] + (app {:uri "/plus" + :request-method :get + :muuntaja/request {:format "application/json"} + :muuntaja/response {:format "application/json"} + :query-params {"x" "2"}})) + (deftest malli-coercion-test (let [create (fn [middleware] (ring/ring-handler @@ -397,7 +415,7 @@ (testing "encoding errors" (let [app (->app {:encode-error (fn [error] {:errors (:humanized error)})})] (is (= {:status 400, :body {:errors {:x ["missing required key"]}}} - (app (assoc (->request "closed") :body-params {})))))) + (app (assoc (->request "closed") :body-params {})))))) (testing "when schemas are not closed and extra keys are not stripped" (let [app (->app {:compile (fn [v _] v) :strip-extra-keys false})] @@ -449,7 +467,35 @@ (testing "failed response" (let [{:keys [status body]} (app (->request [{:message "kosh"}]))] (is (= 500 status)) - (is (= :reitit.coercion/response-coercion (:type body)))))))))) + (is (= :reitit.coercion/response-coercion (:type body)))))))) + + (testing "encoding responses" + (let [->app (fn [total-schema] + (ring/ring-handler + (ring/router + ["/total" {:get {:parameters {:query [:map [:x :int]]} + :responses {200 {:body [:map [:total total-schema]]}} + :handler (fn [{{{:keys [x]} :query} :parameters}] + {:status 200 + :body {:total (* x x)}})}}] + {:data {:middleware [rrc/coerce-request-middleware + rrc/coerce-response-middleware] + :coercion malli/coercion}}))) + call (fn [accept total-schema] + ((->app total-schema) {:uri "/total" + :request-method :get + :muuntaja/request {:format "application/json"} + :muuntaja/response {:format accept} + :query-params {"x" "2"}}))] + + (testing "no encoding" + (is (= {:status 200, :body {:total +4}} (call "application/json" :int)))) + + (testing "json encoding" + (is (= {:status 200, :body {:total -4}} (call "application/json" [:int {:encode/json -}])))) + + (testing "edn encoding (nada)" + (is (= {:status 200, :body {:total +4}} (call "application/edn" [:int {:encode/json -}])))))))) #?(:clj (deftest muuntaja-test From 020c424b4e5577ffb01f6dcf6635e0c298f17745 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Tue, 3 Aug 2021 13:24:29 +0300 Subject: [PATCH 5/5] dead code, CHANGELOG --- CHANGELOG.md | 4 ++++ test/cljc/reitit/ring_coercion_test.cljc | 18 ------------------ 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95a96aae..d49cbb30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,10 @@ We use [Break Versioning][breakver]. The version numbers follow a `.