From 2dc36360649a7c0e73923494bea0c88beeace57b Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Wed, 13 Dec 2017 18:00:50 +0200 Subject: [PATCH] Coercion is a reified Protocol, not a Record --- doc/coercion/clojure_spec_coercion.md | 2 +- doc/coercion/coercion.md | 7 +- doc/coercion/data_spec_coercion.md | 2 +- doc/coercion/schema_coercion.md | 2 +- modules/reitit-core/src/reitit/coercion.cljc | 11 ++- .../src/reitit/coercion/schema.cljc | 75 +++++++-------- .../reitit-spec/src/reitit/coercion/spec.cljc | 92 +++++++++---------- 7 files changed, 90 insertions(+), 101 deletions(-) diff --git a/doc/coercion/clojure_spec_coercion.md b/doc/coercion/clojure_spec_coercion.md index eef0aa49..61525965 100644 --- a/doc/coercion/clojure_spec_coercion.md +++ b/doc/coercion/clojure_spec_coercion.md @@ -34,7 +34,7 @@ Successful coercion: (match-by-path-and-coerce! "/metosin/users/123") ; #Match{:template "/:company/users/:user-id", ; :data {:name :user/user-view, -; :coercion #SpecCoercion{...} +; :coercion <<:spec>> ; :parameters {:path ::path-params}}, ; :result {:path #object[reitit.coercion$request_coercer$]}, ; :params {:company "metosin", :user-id "123"}, diff --git a/doc/coercion/coercion.md b/doc/coercion/coercion.md index af79729b..17390e19 100644 --- a/doc/coercion/coercion.md +++ b/doc/coercion/coercion.md @@ -65,7 +65,7 @@ A Match: (r/match-by-path r "/metosin/users/123") ; #Match{:template "/:company/users/:user-id", ; :data {:name :user/user-view, -; :coercion #SchemaCoercion{...} +; :coercion <<:schema>> ; :parameters {:path {:company java.lang.String, ; :user-id Int}}}, ; :result nil, @@ -105,7 +105,7 @@ Routing again: (r/match-by-path r "/metosin/users/123") ; #Match{:template "/:company/users/:user-id", ; :data {:name :user/user-view, -; :coercion #SchemaCoercion{...} +; :coercion <<:schema>> ; :parameters {:path {:company java.lang.String, ; :user-id Int}}}, ; :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 (require '[reitit.coercion.schema]) (require '[reitit.coercion :as coercion]) +(require '[reitit.core :as r]) (require '[schema.core :as s]) (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{:template "/:company/users/:user-id", ; :data {:name :user/user-view, -; :coercion #SchemaCoercion{...} +; :coercion <<:schema>> ; :parameters {:path {:company java.lang.String, ; :user-id Int}}}, ; :result {:path #object[reitit.coercion$request_coercer$]}, diff --git a/doc/coercion/data_spec_coercion.md b/doc/coercion/data_spec_coercion.md index b3da9bc0..c34bffcc 100644 --- a/doc/coercion/data_spec_coercion.md +++ b/doc/coercion/data_spec_coercion.md @@ -26,7 +26,7 @@ Successful coercion: (match-by-path-and-coerce! "/metosin/users/123") ; #Match{:template "/:company/users/:user-id", ; :data {:name :user/user-view, -; :coercion #SpecCoercion{...} +; :coercion <<:spec>> ; :parameters {:path {:company string?, ; :user-id int?}}}, ; :result {:path #object[reitit.coercion$request_coercer$]}, diff --git a/doc/coercion/schema_coercion.md b/doc/coercion/schema_coercion.md index 9ea5ee9a..9108a5a0 100644 --- a/doc/coercion/schema_coercion.md +++ b/doc/coercion/schema_coercion.md @@ -27,7 +27,7 @@ Successful coercion: (match-by-path-and-coerce! "/metosin/users/123") ; #Match{:template "/:company/users/:user-id", ; :data {:name :user/user-view, -; :coercion #SchemaCoercion{...} +; :coercion <<:schema>> ; :parameters {:path {:company java.lang.String, ; :user-id Int}}}, ; :result {:path #object[reitit.coercion$request_coercer$]}, diff --git a/modules/reitit-core/src/reitit/coercion.cljc b/modules/reitit-core/src/reitit/coercion.cljc index 3870b91e..c16b0c69 100644 --- a/modules/reitit-core/src/reitit/coercion.cljc +++ b/modules/reitit-core/src/reitit/coercion.cljc @@ -1,8 +1,8 @@ (ns reitit.coercion (:require [clojure.walk :as walk] - [spec-tools.core :as st] - [reitit.ring :as ring] - [reitit.impl :as impl])) + [reitit.impl :as impl]) + #?(:clj + (:import (java.io Writer)))) ;; ;; Protocol @@ -11,6 +11,7 @@ (defprotocol Coercion "Pluggable coercion protocol" (-get-name [this] "Keyword name for the coercion") + (-get-options [this] "Coercion options") (-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") @@ -18,6 +19,10 @@ (-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")) +#?(:clj + (defmethod print-method ::coercion [coercion ^Writer w] + (.write w (str "<<" (-get-name coercion) ">>")))) + (defrecord CoercionError []) (defn error? [x] diff --git a/modules/reitit-schema/src/reitit/coercion/schema.cljc b/modules/reitit-schema/src/reitit/coercion/schema.cljc index fa130571..741f08a7 100644 --- a/modules/reitit-schema/src/reitit/coercion/schema.cljc +++ b/modules/reitit-schema/src/reitit/coercion/schema.cljc @@ -5,7 +5,6 @@ [schema.coerce :as sc] [schema.utils :as su] [schema-tools.coerce :as stc] - [spec-tools.swagger.core :as swagger] [reitit.coercion :as coercion])) (def string-coercion-matcher @@ -33,45 +32,6 @@ :else x)) schema)) -(defrecord SchemaCoercion [name matchers coerce-response?] - - coercion/Coercion - (-get-name [_] name) - - (-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) - - (-open-model [_ schema] (st/open-schema schema)) - - (-encode-error [_ error] - (-> error - (update :schema stringify) - (update :errors stringify))) - - (-request-coercer [_ type schema] - (let [{:keys [formats default]} (matchers type) - coercers (->> (for [m (conj (vals formats) default)] - [m (sc/coercer schema m)]) - (into {}))] - (fn [value format] - (if-let [matcher (or (get formats format) default)] - (let [coercer (coercers matcher) - coerced (coercer value)] - (if-let [error (su/error-val coerced)] - (coercion/map->CoercionError - {:schema schema - :errors error}) - coerced)) - value)))) - - (-response-coercer [this schema] - (if (coerce-response? schema) - (coercion/-request-coercer this :response schema)))) - (def default-options {:coerce-response? coerce-response? :matchers {:body {:default default-coercion-matcher @@ -79,7 +39,38 @@ :string {:default string-coercion-matcher} :response {:default default-coercion-matcher}}}) -(defn create [{:keys [matchers coerce-response?]}] - (->SchemaCoercion :schema matchers coerce-response?)) +(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}] + (cond-> (dissoc info :parameters :responses) + parameters (assoc ::parameters parameters) + responses (assoc ::responses responses))) + (-compile-model [_ model _] model) + (-open-model [_ schema] (st/open-schema schema)) + (-encode-error [_ error] + (-> error + (update :schema stringify) + (update :errors stringify))) + (-request-coercer [_ type schema] + (let [{:keys [formats default]} (matchers type) + coercers (->> (for [m (conj (vals formats) default)] + [m (sc/coercer schema m)]) + (into {}))] + (fn [value format] + (if-let [matcher (or (get formats format) default)] + (let [coercer (coercers matcher) + coerced (coercer value)] + (if-let [error (su/error-val coerced)] + (coercion/map->CoercionError + {:schema schema + :errors error}) + coerced)) + value)))) + (-response-coercer [this schema] + (if (coerce-response? schema) + (coercion/-request-coercer this :response schema))))) (def coercion (create default-options)) diff --git a/modules/reitit-spec/src/reitit/coercion/spec.cljc b/modules/reitit-spec/src/reitit/coercion/spec.cljc index f316dd85..e81a24fa 100644 --- a/modules/reitit-spec/src/reitit/coercion/spec.cljc +++ b/modules/reitit-spec/src/reitit/coercion/spec.cljc @@ -52,54 +52,6 @@ (defmulti coerce-response? identity :default ::default) (defmethod coerce-response? ::default [_] true) -(defrecord SpecCoercion [name conforming coerce-response?] - - coercion/Coercion - (-get-name [_] name) - - (-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 (coercion/-compile-model this v nil)]))) - responses (assoc - ::swagger/responses - (into - (empty responses) - (for [[k response] responses] - [k (update response :schema #(coercion/-compile-model this % nil))]))))) - - (-compile-model [_ model _] - (into-spec model (or name (gensym "spec")))) - - (-open-model [_ spec] spec) - - (-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 (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)] - (coercion/map->CoercionError - {:spec spec - :problems (::s/problems problems)})) - (s/unform spec conformed))) - value)))) - - (-response-coercer [this spec] - (if (coerce-response? spec) - (coercion/-request-coercer this :response spec)))) - (def default-options {:coerce-response? coerce-response? :conforming {:body {:default default-conforming @@ -107,7 +59,47 @@ :string {:default string-conforming} :response {:default default-conforming}}}) -(defn create [{:keys [conforming coerce-response?]}] - (->SpecCoercion :spec conforming coerce-response?)) +(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}] + (cond-> (dissoc info :parameters :responses) + parameters (assoc + ::swagger/parameters + (into + (empty parameters) + (for [[k v] parameters] + [k (coercion/-compile-model this v nil)]))) + responses (assoc + ::swagger/responses + (into + (empty responses) + (for [[k response] responses] + [k (update response :schema #(coercion/-compile-model this % nil))]))))) + (-compile-model [_ model name] + (into-spec model (or name (gensym "spec")))) + (-open-model [_ spec] spec) + (-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 (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)] + (coercion/map->CoercionError + {:spec spec + :problems (::s/problems problems)})) + (s/unform spec conformed))) + value)))) + (-response-coercer [this spec] + (if (coerce-response? spec) + (coercion/-request-coercer this :response spec))))) (def coercion (create default-options))