These probably work in Gitbook to some extent but they don't work on GitHub. In most places of the documentation files are referenced via their source files (`.md`) and I assume Gitbook deals fine with that For cljdoc the correct links are required to properly fix links that are broken when rendering the document on different URLs.
5.8 KiB
Pluggable Coercion
Basic coercion is explained in detail in the Coercion Guide. With Ring, both request parameters (:query, :body, :form, :header and :path) and response :body can be coerced.
To enable coercion, the following things need to be done:
- Define a
reitit.coercion/Coercionfor the routes - Define types for the parameters and/or responses
- Mount Coercion Middleware to apply to coercion
- Use the coerced parameters in a handler/middleware
Define coercion
reitit.coercion/Coercion is a protocol defining how types are defined, coerced and inventoried.
Reitit ships with the following coercion modules:
reitit.coercion.schema/coercionfor plumatic schemareitit.coercion.spec/coercionfor both clojure.spec and data-specs
Coercion can be attached to route data under :coercion key. There can be multiple Coercion implementations within a single router, normal scoping rules apply.
Defining parameters and responses
Parameters are defined in :parameters key and responses in :responses.
Below is an example with Plumatic Schema. It defines input schemas for :query, :body and :path parameters and a schema for a successful response :body.
Handler can access the coerced parameters can be read under :parameters key in the request.
(require '[reitit.coercion.schema])
(require '[schema.core :as s])
(def PositiveInt (s/constrained s/Int pos? 'PositiveInt))
(def plus-endpoint
{:coercion reitit.coercion.schema/coercion
:parameters {:query {:x s/Int}
:body {:y s/Int}
:path {:z s/Int}}
:responses {200 {:body {:total PositiveInt}}}
:handler (fn [{:keys [parameters]}]
(let [total (+ (-> parameters :query :x)
(-> parameters :body :y)
(-> parameters :path :z))]
{:status 200
:body {:total total}}))})
Coercion Middleware
Defining a coercion for a route data doesn't do anything, as it's just data. We have to attach some code to apply the actual coercion. We can use the middleware from reitit.ring.coercion:
coerce-request-middlewareto apply the parameter coercioncoerce-response-middlewareto apply the response coercioncoerce-exceptions-middlewareto transform coercion exceptions into pretty responses
Full example
Here's an full example for applying coercion with Reitit, Ring and Schema:
(require '[reitit.ring.coercion :as rrc])
(require '[reitit.coercion.schema])
(require '[reitit.ring :as ring])
(require '[schema.core :as s])
(def PositiveInt (s/constrained s/Int pos? 'PositiveInt))
(def app
(ring/ring-handler
(ring/router
["/api"
["/ping" {:name ::ping
:get (fn [_]
{:status 200
:body "pong"})}]
["/plus/:z" {:name ::plus
:post {:coercion reitit.coercion.schema/coercion
:parameters {:query {:x s/Int}
:body {:y s/Int}
:path {:z s/Int}}
:responses {200 {:body {:total PositiveInt}}}
:handler (fn [{:keys [parameters]}]
(let [total (+ (-> parameters :query :x)
(-> parameters :body :y)
(-> parameters :path :z))]
{:status 200
:body {:total total}}))}}]]
{:data {:middleware [rrc/coerce-exceptions-middleware
rrc/coerce-request-middleware
rrc/coerce-response-middleware]}})))
Valid request:
(app {:request-method :post
:uri "/api/plus/3"
:query-params {"x" "1"}
:body-params {:y 2}})
; {:status 200, :body {:total 6}}
Invalid request:
(app {:request-method :post
:uri "/api/plus/3"
:query-params {"x" "abba"}
:body-params {:y 2}})
; {:status 400,
; :body {:schema {:x "Int", "Any" "Any"},
; :errors {:x "(not (integer? \"abba\"))"},
; :type :reitit.coercion/request-coercion,
; :coercion :schema,
; :value {:x "abba"},
; :in [:request :query-params]}}
Invalid response:
(app {:request-method :post
:uri "/api/plus/3"
:query-params {"x" "1"}
:body-params {:y -10}})
; {:status 500,
; :body {:schema {:total "(constrained Int PositiveInt)"},
; :errors {:total "(not (PositiveInt -6))"},
; :type :reitit.coercion/response-coercion,
; :coercion :schema,
; :value {:total -6},
; :in [:response :body]}}
Optimizations
The coercion middleware are compiled againts a route. 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:
(require '[reitit.core :as r])
(-> (ring/get-router app)
(r/match-by-name ::plus)
:result :post :middleware
(->> (mapv :name)))
; [::mw/coerce-exceptions
; ::mw/coerce-request
; ::mw/coerce-response]
Route without coercion defined:
(app {:request-method :get, :uri "/api/ping"})
; {:status 200, :body "pong"}
Has no mounted middleware:
(-> (ring/get-router app)
(r/match-by-name ::ping)
:result :get :middleware
(->> (mapv :name)))
; []