mirror of
https://github.com/metosin/reitit.git
synced 2025-12-17 00:11:11 +00:00
Polish implementations
This commit is contained in:
parent
861c16b195
commit
4d772c62e1
5 changed files with 79 additions and 88 deletions
|
|
@ -15,6 +15,8 @@
|
||||||
|
|
||||||
(defrecord ParameterCoercion [in style keywordize? open?])
|
(defrecord ParameterCoercion [in style keywordize? open?])
|
||||||
|
|
||||||
|
(def valid-type? #{::request-coercion ::response-coercion})
|
||||||
|
|
||||||
(def ring-parameter-coercion
|
(def ring-parameter-coercion
|
||||||
{:query (->ParameterCoercion :query-params :string true true)
|
{:query (->ParameterCoercion :query-params :string true true)
|
||||||
:body (->ParameterCoercion :body-params :string false true)
|
:body (->ParameterCoercion :body-params :string false true)
|
||||||
|
|
@ -47,6 +49,8 @@
|
||||||
:request request
|
:request request
|
||||||
:response response}))))
|
:response response}))))
|
||||||
|
|
||||||
|
;; TODO: support faster key walking, walk/keywordize-keys is quite slow...
|
||||||
|
|
||||||
(defn request-coercer [coercion type model]
|
(defn request-coercer [coercion type model]
|
||||||
(if coercion
|
(if coercion
|
||||||
(let [{:keys [keywordize? open? in style]} (ring-parameter-coercion type)
|
(let [{:keys [keywordize? open? in style]} (ring-parameter-coercion type)
|
||||||
|
|
@ -81,7 +85,7 @@
|
||||||
;; middleware
|
;; middleware
|
||||||
;;
|
;;
|
||||||
|
|
||||||
(defn- coerce-parameters [coercers request]
|
(defn- coerce-request [coercers request]
|
||||||
(reduce-kv
|
(reduce-kv
|
||||||
(fn [acc k coercer]
|
(fn [acc k coercer]
|
||||||
(impl/fast-assoc acc k (coercer request)))
|
(impl/fast-assoc acc k (coercer request)))
|
||||||
|
|
@ -131,21 +135,21 @@
|
||||||
(handler (impl/fast-assoc request :parameters coerced) respond raise)))))))
|
(handler (impl/fast-assoc request :parameters coerced) respond raise)))))))
|
||||||
|
|
||||||
(def gen-wrap-coerce-parameters
|
(def gen-wrap-coerce-parameters
|
||||||
"Generator for pluggable request coercion middleware.
|
"Middleware for pluggable request coercion.
|
||||||
Expects a :coercion of type `reitit.coercion.protocol/Coercion`
|
Expects a :coercion of type `reitit.coercion.protocol/Coercion`
|
||||||
and :parameters from route data, otherwise does not mount."
|
and :parameters from route data, otherwise does not mount."
|
||||||
(middleware/create
|
(middleware/create
|
||||||
{:name ::coerce-parameters
|
{:name ::coerce-parameters
|
||||||
:gen-wrap (fn [{:keys [parameters coercion]} _]
|
:gen-wrap (fn [{:keys [coercion parameters]} _]
|
||||||
(if (and coercion parameters)
|
(if (and coercion parameters)
|
||||||
(let [coercers (request-coercers coercion parameters)]
|
(let [coercers (request-coercers coercion parameters)]
|
||||||
(fn [handler]
|
(fn [handler]
|
||||||
(fn
|
(fn
|
||||||
([request]
|
([request]
|
||||||
(let [coerced (coerce-parameters coercers request)]
|
(let [coerced (coerce-request coercers request)]
|
||||||
(handler (impl/fast-assoc request :parameters coerced))))
|
(handler (impl/fast-assoc request :parameters coerced))))
|
||||||
([request respond raise]
|
([request respond raise]
|
||||||
(let [coerced (coerce-parameters coercers request)]
|
(let [coerced (coerce-request coercers request)]
|
||||||
(handler (impl/fast-assoc request :parameters coerced) respond raise))))))))}))
|
(handler (impl/fast-assoc request :parameters coerced) respond raise))))))))}))
|
||||||
|
|
||||||
(defn wrap-coerce-response
|
(defn wrap-coerce-response
|
||||||
|
|
@ -177,12 +181,12 @@
|
||||||
(handler request respond raise))))))
|
(handler request respond raise))))))
|
||||||
|
|
||||||
(def gen-wrap-coerce-response
|
(def gen-wrap-coerce-response
|
||||||
"Generator for pluggable response coercion middleware.
|
"Middleware for pluggable response coercion.
|
||||||
Expects a :coercion of type `reitit.coercion.protocol/Coercion`
|
Expects a :coercion of type `reitit.coercion.protocol/Coercion`
|
||||||
and :responses from route data, otherwise does not mount."
|
and :responses from route data, otherwise does not mount."
|
||||||
(middleware/create
|
(middleware/create
|
||||||
{:name ::coerce-response
|
{:name ::coerce-response
|
||||||
:gen-wrap (fn [{:keys [responses coercion opts]} _]
|
:gen-wrap (fn [{:keys [coercion responses opts]} _]
|
||||||
(if (and coercion responses)
|
(if (and coercion responses)
|
||||||
(let [coercers (response-coercers coercion responses opts)]
|
(let [coercers (response-coercers coercion responses opts)]
|
||||||
(fn [handler]
|
(fn [handler]
|
||||||
|
|
|
||||||
|
|
@ -1,112 +1,90 @@
|
||||||
(ns reitit.ring.coercion.schema
|
(ns reitit.ring.coercion.schema
|
||||||
(:require [clojure.spec.alpha :as s]
|
(:require [schema.core :as s]
|
||||||
[spec-tools.core :as st #?@(:cljs [:refer [Spec]])]
|
[schema-tools.core :as st]
|
||||||
[spec-tools.data-spec :as ds]
|
[schema.coerce :as sc]
|
||||||
[spec-tools.conform :as conform]
|
[schema.utils :as su]
|
||||||
|
[schema-tools.coerce :as stc]
|
||||||
[spec-tools.swagger.core :as swagger]
|
[spec-tools.swagger.core :as swagger]
|
||||||
|
[clojure.walk :as walk]
|
||||||
[reitit.ring.coercion.protocol :as protocol])
|
[reitit.ring.coercion.protocol :as protocol])
|
||||||
#?(:clj
|
(:import (schema.core OptionalKey RequiredKey)
|
||||||
(:import (spec_tools.core Spec))))
|
(schema.utils ValidationError NamedError)))
|
||||||
|
|
||||||
(def string-conforming
|
(def string-coercion-matcher
|
||||||
(st/type-conforming
|
stc/string-coercion-matcher)
|
||||||
(merge
|
|
||||||
conform/string-type-conforming
|
|
||||||
conform/strip-extra-keys-type-conforming)))
|
|
||||||
|
|
||||||
(def json-conforming
|
(def json-coercion-matcher
|
||||||
(st/type-conforming
|
stc/json-coercion-matcher)
|
||||||
(merge
|
|
||||||
conform/json-type-conforming
|
|
||||||
conform/strip-extra-keys-type-conforming)))
|
|
||||||
|
|
||||||
(def default-conforming
|
(def default-coercion-matcher
|
||||||
::default)
|
(constantly nil))
|
||||||
|
|
||||||
(defprotocol IntoSpec
|
|
||||||
(into-spec [this name]))
|
|
||||||
|
|
||||||
(extend-protocol IntoSpec
|
|
||||||
|
|
||||||
#?(:clj clojure.lang.PersistentArrayMap
|
|
||||||
:cljs cljs.core.PersistentArrayMap)
|
|
||||||
(into-spec [this name]
|
|
||||||
(ds/spec name this))
|
|
||||||
|
|
||||||
#?(:clj clojure.lang.PersistentHashMap
|
|
||||||
:cljs cljs.core.PersistentHashMap)
|
|
||||||
(into-spec [this name]
|
|
||||||
(ds/spec name this))
|
|
||||||
|
|
||||||
Spec
|
|
||||||
(into-spec [this _] this)
|
|
||||||
|
|
||||||
#?(:clj Object
|
|
||||||
:cljs default)
|
|
||||||
(into-spec [this _]
|
|
||||||
(st/create-spec {:spec this})))
|
|
||||||
|
|
||||||
;; TODO: proper name!
|
|
||||||
(def memoized-into-spec
|
|
||||||
(memoize #(into-spec %1 (gensym "spec"))))
|
|
||||||
|
|
||||||
(defmulti coerce-response? identity :default ::default)
|
(defmulti coerce-response? identity :default ::default)
|
||||||
(defmethod coerce-response? ::default [_] true)
|
(defmethod coerce-response? ::default [_] true)
|
||||||
|
|
||||||
(defrecord SpecCoercion [name conforming coerce-response?]
|
(defn stringify [schema]
|
||||||
|
(walk/prewalk
|
||||||
|
(fn [x]
|
||||||
|
(cond
|
||||||
|
(class? x) (.getName ^Class x)
|
||||||
|
(instance? OptionalKey x) (pr-str (list 'opt (:k x)))
|
||||||
|
(instance? RequiredKey x) (pr-str (list 'req (:k x)))
|
||||||
|
(and (satisfies? s/Schema x) (record? x)) (try (pr-str (s/explain x)) (catch Exception _ x))
|
||||||
|
(instance? ValidationError x) (str (su/validation-error-explain x))
|
||||||
|
(instance? NamedError x) (str (su/named-error-explain x))
|
||||||
|
:else x))
|
||||||
|
schema))
|
||||||
|
|
||||||
|
(defrecord SchemaCoercion [name matchers coerce-response?]
|
||||||
|
|
||||||
protocol/Coercion
|
protocol/Coercion
|
||||||
(get-name [_] name)
|
(get-name [_] name)
|
||||||
|
|
||||||
(compile [_ model _]
|
(compile [_ model _]
|
||||||
(memoized-into-spec model))
|
model)
|
||||||
|
|
||||||
(get-apidocs [_ _ {:keys [parameters responses] :as info}]
|
(get-apidocs [_ _ {:keys [parameters responses] :as info}]
|
||||||
(cond-> (dissoc info :parameters :responses)
|
(cond-> (dissoc info :parameters :responses)
|
||||||
parameters (assoc
|
parameters (assoc
|
||||||
::swagger/parameters
|
::swagger/parameters
|
||||||
(into
|
parameters)
|
||||||
(empty parameters)
|
|
||||||
(for [[k v] parameters]
|
|
||||||
[k memoized-into-spec])))
|
|
||||||
responses (assoc
|
responses (assoc
|
||||||
::swagger/responses
|
::swagger/responses
|
||||||
(into
|
responses)))
|
||||||
(empty responses)
|
|
||||||
(for [[k response] responses]
|
|
||||||
[k (update response :schema memoized-into-spec)])))))
|
|
||||||
|
|
||||||
(make-open [_ spec] spec)
|
(make-open [_ schema] (st/open-schema schema))
|
||||||
|
|
||||||
(encode-error [_ error]
|
(encode-error [_ error]
|
||||||
(update error :spec (comp str s/form)))
|
(-> error
|
||||||
|
(update :schema stringify)
|
||||||
|
(update :errors stringify)))
|
||||||
|
|
||||||
(request-coercer [_ type spec]
|
;; TODO: create all possible coercers ahead of time
|
||||||
(let [spec (memoized-into-spec spec)
|
(request-coercer [_ type schema]
|
||||||
{:keys [formats default]} (conforming type)]
|
(let [{:keys [formats default]} (matchers type)]
|
||||||
(fn [value format]
|
(fn [value format]
|
||||||
(if-let [conforming (or (get formats format) default)]
|
(if-let [matcher (or (get formats format) default)]
|
||||||
(let [conformed (st/conform spec value conforming)]
|
(let [coercer (sc/coercer schema matcher)
|
||||||
(if (s/invalid? conformed)
|
coerced (coercer value)]
|
||||||
(let [problems (st/explain-data spec value conforming)]
|
(if-let [error (su/error-val coerced)]
|
||||||
(protocol/map->CoercionError
|
(protocol/map->CoercionError
|
||||||
{:spec spec
|
{:schema schema
|
||||||
:problems (::s/problems problems)}))
|
:errors error})
|
||||||
(s/unform spec conformed)))
|
coerced))
|
||||||
value))))
|
value))))
|
||||||
|
|
||||||
(response-coercer [this spec]
|
(response-coercer [this schema]
|
||||||
(if (coerce-response? spec)
|
(if (coerce-response? schema)
|
||||||
(protocol/request-coercer this :response spec))))
|
(protocol/request-coercer this :response schema))))
|
||||||
|
|
||||||
(def default-options
|
(def default-options
|
||||||
{:coerce-response? coerce-response?
|
{:coerce-response? coerce-response?
|
||||||
:conforming {:body {:default default-conforming
|
:matchers {:body {:default default-coercion-matcher
|
||||||
:formats {"application/json" json-conforming}}
|
:formats {"application/json" json-coercion-matcher}}
|
||||||
:string {:default string-conforming}
|
:string {:default string-coercion-matcher}
|
||||||
:response {:default default-conforming}}})
|
:response {:default default-coercion-matcher}}})
|
||||||
|
|
||||||
(defn create [{:keys [conforming coerce-response?]}]
|
(defn create [{:keys [matchers coerce-response?]}]
|
||||||
(->SpecCoercion :spec conforming coerce-response?))
|
(->SchemaCoercion :schema matchers coerce-response?))
|
||||||
|
|
||||||
(def coercion (create default-options))
|
(def coercion (create default-options))
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,11 @@
|
||||||
(def memoized-into-spec
|
(def memoized-into-spec
|
||||||
(memoize #(into-spec %1 (gensym "spec"))))
|
(memoize #(into-spec %1 (gensym "spec"))))
|
||||||
|
|
||||||
|
(defn stringify-pred [pred]
|
||||||
|
(str (if (instance? clojure.lang.LazySeq pred)
|
||||||
|
(seq pred)
|
||||||
|
pred)))
|
||||||
|
|
||||||
(defmulti coerce-response? identity :default ::default)
|
(defmulti coerce-response? identity :default ::default)
|
||||||
(defmethod coerce-response? ::default [_] true)
|
(defmethod coerce-response? ::default [_] true)
|
||||||
|
|
||||||
|
|
@ -79,7 +84,9 @@
|
||||||
(make-open [_ spec] spec)
|
(make-open [_ spec] spec)
|
||||||
|
|
||||||
(encode-error [_ error]
|
(encode-error [_ error]
|
||||||
(update error :spec (comp str s/form)))
|
(-> error
|
||||||
|
(update :spec (comp str s/form))
|
||||||
|
(update :problems (partial mapv #(update % :pred stringify-pred)))))
|
||||||
|
|
||||||
(request-coercer [_ type spec]
|
(request-coercer [_ type spec]
|
||||||
(let [spec (memoized-into-spec spec)
|
(let [spec (memoized-into-spec spec)
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,5 @@
|
||||||
:inherit [:deploy-repositories :managed-dependencies]}
|
:inherit [:deploy-repositories :managed-dependencies]}
|
||||||
:dependencies [[metosin/reitit-core]
|
:dependencies [[metosin/reitit-core]
|
||||||
[metosin/reitit-ring]
|
[metosin/reitit-ring]
|
||||||
[metosin/reitit-spec]])
|
[metosin/reitit-spec]
|
||||||
|
[metosin/reitit-schema]])
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
[meta-merge "1.0.0"]
|
[meta-merge "1.0.0"]
|
||||||
[metosin/spec-tools "0.5.1"]
|
[metosin/spec-tools "0.5.1"]
|
||||||
[metosin/schema-tools "0.9.1"]]
|
[metosin/schema-tools "0.10.0-SNAPSHOT"]]
|
||||||
|
|
||||||
:plugins [[jonase/eastwood "0.2.5"]
|
:plugins [[jonase/eastwood "0.2.5"]
|
||||||
[lein-doo "0.1.8"]
|
[lein-doo "0.1.8"]
|
||||||
|
|
@ -38,8 +38,9 @@
|
||||||
:dependencies [[org.clojure/clojure "1.9.0-RC1"]
|
:dependencies [[org.clojure/clojure "1.9.0-RC1"]
|
||||||
[org.clojure/clojurescript "1.9.946"]
|
[org.clojure/clojurescript "1.9.946"]
|
||||||
|
|
||||||
;; all modules dependencies
|
;; modules dependencies
|
||||||
[metosin/reitit]
|
[metosin/reitit]
|
||||||
|
[metosin/schema-tools "0.10.0-SNAPSHOT"]
|
||||||
|
|
||||||
[expound "0.3.2"]
|
[expound "0.3.2"]
|
||||||
[orchestra "2017.08.13"]
|
[orchestra "2017.08.13"]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue