Coercion is a process of transforming parameters (and responses) from one format into another. Reitit separates routing and coercion into two separate steps.
By default, all wildcard and catch-all parameters are parsed into strings:
(require '[reitit.core :as r])
(def router
(r/router
["/:company/users/:user-id" ::user-view]))
Match with the parsed :path-params
as strings:
(r/match-by-path r "/metosin/users/123")
; #Match{:template "/:company/users/:user-id",
; :data {:name :user/user-view},
; :result nil,
; :path-params {:company "metosin", :user-id "123"},
; :path "/metosin/users/123"}
To enable parameter coercion, the following things need to be done:
Coercion
for the routesreitit.coercion/Coercion
is a protocol defining how types are defined, coerced and inventoried.
Reitit ships with the following coercion modules:
reitit.coercion.malli/coercion
for mallireitit.coercion.schema/coercion
for plumatic schemareitit.coercion.spec/coercion
for both clojure.spec and data-specsCoercion can be attached to route data under :coercion
key. There can be multiple Coercion
implementations within a single router, normal scoping rules apply.
Route parameters can be defined via route data :parameters
. It has keys for different type of parameters: :query
, :body
, :form
, :header
and :path
. Syntax for the actual parameters depends on the Coercion
implementation.
Example with Schema path-parameters:
(require '[reitit.coercion.schema])
(require '[schema.core :as s])
(def router
(r/router
["/:company/users/:user-id" {:name ::user-view
:coercion reitit.coercion.schema/coercion
:parameters {:path {:company s/Str
:user-id s/Int}}}]))
A Match:
(r/match-by-path r "/metosin/users/123")
; #Match{:template "/:company/users/:user-id",
; :data {:name :user/user-view,
; :coercion <<:schema>>
; :parameters {:path {:company java.lang.String,
; :user-id Int}}},
; :result nil,
; :path-params {:company "metosin", :user-id "123"},
; :path "/metosin/users/123"}
Coercion was not applied. Why? In Reitit, routing and coercion are separate processes and we have done just the routing part. We need to apply coercion after the successful routing.
But now we should have enough data on the match to apply the coercion.
Before the actual coercion, we ~~should~~ need to compile the coercers against the route data. Compiled coercers yield much better performance and the manual step of adding a coercion compiler makes things explicit and non-magical.
Compiling can be done via a Middleware, Interceptor or a Router. We apply it now at router-level, effecting all routes (with :parameters
and :coercion
defined).
There is a helper function reitit.coercion/compile-request-coercers
just for this:
(require '[reitit.coercion :as coercion])
(require '[reitit.coercion.schema])
(require '[schema.core :as s])
(def router
(r/router
["/:company/users/:user-id" {:name ::user-view
:coercion reitit.coercion.schema/coercion
:parameters {:path {:company s/Str
:user-id s/Int}}}]
{:compile coercion/compile-request-coercers}))
Routing again:
(r/match-by-path r "/metosin/users/123")
; #Match{:template "/:company/users/:user-id",
; :data {:name :user/user-view,
; :coercion <<:schema>>
; :parameters {:path {:company java.lang.String,
; :user-id Int}}},
; :result {:path #object[reitit.coercion$request_coercer$]},
; :path-params {:company "metosin", :user-id "123"},
; :path "/metosin/users/123"}
The compiler added a :result
key into the match (done just once, at router creation time), which holds the compiled coercers. We are almost done.
We can use a helper function reitit.coercion/coerce!
to do the actual coercion, based on a Match
:
(coercion/coerce!
(r/match-by-path router "/metosin/users/123"))
; {:path {:company "metosin", :user-id 123}}
We get the coerced parameters back. If a coercion fails, a typed (:reitit.coercion/request-coercion
) ExceptionInfo is thrown, with data about the actual error:
(coercion/coerce!
(r/match-by-path router "/metosin/users/ikitommi"))
; => ExceptionInfo Request coercion failed:
; #CoercionError{:schema {:company java.lang.String, :user-id Int, Any Any},
; :errors {:user-id (not (integer? "ikitommi"))}}
; clojure.core/ex-info (core.clj:4739)
Here's a full example for doing routing and coercion with Reitit and Schema:
(require '[reitit.coercion.schema])
(require '[reitit.coercion :as coercion])
(require '[reitit.core :as r])
(require '[schema.core :as s])
(def router
(r/router
["/:company/users/:user-id" {:name ::user-view
:coercion reitit.coercion.schema/coercion
:parameters {:path {:company s/Str
:user-id s/Int}}}]
{:compile coercion/compile-request-coercers}))
(defn match-by-path-and-coerce! [path]
(if-let [match (r/match-by-path router path)]
(assoc match :parameters (coercion/coerce! match))))
(match-by-path-and-coerce! "/metosin/users/123")
; #Match{:template "/:company/users/:user-id",
; :data {:name :user/user-view,
; :coercion <<:schema>>
; :parameters {:path {:company java.lang.String,
; :user-id Int}}},
; :result {:path #object[reitit.coercion$request_coercer$]},
; :path-params {:company "metosin", :user-id "123"},
; :parameters {:path {:company "metosin", :user-id 123}}
; :path "/metosin/users/123"}
(match-by-path-and-coerce! "/metosin/users/ikitommi")
; => ExceptionInfo Request coercion failed...
For a full-blown http-coercion, see the ring coercion.
Can you improve this documentation? These fine people already did:
Tommi Reiman, Marcus Spiegel, tjalkane & Martin KlepschEdit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close