Coercion is a reified Protocol, not a Record

This commit is contained in:
Tommi Reiman 2017-12-13 18:00:50 +02:00
parent cc8bd542dd
commit 2dc3636064
7 changed files with 90 additions and 101 deletions

View file

@ -34,7 +34,7 @@ Successful coercion:
(match-by-path-and-coerce! "/metosin/users/123") (match-by-path-and-coerce! "/metosin/users/123")
; #Match{:template "/:company/users/:user-id", ; #Match{:template "/:company/users/:user-id",
; :data {:name :user/user-view, ; :data {:name :user/user-view,
; :coercion #SpecCoercion{...} ; :coercion <<:spec>>
; :parameters {:path ::path-params}}, ; :parameters {:path ::path-params}},
; :result {:path #object[reitit.coercion$request_coercer$]}, ; :result {:path #object[reitit.coercion$request_coercer$]},
; :params {:company "metosin", :user-id "123"}, ; :params {:company "metosin", :user-id "123"},

View file

@ -65,7 +65,7 @@ A Match:
(r/match-by-path r "/metosin/users/123") (r/match-by-path r "/metosin/users/123")
; #Match{:template "/:company/users/:user-id", ; #Match{:template "/:company/users/:user-id",
; :data {:name :user/user-view, ; :data {:name :user/user-view,
; :coercion #SchemaCoercion{...} ; :coercion <<:schema>>
; :parameters {:path {:company java.lang.String, ; :parameters {:path {:company java.lang.String,
; :user-id Int}}}, ; :user-id Int}}},
; :result nil, ; :result nil,
@ -105,7 +105,7 @@ Routing again:
(r/match-by-path r "/metosin/users/123") (r/match-by-path r "/metosin/users/123")
; #Match{:template "/:company/users/:user-id", ; #Match{:template "/:company/users/:user-id",
; :data {:name :user/user-view, ; :data {:name :user/user-view,
; :coercion #SchemaCoercion{...} ; :coercion <<:schema>>
; :parameters {:path {:company java.lang.String, ; :parameters {:path {:company java.lang.String,
; :user-id Int}}}, ; :user-id Int}}},
; :result {:path #object[reitit.coercion$request_coercer$]}, ; :result {:path #object[reitit.coercion$request_coercer$]},
@ -143,6 +143,7 @@ Here's an full example for doing both routing and coercion with Reitit:
```clj ```clj
(require '[reitit.coercion.schema]) (require '[reitit.coercion.schema])
(require '[reitit.coercion :as coercion]) (require '[reitit.coercion :as coercion])
(require '[reitit.core :as r])
(require '[schema.core :as s]) (require '[schema.core :as s])
(def router (def router
@ -160,7 +161,7 @@ Here's an full example for doing both routing and coercion with Reitit:
(match-by-path-and-coerce! "/metosin/users/123") (match-by-path-and-coerce! "/metosin/users/123")
; #Match{:template "/:company/users/:user-id", ; #Match{:template "/:company/users/:user-id",
; :data {:name :user/user-view, ; :data {:name :user/user-view,
; :coercion #SchemaCoercion{...} ; :coercion <<:schema>>
; :parameters {:path {:company java.lang.String, ; :parameters {:path {:company java.lang.String,
; :user-id Int}}}, ; :user-id Int}}},
; :result {:path #object[reitit.coercion$request_coercer$]}, ; :result {:path #object[reitit.coercion$request_coercer$]},

View file

@ -26,7 +26,7 @@ Successful coercion:
(match-by-path-and-coerce! "/metosin/users/123") (match-by-path-and-coerce! "/metosin/users/123")
; #Match{:template "/:company/users/:user-id", ; #Match{:template "/:company/users/:user-id",
; :data {:name :user/user-view, ; :data {:name :user/user-view,
; :coercion #SpecCoercion{...} ; :coercion <<:spec>>
; :parameters {:path {:company string?, ; :parameters {:path {:company string?,
; :user-id int?}}}, ; :user-id int?}}},
; :result {:path #object[reitit.coercion$request_coercer$]}, ; :result {:path #object[reitit.coercion$request_coercer$]},

View file

@ -27,7 +27,7 @@ Successful coercion:
(match-by-path-and-coerce! "/metosin/users/123") (match-by-path-and-coerce! "/metosin/users/123")
; #Match{:template "/:company/users/:user-id", ; #Match{:template "/:company/users/:user-id",
; :data {:name :user/user-view, ; :data {:name :user/user-view,
; :coercion #SchemaCoercion{...} ; :coercion <<:schema>>
; :parameters {:path {:company java.lang.String, ; :parameters {:path {:company java.lang.String,
; :user-id Int}}}, ; :user-id Int}}},
; :result {:path #object[reitit.coercion$request_coercer$]}, ; :result {:path #object[reitit.coercion$request_coercer$]},

View file

@ -1,8 +1,8 @@
(ns reitit.coercion (ns reitit.coercion
(:require [clojure.walk :as walk] (:require [clojure.walk :as walk]
[spec-tools.core :as st] [reitit.impl :as impl])
[reitit.ring :as ring] #?(:clj
[reitit.impl :as impl])) (:import (java.io Writer))))
;; ;;
;; Protocol ;; Protocol
@ -11,6 +11,7 @@
(defprotocol Coercion (defprotocol Coercion
"Pluggable coercion protocol" "Pluggable coercion protocol"
(-get-name [this] "Keyword name for the coercion") (-get-name [this] "Keyword name for the coercion")
(-get-options [this] "Coercion options")
(-get-apidocs [this model data] "???") (-get-apidocs [this model data] "???")
(-compile-model [this model name] "Compiles a model") (-compile-model [this model name] "Compiles a model")
(-open-model [this model] "Returns a new model which allows extra keys in maps") (-open-model [this model] "Returns a new model which allows extra keys in maps")
@ -18,6 +19,10 @@
(-request-coercer [this type model] "Returns a `value format => value` request coercion function") (-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")) (-response-coercer [this model] "Returns a `value format => value` response coercion function"))
#?(:clj
(defmethod print-method ::coercion [coercion ^Writer w]
(.write w (str "<<" (-get-name coercion) ">>"))))
(defrecord CoercionError []) (defrecord CoercionError [])
(defn error? [x] (defn error? [x]

View file

@ -5,7 +5,6 @@
[schema.coerce :as sc] [schema.coerce :as sc]
[schema.utils :as su] [schema.utils :as su]
[schema-tools.coerce :as stc] [schema-tools.coerce :as stc]
[spec-tools.swagger.core :as swagger]
[reitit.coercion :as coercion])) [reitit.coercion :as coercion]))
(def string-coercion-matcher (def string-coercion-matcher
@ -33,25 +32,28 @@
:else x)) :else x))
schema)) schema))
(defrecord SchemaCoercion [name matchers coerce-response?] (def default-options
{:coerce-response? coerce-response?
coercion/Coercion :matchers {:body {:default default-coercion-matcher
(-get-name [_] name) :formats {"application/json" json-coercion-matcher}}
:string {:default string-coercion-matcher}
:response {:default default-coercion-matcher}}})
(defn create [{:keys [matchers coerce-response?] :as opts}]
^{:type ::coercion/coercion}
(reify coercion/Coercion
(-get-name [_] :schema)
(-get-options [_] opts)
(-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 ::swagger/parameters parameters) parameters (assoc ::parameters parameters)
responses (assoc ::swagger/responses responses))) responses (assoc ::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 (-> error
(update :schema stringify) (update :schema stringify)
(update :errors stringify))) (update :errors stringify)))
(-request-coercer [_ type schema] (-request-coercer [_ type schema]
(let [{:keys [formats default]} (matchers type) (let [{:keys [formats default]} (matchers type)
coercers (->> (for [m (conj (vals formats) default)] coercers (->> (for [m (conj (vals formats) default)]
@ -67,19 +69,8 @@
:errors error}) :errors error})
coerced)) coerced))
value)))) value))))
(-response-coercer [this schema] (-response-coercer [this schema]
(if (coerce-response? schema) (if (coerce-response? schema)
(coercion/-request-coercer this :response schema)))) (coercion/-request-coercer this :response schema)))))
(def default-options
{:coerce-response? coerce-response?
:matchers {:body {:default default-coercion-matcher
:formats {"application/json" json-coercion-matcher}}
:string {:default string-coercion-matcher}
:response {:default default-coercion-matcher}}})
(defn create [{:keys [matchers coerce-response?]}]
(->SchemaCoercion :schema matchers coerce-response?))
(def coercion (create default-options)) (def coercion (create default-options))

View file

@ -52,11 +52,18 @@
(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?] (def default-options
{:coerce-response? coerce-response?
coercion/Coercion :conforming {:body {:default default-conforming
(-get-name [_] name) :formats {"application/json" json-conforming}}
:string {:default string-conforming}
:response {:default default-conforming}}})
(defn create [{:keys [conforming coerce-response?] :as opts}]
^{:type ::coercion/coercion}
(reify coercion/Coercion
(-get-name [_] :spec)
(-get-options [_] opts)
(-get-apidocs [this _ {:keys [parameters responses] :as info}] (-get-apidocs [this _ {:keys [parameters responses] :as info}]
(cond-> (dissoc info :parameters :responses) (cond-> (dissoc info :parameters :responses)
parameters (assoc parameters (assoc
@ -71,17 +78,13 @@
(empty responses) (empty responses)
(for [[k response] responses] (for [[k response] responses]
[k (update response :schema #(coercion/-compile-model this % nil))]))))) [k (update response :schema #(coercion/-compile-model this % nil))])))))
(-compile-model [_ model name]
(-compile-model [_ model _]
(into-spec model (or name (gensym "spec")))) (into-spec model (or name (gensym "spec"))))
(-open-model [_ spec] spec) (-open-model [_ spec] spec)
(-encode-error [_ error] (-encode-error [_ error]
(-> error (-> error
(update :spec (comp str s/form)) (update :spec (comp str s/form))
(update :problems (partial mapv #(update % :pred stringify-pred))))) (update :problems (partial mapv #(update % :pred stringify-pred)))))
(-request-coercer [this type spec] (-request-coercer [this type spec]
(let [spec (coercion/-compile-model this spec nil) (let [spec (coercion/-compile-model this spec nil)
{:keys [formats default]} (conforming type)] {:keys [formats default]} (conforming type)]
@ -95,19 +98,8 @@
:problems (::s/problems problems)})) :problems (::s/problems problems)}))
(s/unform spec conformed))) (s/unform spec conformed)))
value)))) value))))
(-response-coercer [this spec] (-response-coercer [this spec]
(if (coerce-response? spec) (if (coerce-response? spec)
(coercion/-request-coercer this :response spec)))) (coercion/-request-coercer this :response spec)))))
(def default-options
{:coerce-response? coerce-response?
:conforming {:body {:default default-conforming
:formats {"application/json" json-conforming}}
:string {:default string-conforming}
:response {:default default-conforming}}})
(defn create [{:keys [conforming coerce-response?]}]
(->SpecCoercion :spec conforming coerce-response?))
(def coercion (create default-options)) (def coercion (create default-options))