2017-12-15 06:20:53 +00:00
# Ring Coercion
2017-11-27 06:02:35 +00:00
2017-12-15 06:20:53 +00:00
Coercion is explained in detail [in the Coercion Guide ](../coercion/coercion.md ). Both request parameters (`:query`, `:body` , `:form` , `:header` and `:path` ) and response `:body` can be coerced.
2017-11-27 06:02:35 +00:00
2017-12-15 06:20:53 +00:00
To enable coercion, the following things need to be done:
2017-11-27 06:02:35 +00:00
2017-12-15 06:20:53 +00:00
* Define a `reitit.coercion/Coercion` for 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
2017-11-27 06:02:35 +00:00
2017-12-15 06:20:53 +00:00
## Define coercion
2017-11-27 06:02:35 +00:00
2017-12-15 06:20:53 +00:00
`reitit.coercion/Coercion` is a protocol defining how types are defined, coerced and inventoried.
2017-11-27 06:02:35 +00:00
2017-12-15 06:20:53 +00:00
Reitit ships with the following coercion modules:
2017-11-27 06:02:35 +00:00
2017-12-15 06:20:53 +00:00
* `reitit.coercion.schema/coercion` for [plumatic schema ](https://github.com/plumatic/schema )
* `reitit.coercion.spec/coercion` for both [clojure.spec ](https://clojure.org/about/spec ) and [data-specs ](https://github.com/metosin/spec-tools#data-specs )
2017-11-27 06:02:35 +00:00
2017-12-15 06:20:53 +00:00
Coercion can be attached to route data under `:coercion` key. There can be multiple `Coercion` implementations within a single router, normal [scoping rules ](../basics/route_data.html#nested-route-data ) apply.
2017-11-27 06:02:35 +00:00
2017-12-15 06:20:53 +00:00
# Defining parameters and responses
Below is a ring route data defining [Plumatic Schema ](https://github.com/plumatic/schema ) coercion. It defines schemas for `:query` , `:body` and `:path` parameters and for a successful response `:body` .
The coerced parameters can be read under `:parameters` key in the request.
2017-11-27 06:02:35 +00:00
```clj
2017-12-15 06:20:53 +00:00
(require '[reitit.coercion.schema])
2017-11-27 06:02:35 +00:00
(require '[schema.core :as s])
2017-12-15 06:20:53 +00:00
(def plus-endpoint
{:coercion reitit.coercion.schema/coercion
:parameters {:query {:x s/Int}
:body {:y s/Int}
:path {:z s/Int}}
:responses {200 {:schema {:total PositiveInt}}}
:handler (fn [{:keys [parameters]}]
(let [total (+ (-> parameters :query :x)
(-> parameters :body :y)
(-> parameters :path :z))]
{:status 200
:body {:total total}}))})
2017-11-27 06:02:35 +00:00
```
2017-12-15 06:20:53 +00:00
## Coercion Middleware
2017-11-27 06:02:35 +00:00
2017-12-15 06:20:53 +00:00
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-middleware` :
2017-11-27 06:02:35 +00:00
2017-12-15 06:20:53 +00:00
* `coerce-request-middleware` for the parameter coercion
* `coerce-response-middleware` for the response coercion
* `coerce-exceptions-middleware` to turn coercion exceptions into pretty responses
2017-11-27 06:02:35 +00:00
2017-12-15 06:20:53 +00:00
### Example with Schema
2017-11-27 06:02:35 +00:00
```clj
2017-12-15 06:20:53 +00:00
(require '[reitit.ring.coercion-middleware :as mw])
2017-11-27 06:02:35 +00:00
(def app
(ring/ring-handler
(ring/router
["/api"
2017-12-15 06:20:53 +00:00
["/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 {:schema {:total PositiveInt}}}
:handler (fn [{:keys [parameters]}]
(let [total (+ (-> parameters :query :x)
(-> parameters :body :y)
(-> parameters :path :z))]
{:status 200
:body {:total total}}))}}]]
{:data {:middleware [mw/coerce-exceptions-middleware
mw/coerce-request-middleware
mw/coerce-response-middleware]}})))
2017-11-27 06:02:35 +00:00
```
Valid request:
```clj
2017-12-15 06:20:53 +00:00
(app {:request-method :post
:uri "/api/plus/3"
:query-params {"x" "1"}
:body-params {:y 2}})
; {:status 200, :body {:total 6}}
2017-11-27 06:02:35 +00:00
```
Invalid request:
```clj
2017-12-15 06:20:53 +00:00
(app {:request-method :post
:uri "/api/plus/3"
:query-params {"x" "abba"}
:body-params {:y 2}})
2017-11-27 06:02:35 +00:00
; {:status 400,
2017-12-15 06:20:53 +00:00
; :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]}}
2017-11-27 06:02:35 +00:00
```
2017-12-15 06:20:53 +00:00
Invalid response:
2017-11-27 06:02:35 +00:00
```clj
2017-12-15 06:20:53 +00:00
(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]}}
2017-11-27 06:02:35 +00:00
```
2017-12-15 06:20:53 +00:00
### Optimizations
2017-11-27 06:02:35 +00:00
2017-12-15 06:20:53 +00:00
The coercion middleware are [compiled againts a route ](compiling_middleware,md ). This enables them to compile and cache the actual coercers for the defined models ahead of time. They also unmount if a route doesn't have `:coercion` and `:parameters` or `:responses` defined.
2017-11-27 06:02:35 +00:00
2017-12-15 06:20:53 +00:00
We can query the compiled middleware chain for the routes:
2017-11-27 06:02:35 +00:00
```clj
2017-12-15 06:20:53 +00:00
(require '[reitit.core :as r])
(-> (ring/get-router app)
(r/match-by-name ::plus)
:result :post :middleware
(->> (mapv :name)))
; [::mw/coerce-exceptions
; ::mw/coerce-parameters
; ::mw/coerce-response]
2017-11-27 06:02:35 +00:00
```
2017-12-15 06:20:53 +00:00
Route without coercion defined:
2017-11-27 06:02:35 +00:00
2017-12-15 06:20:53 +00:00
```clj
(app {:request-method :get, :uri "/api/ping"})
; {:status 200, :body "pong"}
```
2017-11-27 06:02:35 +00:00
2017-12-15 06:20:53 +00:00
Has no mounted middleware:
2017-11-27 06:02:35 +00:00
```clj
2017-12-15 06:20:53 +00:00
(-> (ring/get-router app)
(r/match-by-name ::ping)
:result :get :middleware
(->> (mapv :name)))
; []
2017-11-27 06:02:35 +00:00
```
2017-12-15 06:20:53 +00:00
## Thanks to
Most of the thing are just polished version of the original implementations. Thanks to:
* [compojure-api ](https://clojars.org/metosin/compojure-api ) for the initial `Coercion` protocol
* [ring-swagger ](https://github.com/metosin/ring-swagger#more-complete-example ) for the `:parameters` and `:responses` syntax.
* [schema ](https://github.com/plumatic/schema ) and [schema-tools ](https://github.com/metosin/schema-tools ) for Schema Coercion
* [spec-tools ](https://github.com/metosin/spec-tools ) for Spec Coercion