Cleanup Coercion

This commit is contained in:
Tommi Reiman 2017-12-09 22:49:32 +02:00
parent 5a74a4269f
commit b9f032a5ce
20 changed files with 444 additions and 277 deletions

View file

@ -1,5 +1,5 @@
(ns example.dspec
(:require [reitit.ring.coercion :as coercion]
(:require [reitit.ring.coercion-middleware :as coercion]
[reitit.ring.coercion.spec :as spec-coercion]
[example.server :as server]))

View file

@ -1,5 +1,5 @@
(ns example.schema
(:require [reitit.ring.coercion :as coercion]
(:require [reitit.ring.coercion-middleware :as coercion]
[reitit.ring.coercion.schema :as schema-coercion]
[example.server :as server]))

View file

@ -1,7 +1,7 @@
(ns example.server
(:require [ring.adapter.jetty :as jetty]
[reitit.middleware :as middleware]
[reitit.ring.coercion :as coercion]))
[reitit.ring.coercion-middleware :as coercion]))
(defonce ^:private server (atom nil))

View file

@ -1,7 +1,7 @@
(ns example.spec
(:require [clojure.spec.alpha :as s]
[spec-tools.spec :as spec]
[reitit.ring.coercion :as coercion]
[reitit.ring.coercion-middleware :as coercion]
[reitit.ring.coercion.spec :as spec-coercion]
[example.server :as server]))

View file

@ -1,5 +1,5 @@
(ns example.dspec
(:require [reitit.ring.coercion :as coercion]
(:require [reitit.ring.coercion-middleware :as coercion]
[reitit.ring.coercion.spec :as spec-coercion]))
(def routes

View file

@ -1,6 +1,6 @@
(ns example.schema
(:require [schema.core :as s]
[reitit.ring.coercion :as coercion]
[reitit.ring.coercion-middleware :as coercion]
[reitit.ring.coercion.schema :as schema-coercion]))
(def routes

View file

@ -3,7 +3,7 @@
[ring.middleware.params]
[muuntaja.middleware]
[reitit.ring :as ring]
[reitit.ring.coercion :as coercion]
[reitit.ring.coercion-middleware :as coercion]
[example.dspec]
[example.schema]
[example.spec]))

View file

@ -1,7 +1,7 @@
(ns example.spec
(:require [clojure.spec.alpha :as s]
[spec-tools.spec :as spec]
[reitit.ring.coercion :as coercion]
[reitit.ring.coercion-middleware :as coercion]
[reitit.ring.coercion.spec :as spec-coercion]))
;; wrap into Spec Records to enable runtime conforming

View file

@ -0,0 +1,134 @@
(ns reitit.coercion
(:require [clojure.walk :as walk]
[spec-tools.core :as st]
[reitit.ring :as ring]
[reitit.impl :as impl]))
;;
;; Protocol
;;
(defprotocol Coercion
"Pluggable coercion protocol"
(-get-name [this] "Keyword name for the coercion")
(-get-apidocs [this model data] "???")
(-compile-model [this model name] "Compiles a model")
(-open-model [this model] "Returns a new model which allows extra keys in maps")
(-encode-error [this error] "Converts error in to a serializable format")
(-request-coercer [this type model] "Returns a `value format => value` request coercion function")
(-response-coercer [this model] "Returns a `value format => value` response coercion function"))
(defrecord CoercionError [])
(defn error? [x]
(instance? CoercionError x))
;;
;; api-docs
;;
#_(defn get-apidocs [coercion spec info]
(protocol/get-apidocs coercion spec info))
;;
;; coercer
;;
(defrecord ParameterCoercion [in style keywordize? open?])
(def ^:no-doc ring-parameter-coercion
{:query (->ParameterCoercion :query-params :string true true)
:body (->ParameterCoercion :body-params :body false false)
:form (->ParameterCoercion :form-params :string true true)
:header (->ParameterCoercion :header-params :string true true)
:path (->ParameterCoercion :path-params :string true true)})
(defn ^:no-doc request-coercion-failed! [result coercion value in request]
(throw
(ex-info
(str "Request coercion failed: " (pr-str result))
(merge
(into {} result)
{:type ::request-coercion
:coercion coercion
:value value
:in [:request in]
:request request}))))
(defn ^:no-doc response-coercion-failed! [result coercion value request response]
(throw
(ex-info
(str "Response coercion failed: " (pr-str result))
(merge
(into {} result)
{:type ::response-coercion
:coercion coercion
:value value
:in [:response :body]
:request request
:response response}))))
;; TODO: support faster key walking, walk/keywordize-keys is quite slow...
(defn request-coercer [coercion type model {:keys [extract-request-format]
:or {extract-request-format (constantly nil)}}]
(if coercion
(let [{:keys [keywordize? open? in style]} (ring-parameter-coercion type)
transform (comp (if keywordize? walk/keywordize-keys identity) in)
model (if open? (-open-model coercion model) model)
coercer (-request-coercer coercion style model)]
(fn [request]
(let [value (transform request)
format (extract-request-format request)
result (coercer value format)]
(if (error? result)
(request-coercion-failed! result coercion value in request)
result))))))
(defn response-coercer [coercion model {:keys [extract-response-format]
:or {extract-response-format (constantly nil)}}]
(if coercion
(let [coercer (-response-coercer coercion model)]
(fn [request response]
(let [format (extract-response-format request response)
value (:body response)
result (coercer value format)]
(if (error? result)
(response-coercion-failed! result coercion value request response)
result))))))
(defn encode-error [data]
(-> data
(dissoc :request :response)
(update :coercion -get-name)
(->> (-encode-error (:coercion data)))))
(defn coerce-request [coercers request]
(reduce-kv
(fn [acc k coercer]
(impl/fast-assoc acc k (coercer request)))
{}
coercers))
(defn coerce-response [coercers request response]
(if response
(if-let [coercer (or (coercers (:status response)) (coercers :default))]
(impl/fast-assoc response :body (coercer request response)))))
(defn request-coercers [coercion parameters opts]
(->> (for [[k v] parameters
:when v]
[k (request-coercer coercion k v opts)])
(into {})))
(defn response-coercers [coercion responses opts]
(->> (for [[status {:keys [schema]}] responses :when schema]
[status (response-coercer coercion schema opts)])
(into {})))
;;
;; integration
;;
(defn compile-request-coercers [[p {:keys [parameters coercion]}] opts]
(if (and parameters coercion)
(request-coercers coercion parameters opts)))

View file

@ -0,0 +1,136 @@
(ns reitit.interceptor
(:require [meta-merge.core :refer [meta-merge]]
[reitit.core :as r]
[reitit.impl :as impl]))
(defprotocol IntoInterceptor
(into-interceptor [this data opts]))
(defrecord Interceptor [name enter leave error])
(defrecord Endpoint [data interceptors])
(defn create [{:keys [name wrap compile] :as m}]
(when (and wrap compile)
(throw
(ex-info
(str "Interceptor can't have both :wrap and :compile defined " m) m)))
(map->Interceptor m))
(def ^:dynamic *max-compile-depth* 10)
(extend-protocol IntoInterceptor
#?(:clj clojure.lang.APersistentVector
:cljs cljs.core.PersistentVector)
(into-interceptor [[f & args] data opts]
(if-let [{:keys [wrap] :as mw} (into-interceptor f data opts)]
(assoc mw :wrap #(apply wrap % args))))
#?(:clj clojure.lang.Fn
:cljs function)
(into-interceptor [this _ _]
(map->Interceptor
{:enter this}))
#?(:clj clojure.lang.PersistentArrayMap
:cljs cljs.core.PersistentArrayMap)
(into-interceptor [this data opts]
(into-interceptor (create this) data opts))
#?(:clj clojure.lang.PersistentHashMap
:cljs cljs.core.PersistentHashMap)
(into-interceptor [this data opts]
(into-interceptor (create this) data opts))
Interceptor
(into-interceptor [{:keys [compile] :as this} data opts]
(if-not compile
this
(let [compiled (::compiled opts 0)
opts (assoc opts ::compiled (inc compiled))]
(when (>= compiled *max-compile-depth*)
(throw
(ex-info
(str "Too deep Interceptor compilation - " compiled)
{:this this, :data data, :opts opts})))
(if-let [interceptor (into-interceptor (compile data opts) data opts)]
(map->Interceptor
(merge
(dissoc this :create)
(impl/strip-nils interceptor)))))))
nil
(into-interceptor [_ _ _]))
(defn- ensure-handler! [path data scope]
(when-not (:handler data)
(throw (ex-info
(str "path \"" path "\" doesn't have a :handler defined"
(if scope (str " for " scope)))
(merge {:path path, :data data}
(if scope {:scope scope}))))))
(defn expand [interceptors data opts]
(->> interceptors
(keep #(into-interceptor % data opts))
(into [])))
(defn interceptor-chain [interceptors handler data opts]
(expand (conj interceptors handler) data opts))
(defn compile-result
([route opts]
(compile-result route opts nil))
([[path {:keys [interceptors handler] :as data}]
{:keys [::transform] :or {transform identity} :as opts} scope]
(ensure-handler! path data scope)
(let [interceptors (expand (transform (expand interceptors data opts)) data opts)]
(map->Endpoint
{:interceptors (interceptor-chain interceptors handler data opts)
:data data}))))
(defn router
"Creates a [[reitit.core/Router]] from raw route data and optionally an options map with
support for Interceptors. See [docs](https://metosin.github.io/reitit/) for details.
Example:
(router
[\"/api\" {:interceptors [i/format i/oauth2]}
[\"/users\" {:interceptors [i/delete]
:handler get-user}]])
See router options from [[reitit.core/router]]."
([data]
(router data nil))
([data opts]
(let [opts (meta-merge {:compile compile-result} opts)]
(r/router data opts))))
(defn interceptor-handler [router]
(with-meta
(fn [path]
(some->> path
(r/match-by-path router)
:result
:interceptors))
{::router router}))
(defn execute [r {{:keys [uri]} :request :as ctx}]
(if-let [interceptors (-> (r/match-by-path r uri)
:result
:interceptors)]
(as-> ctx $
(reduce #(%2 %1) $ (keep :enter interceptors))
(reduce #(%2 %1) $ (keep :leave interceptors)))))
(def r
(router
["/api" {:interceptors [{:name ::add
:enter (fn [ctx]
(assoc ctx :enter true))
:leave (fn [ctx]
(assoc ctx :leave true))}]}
["/ping" (fn [ctx] (assoc ctx :response "ok"))]]))
(execute r {:request {:uri "/api/ping"}})

View file

@ -51,7 +51,7 @@
(when (>= compiled *max-compile-depth*)
(throw
(ex-info
(str "Too deep middleware compilation - " compiled)
(str "Too deep Middleware compilation - " compiled)
{:this this, :data data, :opts opts})))
(if-let [middeware (into-middleware (compile data opts) data opts)]
(map->Middleware

View file

@ -1,175 +0,0 @@
(ns reitit.ring.coercion
(:require [clojure.walk :as walk]
[spec-tools.core :as st]
[reitit.middleware :as middleware]
[reitit.ring.coercion.protocol :as protocol]
[reitit.ring :as ring]
[reitit.impl :as impl]))
#_(defn get-apidocs [coercion spec info]
(protocol/get-apidocs coercion spec info))
;;
;; coercer
;;
(defrecord ParameterCoercion [in style keywordize? open?])
(def ^:no-doc ring-parameter-coercion
{:query (->ParameterCoercion :query-params :string true true)
:body (->ParameterCoercion :body-params :body false false)
:form (->ParameterCoercion :form-params :string true true)
:header (->ParameterCoercion :header-params :string true true)
:path (->ParameterCoercion :path-params :string true true)})
(defn ^:no-doc request-coercion-failed! [result coercion value in request]
(throw
(ex-info
(str "Request coercion failed: " (pr-str result))
(merge
(into {} result)
{:type ::request-coercion
:coercion coercion
:value value
:in [:request in]
:request request}))))
(defn ^:no-doc response-coercion-failed! [result coercion value request response]
(throw
(ex-info
(str "Response coercion failed: " (pr-str result))
(merge
(into {} result)
{:type ::response-coercion
:coercion coercion
:value value
:in [:response :body]
:request request
:response response}))))
;; TODO: support faster key walking, walk/keywordize-keys is quite slow...
(defn ^:no-doc request-coercer [coercion type model {:keys [extract-request-format]
:or {extract-request-format (constantly nil)}}]
(if coercion
(let [{:keys [keywordize? open? in style]} (ring-parameter-coercion type)
transform (comp (if keywordize? walk/keywordize-keys identity) in)
model (if open? (protocol/open-model coercion model) model)
coercer (protocol/request-coercer coercion style model)]
(fn [request]
(let [value (transform request)
format (extract-request-format request)
result (coercer value format)]
(if (protocol/error? result)
(request-coercion-failed! result coercion value in request)
result))))))
(defn ^:no-doc response-coercer [coercion model {:keys [extract-response-format]
:or {extract-response-format (constantly nil)}}]
(if coercion
(let [coercer (protocol/response-coercer coercion model)]
(fn [request response]
(let [format (extract-response-format request response)
value (:body response)
result (coercer value format)]
(if (protocol/error? result)
(response-coercion-failed! result coercion value request response)
result))))))
(defn ^:no-doc encode-error [data]
(-> data
(dissoc :request :response)
(update :coercion protocol/get-name)
(->> (protocol/encode-error (:coercion data)))))
(defn ^:no-doc coerce-request [coercers request]
(reduce-kv
(fn [acc k coercer]
(impl/fast-assoc acc k (coercer request)))
{}
coercers))
(defn ^:no-doc coerce-response [coercers request response]
(if response
(if-let [coercer (or (coercers (:status response)) (coercers :default))]
(impl/fast-assoc response :body (coercer request response)))))
(defn ^:no-doc request-coercers [coercion parameters opts]
(->> (for [[k v] parameters
:when v]
[k (request-coercer coercion k v opts)])
(into {})))
(defn ^:no-doc response-coercers [coercion responses opts]
(->> (for [[status {:keys [schema]}] responses :when schema]
[status (response-coercer coercion schema opts)])
(into {})))
(defn ^:no-doc handle-coercion-exception [e respond raise]
(let [data (ex-data e)]
(if-let [status (condp = (:type data)
::request-coercion 400
::response-coercion 500
nil)]
(respond
{:status status
:body (encode-error data)})
(raise e))))
;;
;; middleware
;;
(def coerce-request-middleware
"Middleware for pluggable request coercion.
Expects a :coercion of type `reitit.coercion.protocol/Coercion`
and :parameters from route data, otherwise does not mount."
(middleware/create
{:name ::coerce-parameters
:compile (fn [{:keys [coercion parameters]} opts]
(if (and coercion parameters)
(let [coercers (request-coercers coercion parameters opts)]
(fn [handler]
(fn
([request]
(let [coerced (coerce-request coercers request)]
(handler (impl/fast-assoc request :parameters coerced))))
([request respond raise]
(let [coerced (coerce-request coercers request)]
(handler (impl/fast-assoc request :parameters coerced) respond raise))))))))}))
(def coerce-response-middleware
"Middleware for pluggable response coercion.
Expects a :coercion of type `reitit.coercion.protocol/Coercion`
and :responses from route data, otherwise does not mount."
(middleware/create
{:name ::coerce-response
:compile (fn [{:keys [coercion responses]} opts]
(if (and coercion responses)
(let [coercers (response-coercers coercion responses opts)]
(fn [handler]
(fn
([request]
(coerce-response coercers request (handler request)))
([request respond raise]
(handler request #(respond (coerce-response coercers request %)) raise)))))))}))
(def coerce-exceptions-middleware
"Middleware for handling coercion exceptions.
Expects a :coercion of type `reitit.coercion.protocol/Coercion`
and :parameters or :responses from route data, otherwise does not mount."
(middleware/create
{:name ::coerce-exceptions
:compile (fn [{:keys [coercion parameters responses]} _]
(if (and coercion (or parameters responses))
(fn [handler]
(fn
([request]
(try
(handler request)
(catch #?(:clj Exception :cljs js/Error) e
(handle-coercion-exception e identity #(throw %)))))
([request respond raise]
(try
(handler request respond #(handle-coercion-exception % respond raise))
(catch #?(:clj Exception :cljs js/Error) e
(handle-coercion-exception e respond raise))))))))}))

View file

@ -1,16 +0,0 @@
(ns reitit.ring.coercion.protocol)
(defprotocol Coercion
"Pluggable coercion protocol"
(get-name [this] "Keyword name for the coercion")
(get-apidocs [this model data] "???")
(compile-model [this model name] "Compiles a model")
(open-model [this model] "Returns a new model which allows extra keys in maps")
(encode-error [this error] "Converts error in to a serializable format")
(request-coercer [this type model] "Returns a `value format => value` request coercion function")
(response-coercer [this model] "Returns a `value format => value` response coercion function"))
(defrecord CoercionError [])
(defn error? [x]
(instance? CoercionError x))

View file

@ -0,0 +1,74 @@
(ns reitit.ring.coercion-middleware
(:require [reitit.middleware :as middleware]
[reitit.coercion :as coercion]
[reitit.impl :as impl]))
(defn handle-coercion-exception [e respond raise]
(let [data (ex-data e)]
(if-let [status (condp = (:type data)
::coercion/request-coercion 400
::coercion/response-coercion 500
nil)]
(respond
{:status status
:body (coercion/encode-error data)})
(raise e))))
;;
;; middleware
;;
(def coerce-request-middleware
"Middleware for pluggable request coercion.
Expects a :coercion of type `reitit.coercion/Coercion`
and :parameters from route data, otherwise does not mount."
(middleware/create
{:name ::coerce-parameters
:compile (fn [{:keys [coercion parameters]} opts]
(if (and coercion parameters)
(let [coercers (coercion/request-coercers coercion parameters opts)]
(fn [handler]
(fn
([request]
(let [coerced (coercion/coerce-request coercers request)]
(handler (impl/fast-assoc request :parameters coerced))))
([request respond raise]
(let [coerced (coercion/coerce-request coercers request)]
(handler (impl/fast-assoc request :parameters coerced) respond raise))))))))}))
(def coerce-response-middleware
"Middleware for pluggable response coercion.
Expects a :coercion of type `reitit.coercion/Coercion`
and :responses from route data, otherwise does not mount."
(middleware/create
{:name ::coerce-response
:compile (fn [{:keys [coercion responses]} opts]
(if (and coercion responses)
(let [coercers (coercion/response-coercers coercion responses opts)]
(fn [handler]
(fn
([request]
(coercion/coerce-response coercers request (handler request)))
([request respond raise]
(handler request #(respond (coercion/coerce-response coercers request %)) raise)))))))}))
(def coerce-exceptions-middleware
"Middleware for handling coercion exceptions.
Expects a :coercion of type `reitit.coercion/Coercion`
and :parameters or :responses from route data, otherwise does not mount."
(middleware/create
{:name ::coerce-exceptions
:compile (fn [{:keys [coercion parameters responses]} _]
(if (and coercion (or parameters responses))
(fn [handler]
(fn
([request]
(try
(handler request)
(catch #?(:clj Exception :cljs js/Error) e
(handle-coercion-exception e identity #(throw %)))))
([request respond raise]
(try
(handler request respond #(handle-coercion-exception % respond raise))
(catch #?(:clj Exception :cljs js/Error) e
(handle-coercion-exception e respond raise))))))))}))

View file

@ -1,12 +1,12 @@
(ns reitit.ring.coercion.schema
(:require [schema.core :as s]
(ns reitit.coercion.schema
(:require [clojure.walk :as walk]
[schema.core :as s]
[schema-tools.core :as st]
[schema.coerce :as sc]
[schema.utils :as su]
[schema-tools.coerce :as stc]
[spec-tools.swagger.core :as swagger]
[clojure.walk :as walk]
[reitit.ring.coercion.protocol :as protocol]))
[reitit.coercion :as coercion]))
(def string-coercion-matcher
stc/string-coercion-matcher)
@ -35,24 +35,24 @@
(defrecord SchemaCoercion [name matchers coerce-response?]
protocol/Coercion
(get-name [_] name)
coercion/Coercion
(-get-name [_] name)
(get-apidocs [_ _ {:keys [parameters responses] :as info}]
(-get-apidocs [_ _ {:keys [parameters responses] :as info}]
(cond-> (dissoc info :parameters :responses)
parameters (assoc ::swagger/parameters parameters)
responses (assoc ::swagger/responses responses)))
(compile-model [_ model _] model)
(-compile-model [_ model _] model)
(open-model [_ schema] (st/open-schema schema))
(-open-model [_ schema] (st/open-schema schema))
(encode-error [_ error]
(-encode-error [_ error]
(-> error
(update :schema stringify)
(update :errors stringify)))
(request-coercer [_ type schema]
(-request-coercer [_ type schema]
(let [{:keys [formats default]} (matchers type)
coercers (->> (for [m (conj (vals formats) default)]
[m (sc/coercer schema m)])
@ -62,15 +62,15 @@
(let [coercer (coercers matcher)
coerced (coercer value)]
(if-let [error (su/error-val coerced)]
(protocol/map->CoercionError
(coercion/map->CoercionError
{:schema schema
:errors error})
coerced))
value))))
(response-coercer [this schema]
(-response-coercer [this schema]
(if (coerce-response? schema)
(protocol/request-coercer this :response schema))))
(coercion/-request-coercer this :response schema))))
(def default-options
{:coerce-response? coerce-response?

View file

@ -1,10 +1,10 @@
(ns reitit.ring.coercion.spec
(ns reitit.coercion.spec
(:require [clojure.spec.alpha :as s]
[spec-tools.core :as st #?@(:cljs [:refer [Spec]])]
[spec-tools.data-spec :as ds]
[spec-tools.conform :as conform]
[spec-tools.swagger.core :as swagger]
[reitit.ring.coercion.protocol :as protocol])
[reitit.coercion :as coercion])
#?(:clj
(:import (spec_tools.core Spec))))
@ -54,51 +54,51 @@
(defrecord SpecCoercion [name conforming coerce-response?]
protocol/Coercion
(get-name [_] name)
coercion/Coercion
(-get-name [_] name)
(get-apidocs [this _ {:keys [parameters responses] :as info}]
(-get-apidocs [this _ {:keys [parameters responses] :as info}]
(cond-> (dissoc info :parameters :responses)
parameters (assoc
::swagger/parameters
(into
(empty parameters)
(for [[k v] parameters]
[k (protocol/compile-model this v nil)])))
[k (coercion/-compile-model this v nil)])))
responses (assoc
::swagger/responses
(into
(empty responses)
(for [[k response] responses]
[k (update response :schema #(protocol/compile-model this % nil))])))))
[k (update response :schema #(coercion/-compile-model this % nil))])))))
(compile-model [_ model _]
(-compile-model [_ model _]
(into-spec model (or name (gensym "spec"))))
(open-model [_ spec] spec)
(-open-model [_ spec] spec)
(encode-error [_ error]
(-encode-error [_ error]
(-> error
(update :spec (comp str s/form))
(update :problems (partial mapv #(update % :pred stringify-pred)))))
(request-coercer [this type spec]
(let [spec (protocol/compile-model this spec nil)
(-request-coercer [this type spec]
(let [spec (coercion/-compile-model this spec nil)
{:keys [formats default]} (conforming type)]
(fn [value format]
(if-let [conforming (or (get formats format) default)]
(let [conformed (st/conform spec value conforming)]
(if (s/invalid? conformed)
(let [problems (st/explain-data spec value conforming)]
(protocol/map->CoercionError
(coercion/map->CoercionError
{:spec spec
:problems (::s/problems problems)}))
(s/unform spec conformed)))
value))))
(response-coercer [this spec]
(-response-coercer [this spec]
(if (coerce-response? spec)
(protocol/request-coercer this :response spec))))
(coercion/-request-coercer this :response spec))))
(def default-options
{:coerce-response? coerce-response?

View file

@ -4,18 +4,16 @@
[reitit.perf-utils :refer :all]
[clojure.spec.alpha :as s]
[spec-tools.core :as st]
[reitit.core :as reitit]
[reitit.ring :as ring]
[reitit.ring.coercion :as coercion]
[reitit.ring.coercion.spec :as spec]
[reitit.ring.coercion.schema :as schema]
[reitit.ring.coercion.protocol :as protocol]
[spec-tools.data-spec :as ds]
[muuntaja.middleware :as mm]
[muuntaja.core :as m]
[muuntaja.format.jsonista :as jsonista-format]
[jsonista.core :as j]
[reitit.coercion-middleware :as coercion-middleware]
[reitit.coercion.spec :as spec]
[reitit.coercion.schema :as schema]
[reitit.coercion :as coercion]
[reitit.ring :as ring]
[reitit.core :as r])
(:import (java.io ByteArrayInputStream)))
@ -41,14 +39,14 @@
(s/def ::k (s/keys :req-un [::x ::y]))
(let [spec (spec/into-spec {:x int?, :y int?} ::jeah)
coercers (#'coercion/request-coercers spec/coercion {:body spec})
coercers (#'coercion-middleware/request-coercers spec/coercion {:body spec})
params {:x "1", :y "2"}
request {:body-params {:x "1", :y "2"}}]
;; 4600ns
(bench!
"coerce-parameters"
(#'coercion/coerce-parameters coercers request))
(#'coercion-middleware/coerce-parameters coercers request))
;; 2700ns
(bench!
@ -85,14 +83,14 @@
params))))))
(defrecord NoOpCoercion []
protocol/Coercion
(get-name [_] :no-op)
(get-apidocs [_ _ {:keys [parameters responses] :as info}])
(compile-model [_ model _] model)
(open-model [_ spec] spec)
(encode-error [_ error] error)
(request-coercer [_ type spec] (fn [value format] value))
(response-coercer [this spec] (protocol/request-coercer this :response spec)))
coercion/Coercion
(-get-name [_] :no-op)
(-get-apidocs [_ _ {:keys [parameters responses] :as info}])
(-compile-model [_ model _] model)
(-open-model [_ spec] spec)
(-encode-error [_ error] error)
(-request-coercer [_ type spec] (fn [value format] value))
(-response-coercer [this spec] (protocol/request-coercer this :response spec)))
(comment
(doseq [coercion [nil (->NoOpCoercion) spec/coercion]]
@ -107,24 +105,24 @@
app (ring/ring-handler
(ring/router
routes
{:data {:middleware [coercion/coerce-request-middleware]
{:data {:middleware [coercion-middleware/coerce-request-middleware]
:coercion coercion}}))
app2 (ring/ring-handler
(ring/router
routes
{:data {:middleware [coercion/coerce-request-middleware]
{:data {:middleware [coercion-middleware/coerce-request-middleware]
:coercion coercion}}))
app3 (ring/ring-handler
(ring/router
routes
{:data {:middleware [coercion/coerce-request-middleware
coercion/wrap-coerce-response]
{:data {:middleware [coercion-middleware/coerce-request-middleware
coercion-middleware/wrap-coerce-response]
:coercion coercion}}))
app4 (ring/ring-handler
(ring/router
routes
{:data {:middleware [coercion/coerce-request-middleware
coercion/coerce-response-middleware]
{:data {:middleware [coercion-middleware/coerce-request-middleware
coercion-middleware/coerce-response-middleware]
:coercion coercion}}))
req {:request-method :get
:uri "/api/ping"
@ -161,8 +159,8 @@
:get {:handler (fn [{{{:keys [x y]} :body} :parameters}]
{:status 200
:body {:total (+ x y)}})}}]]
{:data {:middleware [coercion/coerce-request-middleware
coercion/coerce-response-middleware]
{:data {:middleware [coercion-middleware/coerce-request-middleware
coercion-middleware/coerce-response-middleware]
:coercion spec/coercion}})))
(app
@ -205,8 +203,8 @@
(let [body (-> request :parameters :body)]
{:status 200, :body {:result (+ (:x body) (:y body))}}))}}]
{:data {:middleware [[mm/wrap-format m]
coercion/coerce-request-middleware
coercion/coerce-response-middleware]
coercion-middleware/coerce-request-middleware
coercion-middleware/coerce-response-middleware]
:coercion schema/coercion}}))
request {:request-method :post
:uri "/plus"
@ -228,8 +226,8 @@
:handler (fn [request]
(let [body (-> request :parameters :body)]
{:status 200, :body {:result (+ (:x body) (:y body))}}))}}]
{:data {:middleware [coercion/coerce-request-middleware
coercion/coerce-response-middleware]
{:data {:middleware [coercion-middleware/coerce-request-middleware
coercion-middleware/coerce-response-middleware]
:coercion schema/coercion}}))
request {:request-method :post
:uri "/plus"
@ -251,8 +249,8 @@
:handler (fn [request]
(let [body (-> request :parameters :body)]
{:status 200, :body {:result (+ (:x body) (:y body))}}))}}]
{:data {:middleware [coercion/coerce-request-middleware
coercion/coerce-response-middleware]
{:data {:middleware [coercion-middleware/coerce-request-middleware
coercion-middleware/coerce-response-middleware]
:coercion spec/coercion}}))
request {:request-method :post
:uri "/plus"
@ -279,8 +277,8 @@
:handler (fn [request]
(let [body (-> request :parameters :body)]
{:status 200, :body {:result (+ (:x body) (:y body))}}))}}]
{:data {:middleware [coercion/coerce-request-middleware
coercion/coerce-response-middleware]
{:data {:middleware [coercion-middleware/coerce-request-middleware
coercion-middleware/coerce-response-middleware]
:coercion spec/coercion}}))
request {:request-method :post
:uri "/plus"

View file

@ -93,8 +93,8 @@
"httpRouter"
;; 77ns
;; 730ns
;; 960ns
;; 700ns
;; 890ns
(title "reitit-ring")
(let [r1 (map->Request {:request-method :get, :uri "/1/users"})
r2 (map->Request {:request-method :get, :uri "/1/classes/go"})

View file

@ -2,9 +2,9 @@
(:require [clojure.test :refer [deftest testing is]]
[schema.core :as s]
[reitit.ring :as ring]
[reitit.ring.coercion :as coercion]
[reitit.ring.coercion.spec :as spec]
[reitit.ring.coercion.schema :as schema])
[reitit.ring.coercion-middleware :as coercion-middleware]
[reitit.coercion.spec :as spec]
[reitit.coercion.schema :as schema])
#?(:clj
(:import (clojure.lang ExceptionInfo))))
@ -53,8 +53,8 @@
:coercion spec/coercion}})))]
(testing "withut exception handling"
(let [app (create [coercion/coerce-request-middleware
coercion/coerce-response-middleware])]
(let [app (create [coercion-middleware/coerce-request-middleware
coercion-middleware/coerce-response-middleware])]
(testing "all good"
(is (= {:status 200
@ -74,9 +74,9 @@
(app invalid-request2))))))
(testing "with exception handling"
(let [app (create [coercion/coerce-exceptions-middleware
coercion/coerce-request-middleware
coercion/coerce-response-middleware])]
(let [app (create [coercion-middleware/coerce-exceptions-middleware
coercion-middleware/coerce-request-middleware
coercion-middleware/coerce-response-middleware])]
(testing "all good"
(is (= {:status 200
@ -108,8 +108,8 @@
:coercion schema/coercion}})))]
(testing "withut exception handling"
(let [app (create [coercion/coerce-request-middleware
coercion/coerce-response-middleware])]
(let [app (create [coercion-middleware/coerce-request-middleware
coercion-middleware/coerce-response-middleware])]
(testing "all good"
(is (= {:status 200
@ -129,9 +129,9 @@
(app invalid-request2))))
(testing "with exception handling"
(let [app (create [coercion/coerce-exceptions-middleware
coercion/coerce-request-middleware
coercion/coerce-response-middleware])]
(let [app (create [coercion-middleware/coerce-exceptions-middleware
coercion-middleware/coerce-request-middleware
coercion-middleware/coerce-response-middleware])]
(testing "all good"
(is (= {:status 200

View file

@ -234,3 +234,19 @@
[["/a"] ["/a"]]))))
(testing "can be configured to ignore"
(is (not (nil? (r/router [["/a"] ["/a"]] {:conflicts (constantly nil)})))))))
(require '[reitit.coercion :as coercion])
(require '[reitit.coercion.spec :as spec])
(def r
(r/router
["/user/:user-id" {:name ::user
:parameters {:path {:user-id int?}}}]
{:compile coercion/compile-request-coercers
:data {:coercion spec/coercion}}))
(def m
(r/match-by-path r "/user/123"))
(let [m (r/match-by-path r "/user/123")]
(coercion/coerce-request (:result m) {:path-params (:params m)}))