diff --git a/README.md b/README.md index 15c7c927..1a5d78ac 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# reitit +# reitit [![Build Status](https://github.com/metosin/reitit/actions/workflows/testsuite.yml/badge.svg)](https://github.com/metosin/reitit/actions) [![cljdoc badge](https://cljdoc.org/badge/metosin/reitit)](https://cljdoc.org/d/metosin/reitit/) @@ -54,7 +54,7 @@ There is [#reitit](https://clojurians.slack.com/messages/reitit/) in [Clojurians * `metosin/reitit-sieppari` support for [Sieppari](https://github.com/metosin/sieppari) * `metosin/reitit-dev` - development utilities -... * This is not a typo; the new `reitit-openapi` was released under the new, verified `fi.metosin` group. Existing +... * This is not a typo; the new `reitit-openapi` was released under the new, verified `fi.metosin` group. Existing modules will continue to be released under `metosin` for compatibility purposes. ## Extra modules @@ -109,6 +109,7 @@ A Ring routing app with input & output coercion using [data-specs](https://githu (require '[reitit.ring :as ring]) (require '[reitit.coercion.spec]) (require '[reitit.ring.coercion :as rrc]) +(require '[reitit.ring.middleware.exception :as exception]) (require '[reitit.ring.middleware.muuntaja :as muuntaja]) (require '[reitit.ring.middleware.parameters :as parameters]) @@ -124,39 +125,45 @@ A Ring routing app with input & output coercion using [data-specs](https://githu ;; router data affecting all routes {:data {:coercion reitit.coercion.spec/coercion :muuntaja m/instance - :middleware [parameters/parameters-middleware + :middleware [parameters/parameters-middleware ; decoding query & form params + muuntaja/format-middleware ; content negotiation + exception/exception-middleware ; converting exceptions to HTTP responses rrc/coerce-request-middleware - muuntaja/format-response-middleware rrc/coerce-response-middleware]}}))) ``` Valid request: ```clj -(app {:request-method :get - :uri "/api/math" - :query-params {:x "1", :y "2"}}) +(-> (app {:request-method :get + :uri "/api/math" + :query-params {:x "1", :y "2"}}) + (update :body slurp)) ; {:status 200 -; :body {:total 3}} +; :body "{\"total\":3}" +; :headers {"Content-Type" "application/json; charset=utf-8"}} ``` Invalid request: ```clj -(app {:request-method :get - :uri "/api/math" - :query-params {:x "1", :y "a"}}) -;{:status 400, -; :body {:type :reitit.coercion/request-coercion, -; :coercion :spec, -; :spec "(spec-tools.core/spec {:spec (clojure.spec.alpha/keys :req-un [:$spec20745/x :$spec20745/y]), :type :map, :keys #{:y :x}, :keys/req #{:y :x}})", -; :problems [{:path [:y], -; :pred "clojure.core/int?", -; :val "a", -; :via [:$spec20745/y], -; :in [:y]}], -; :value {:x "1", :y "a"}, -; :in [:request :query-params]}} +(-> (app {:request-method :get + :uri "/api/math" + :query-params {:x "1", :y "a"}}) + (update :body jsonista.core/read-value)) +; {:status 400 +; :headers {"Content-Type" "application/json; charset=utf-8"} +; :body {"spec" "(spec-tools.core/spec {:spec (clojure.spec.alpha/keys :req-un [:spec$8974/x :spec$8974/y]), :type :map, :leaf? false})" +; "value" {"x" "1" +; "y" "a"} +; "problems" [{"via" ["spec$8974/y"] +; "path" ["y"] +; "pred" "clojure.core/int?" +; "in" ["y"] +; "val" "a"}] +; "type" "reitit.coercion/request-coercion" +; "coercion" "spec" +; "in" ["request" "query-params"]}} ``` ## More examples diff --git a/doc/ring/data_driven_middleware.md b/doc/ring/data_driven_middleware.md index 2d310ff4..b86a23d9 100644 --- a/doc/ring/data_driven_middleware.md +++ b/doc/ring/data_driven_middleware.md @@ -2,6 +2,8 @@ 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. +For the basics of reitit middleware, [read this first](ring.md#middleware). + Reitit defines middleware as data: 1. A middleware can be defined as first-class data entries diff --git a/doc/ring/default_middleware.md b/doc/ring/default_middleware.md index ae1d375b..18a1df7c 100644 --- a/doc/ring/default_middleware.md +++ b/doc/ring/default_middleware.md @@ -17,8 +17,6 @@ Any Ring middleware can be used with `reitit-ring`, but using data-driven middle `reitit.ring.middleware.parameters/parameters-middleware` to capture query- and form-params. Wraps `ring.middleware.params/wrap-params`. -**NOTE**: This middleware will be factored into two parts: a query-parameters middleware and a Muuntaja format responsible for the the `application/x-www-form-urlencoded` body format. cf. https://github.com/metosin/reitit/issues/134 - ## Exception Handling See [Exception Handling with Ring](exceptions.md). diff --git a/doc/ring/ring.md b/doc/ring/ring.md index c905fb1a..51c58542 100644 --- a/doc/ring/ring.md +++ b/doc/ring/ring.md @@ -141,7 +141,7 @@ Name-based reverse routing: # Middleware -Middleware can be mounted using a `:middleware` key - either to top-level or under request method submap. Its value should be a vector of `reitit.middleware/IntoMiddleware` values. These include: +Middleware can be mounted using a `:middleware` key in [Route Data](../basics/route_data.md) - either to top-level or under request method submap. Its value should be a vector of `reitit.middleware/IntoMiddleware` values. These include: 1. normal ring middleware function `handler -> request -> response` 2. vector of middleware function `[handler args*] -> request -> response` and it's arguments @@ -194,11 +194,56 @@ Top-level middleware, applied before any routing is done: (def app (ring/ring-handler (ring/router - ["/api" {:middleware [[mw :api]]} + ["/api" {:middleware [[wrap :api]]} ["/get" {:get handler}]]) nil - {:middleware [[mw :top]]})) + {:middleware [[wrap :top]]})) (app {:request-method :get, :uri "/api/get"}) ; {:status 200, :body [:top :api :ok]} ``` + +Same middleware for all routes, using [top-level route data](route_data.md#top-level-route-data): + +```clj +(def app + (ring/ring-handler + (ring/router + ["/api" + ["/get" {:get handler + :middleware [[wrap :specific]]}]] + {:data {:middleware [[wrap :generic]]}}))) + +(app {:request-method :get, :uri "/api/get"}) +; {:status 200, :body [:generic :specific :handler]} +``` + +## Execution order + +Here's a full example that shows the execution order of the middleware +using all of the above techniques: + + +```clj +(def app + (ring/ring-handler + (ring/router + ["/api" {:middleware [[wrap :3-parent]]} + ["/get" {:get handler + :middleware [[wrap :4-route]]}]] + {:data {:middleware [[wrap :2-top-level-route-data]]}}) + nil + {:middleware [[wrap :1-top]]})) + +(app {:request-method :get, :uri "/api/get"}) +; {:status 200, :body [:1-top :2-top-level-route-data :3-parent :4-route :handler]} +``` + +## Which method should I use for defining middleware? + +- If you have middleware that you want to apply to the default handler (second argument of `ring/ring-handler`), use _top-level middleware_ +- If you have a generic middleware, that doesn't depend on the route, use _top-level middleware_ or _top-level route data_ + - If you are using top-level route data anyway for some other reasons, it might be clearest to have all the middleware there. This is what most of the reitit examples do. +- If you want to apply a middleware to only a couple of routes, use _nested middleware_ (ie. _route data_) +- If you want a middleware to apply to all routes, but use route-specific data, you need _top-level route data_ combined with [Compiling Middleware](compiling_middleware.md) + - This is what many reitit features like [Ring Coercion](coercion.md) do. Check the examples & docs for the reitit features you want to use!