diff --git a/doc/SUMMARY.md b/doc/SUMMARY.md index 0564015e..84760108 100644 --- a/doc/SUMMARY.md +++ b/doc/SUMMARY.md @@ -27,6 +27,7 @@ * [Static Resources](ring/static.md) * [Dynamic Extensions](ring/dynamic_extensions.md) * [Data-driven Middleware](ring/data_driven_middleware.md) + * [Transformations Middleware Chain](ring/transformating_middleware_chain.md) * [Middleware Registry](ring/middleware_registry.md) * [Default Middleware](ring/default_middleware.md) * [Pluggable Coercion](ring/coercion.md) diff --git a/doc/images/swagger.png b/doc/images/swagger.png index 46d28517..9d5a55b8 100644 Binary files a/doc/images/swagger.png and b/doc/images/swagger.png differ diff --git a/doc/ring/README.md b/doc/ring/README.md index f215c0f7..66f776d6 100644 --- a/doc/ring/README.md +++ b/doc/ring/README.md @@ -6,6 +6,7 @@ * [Static Resources](static.md) * [Dynamic Extensions](dynamic_extensions.md) * [Data-driven Middleware](data_driven_middleware.md) +* [Transformations Middleware Chain](transformating_middleware_chain.md) * [Middleware Registry](middleware_registry.md) * [Default Middleware](default_middleware.md) * [Pluggable Coercion](coercion.md) diff --git a/doc/ring/data_driven_middleware.md b/doc/ring/data_driven_middleware.md index 005be53b..439c1804 100644 --- a/doc/ring/data_driven_middleware.md +++ b/doc/ring/data_driven_middleware.md @@ -88,49 +88,8 @@ All the middleware are applied correctly: Middleware can be optimized against an endpoint using [middleware compilation](compiling_middleware.md). -## Transforming the middleware chain - -There is an extra option in ring-router (actually, in the undelaying middleware-router): `:reitit.middleware/transform` to transform the middleware chain per endpoint. It sees the vector of compiled middleware and should return a new vector of middleware. - -#### Adding debug middleware between all other middleware - -```clj -(def app - (ring/ring-handler - (ring/router - ["/api" {:middleware [[wrap 1] [wrap2 2]]} - ["/ping" {:get {:middleware [[wrap3 3]] - :handler handler}}]] - {::middleware/transform #(interleave % (repeat [wrap :debug]))}))) -``` - -```clj -(app {:request-method :get, :uri "/api/ping"}) -; {:status 200, :body [1 :debug 2 :debug 3 :debug :handler]} -``` - -#### Reversing the middleware chain - -```clj -(def app - (ring/ring-handler - (ring/router - ["/api" {:middleware [[wrap 1] [wrap2 2]]} - ["/ping" {:get {:middleware [[wrap3 3]] - :handler handler}}]] - {::middleware/transform reverse)}))) -``` - -```clj -(app {:request-method :get, :uri "/api/ping"}) -; {:status 200, :body [3 2 1 :handler]} -``` - ## Ideas for the future -* Re-package all useful middleware into (optimized) data-driven Middleware - * just package or a new community-repo with rehosting stuff? -* Support `Keyword` expansion into Middleware, enabling external Middleware Registries (duct/integrant/macchiato -style) * Support Middleware dependency resolution with new keys `:requires` and `:provides`. Values are set of top-level keys of the request. e.g. * `InjectUserIntoRequestMiddleware` requires `#{:session}` and provides `#{:user}` * `AuthorizationMiddleware` requires `#{:user}` diff --git a/doc/ring/swagger.md b/doc/ring/swagger.md index 23ed2855..05e3a037 100644 --- a/doc/ring/swagger.md +++ b/doc/ring/swagger.md @@ -108,80 +108,95 @@ Swagger-ui: ### More complete example -* `clojure.spec` and `Schema` coercion -* swagger data (`:tags`, `:produces`, `:consumes`) -* swagger-spec served from `"/api/swagger.json"` +* `clojure.spec` coercion +* swagger data (`:tags`, `:produces`, `:summary`) +* swagger-spec served from `"/swagger.json"` * swagger-ui mounted to `"/"` -* [Muuntaja](https://github.com/metosin/muuntaja) for request & response formatting -* `wrap-params` to capture query & path parameters +* set of middleware for content negotiation, exceptions, multipart etc. * missed routes are handled by `create-default-handler` * served via [ring-jetty](https://github.com/ring-clojure/ring/tree/master/ring-jetty-adapter) Whole example project is in [`/examples/ring-swagger`](https://github.com/metosin/reitit/tree/master/examples/ring-swagger). ```clj -(require '[reitit.ring :as ring] -(require '[reitit.swagger :as swagger] -(require '[reitit.swagger-ui :as swagger-ui] -;; coercion -(require '[reitit.ring.coercion :as rrc] -(require '[reitit.coercion.spec :as spec] -(require '[reitit.coercion.schema :as schema] -(require '[schema.core :refer [Int]] -;; web server -(require '[ring.adapter.jetty :as jetty] -(require '[ring.middleware.params] -(require '[muuntaja.middleware])) +(ns example.server + (:require [reitit.ring :as ring] + [reitit.swagger :as swagger] + [reitit.swagger-ui :as swagger-ui] + [reitit.ring.coercion :as coercion] + [reitit.coercion.spec] + [reitit.ring.middleware.muuntaja :as muuntaja] + [reitit.ring.middleware.exception :as exception] + [reitit.ring.middleware.multipart :as multipart] + [ring.middleware.params :as params] + [ring.adapter.jetty :as jetty] + [muuntaja.core :as m] + [clojure.java.io :as io])) (def app (ring/ring-handler (ring/router - ["/api" - - ["/swagger.json" + [["/swagger.json" {:get {:no-doc true :swagger {:info {:title "my-api"}} :handler (swagger/create-swagger-handler)}}] - ["/spec" - {:coercion spec/coercion - :swagger {:tags ["spec"]}} + ["/files" + {:swagger {:tags ["files"]}} + + ["/upload" + {:post {:summary "upload a file" + :parameters {:multipart {:file multipart/temp-file-part}} + :responses {200 {:body {:file multipart/temp-file-part}}} + :handler (fn [{{{:keys [file]} :multipart} :parameters}] + {:status 200 + :body {:file file}})}}] + + ["/download" + {:get {:summary "downloads a file" + :swagger {:produces ["image/png"]} + :handler (fn [_] + {:status 200 + :headers {"Content-Type" "image/png"} + :body (io/input-stream (io/resource "reitit.png"))})}}]] + + ["/math" + {:swagger {:tags ["math"]}} ["/plus" - {:get {:summary "plus with spec" + {:get {:summary "plus with spec query parameters" :parameters {:query {:x int?, :y int?}} :responses {200 {:body {:total int?}}} :handler (fn [{{{:keys [x y]} :query} :parameters}] {:status 200 - :body {:total (+ x y)}})}}]] + :body {:total (+ x y)}})} + :post {:summary "plus with spec body parameters" + :parameters {:body {:x int?, :y int?}} + :responses {200 {:body {:total int?}}} + :handler (fn [{{{:keys [x y]} :body} :parameters}] + {:status 200 + :body {:total (+ x y)}})}}]]] - ["/schema" - {:coercion schema/coercion - :swagger {:tags ["schema"]}} - - ["/plus" - {:get {:summary "plus with schema" - :parameters {:query {:x Int, :y Int}} - :responses {200 {:body {:total Int}}} - :handler (fn [{{{:keys [x y]} :query} :parameters}] - {:status 200 - :body {:total (+ x y)}})}}]]] - - {:data {:middleware [ring.middleware.params/wrap-params - muuntaja.middleware/wrap-format - swagger/swagger-feature - rrc/coerce-exceptions-middleware - rrc/coerce-request-middleware - rrc/coerce-response-middleware] - :swagger {:produces #{"application/json" - "application/edn" - "application/transit+json"} - :consumes #{"application/json" - "application/edn" - "application/transit+json"}}}}) + {:data {:coercion reitit.coercion.spec/coercion + :muuntaja m/instance + :middleware [;; query-params & form-params + params/wrap-params + ;; content-negotiation + muuntaja/format-negotiate-middleware + ;; encoding response body + muuntaja/format-response-middleware + ;; exception handling + exception/exception-middleware + ;; decoding request body + muuntaja/format-request-middleware + ;; coercing response bodys + coercion/coerce-response-middleware + ;; coercing request parameters + coercion/coerce-request-middleware + ;; multipart + multipart/multipart-middleware]}}) (ring/routes - (swagger-ui/create-swagger-ui-handler - {:path "/", :url "/api/swagger.json"}) + (swagger-ui/create-swagger-ui-handler {:path "/"}) (ring/create-default-handler)))) (defn start [] @@ -242,7 +257,6 @@ Example with: ### TODO -* create a data-driven version of [Muuntaja](https://github.com/metosin/muuntaja) that integrates into `:produces` and `:consumes` * ClojureScript * example for [Macchiato](https://github.com/macchiato-framework) * body formatting diff --git a/doc/ring/transforming_middleware_chain.md b/doc/ring/transforming_middleware_chain.md new file mode 100644 index 00000000..6179bffb --- /dev/null +++ b/doc/ring/transforming_middleware_chain.md @@ -0,0 +1,37 @@ +# Transformation Middleware Chain + +There is an extra option in ring-router (actually, in the underlying middleware-router): `:reitit.middleware/transform` to transform the middleware chain per endpoint. It gets the vector of compiled middleware and should return a new vector of middleware. + +## Adding debug middleware between all other middleware + +```clj +(def app + (ring/ring-handler + (ring/router + ["/api" {:middleware [[wrap 1] [wrap2 2]]} + ["/ping" {:get {:middleware [[wrap3 3]] + :handler handler}}]] + {::middleware/transform #(interleave % (repeat [wrap :debug]))}))) +``` + +```clj +(app {:request-method :get, :uri "/api/ping"}) +; {:status 200, :body [1 :debug 2 :debug 3 :debug :handler]} +``` + +## Reversing the middleware chain + +```clj +(def app + (ring/ring-handler + (ring/router + ["/api" {:middleware [[wrap 1] [wrap2 2]]} + ["/ping" {:get {:middleware [[wrap3 3]] + :handler handler}}]] + {::middleware/transform reverse)}))) +``` + +```clj +(app {:request-method :get, :uri "/api/ping"}) +; {:status 200, :body [3 2 1 :handler]} +``` diff --git a/modules/reitit-middleware/src/reitit/ring/middleware/muuntaja.clj b/modules/reitit-middleware/src/reitit/ring/middleware/muuntaja.clj index 28e02e8f..faa44e2c 100644 --- a/modules/reitit-middleware/src/reitit/ring/middleware/muuntaja.clj +++ b/modules/reitit-middleware/src/reitit/ring/middleware/muuntaja.clj @@ -63,7 +63,7 @@ (if muuntaja {:data {:swagger {:consumes (displace (m/decodes muuntaja))}} :wrap #(muuntaja.middleware/wrap-format-request % muuntaja)}))}) -© + (def format-response-middleware "Middleware for response formatting.