From 499f84be214ccd472801ead9966d8a885df24034 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Sat, 27 May 2023 18:08:39 +0300 Subject: [PATCH 01/12] fix warning --- project.clj | 1 + 1 file changed, 1 insertion(+) diff --git a/project.clj b/project.clj index fc210596..7b7a7a74 100644 --- a/project.clj +++ b/project.clj @@ -105,6 +105,7 @@ [ikitommi/immutant-web "3.0.0-alpha1"] [metosin/ring-http-response "0.9.3"] [metosin/ring-swagger-ui "4.18.1"] + [org.clojure/tools.analyzer "1.1.1"] [criterium "0.4.6"] [org.clojure/test.check "1.1.1"] From d17c97780e2bdc0695dfa0242657573a2c80d9d9 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Sat, 27 May 2023 19:01:17 +0300 Subject: [PATCH 02/12] wrap :content schemas in :schema --- modules/reitit-core/src/reitit/coercion.cljc | 8 +- modules/reitit-core/src/reitit/spec.cljc | 5 +- .../src/reitit/coercion/malli.cljc | 12 +-- modules/reitit-ring/src/reitit/ring.cljc | 8 +- .../src/reitit/coercion/schema.cljc | 67 +++++++------ .../reitit-spec/src/reitit/coercion/spec.cljc | 9 +- test/cljc/reitit/openapi_test.clj | 99 ++++++++++--------- test/cljc/reitit/ring_coercion_test.cljc | 10 +- 8 files changed, 114 insertions(+), 104 deletions(-) diff --git a/modules/reitit-core/src/reitit/coercion.cljc b/modules/reitit-core/src/reitit/coercion.cljc index 0952bfab..19106ac0 100644 --- a/modules/reitit-core/src/reitit/coercion.cljc +++ b/modules/reitit-core/src/reitit/coercion.cljc @@ -91,9 +91,9 @@ (let [transform (comp (if keywordize? walk/keywordize-keys identity) in) ->open (if open? #(-open-model coercion %) identity) format-schema-pairs (if (= :request style) - (conj (:content model) [:default (:body model)]) - [[:default model]]) - format->coercer (some->> (for [[format schema] format-schema-pairs + (conj (:content model) [:default {:schema (:body model)}]) + [[:default {:schema model}]]) + format->coercer (some->> (for [[format {:keys [schema]}] format-schema-pairs :when schema :let [type (case style :request :body style)]] [format (-request-coercer coercion type (->open schema))]) @@ -118,7 +118,7 @@ (defn response-coercer [coercion {:keys [content body]} {:keys [extract-response-format serialize-failed-result] :or {extract-response-format extract-response-format-default}}] (if coercion - (let [per-format-coercers (some->> (for [[format schema] content + (let [per-format-coercers (some->> (for [[format {:keys [schema]}] content :when schema] [format (-response-coercer coercion schema)]) (filter second) diff --git a/modules/reitit-core/src/reitit/spec.cljc b/modules/reitit-core/src/reitit/spec.cljc index d5ddaf84..3d2a7a36 100644 --- a/modules/reitit-core/src/reitit/spec.cljc +++ b/modules/reitit-core/src/reitit/spec.cljc @@ -82,8 +82,11 @@ (s/def :reitit.core.coercion/model any?) +(s/def :reitit.core.coercion/schema any?) +(s/def :reitit.core.coercion/map-model (s/keys :opt-un [:reitit.core.coercion/schema])) + (s/def :reitit.core.coercion/content - (s/map-of string? :reitit.core.coercion/model)) + (s/map-of string? :reitit.core.coercion/map-model)) (s/def :reitit.core.coercion/query :reitit.core.coercion/model) (s/def :reitit.core.coercion/body :reitit.core.coercion/model) diff --git a/modules/reitit-malli/src/reitit/coercion/malli.cljc b/modules/reitit-malli/src/reitit/coercion/malli.cljc index 6f4dff5b..36daefa4 100644 --- a/modules/reitit-malli/src/reitit/coercion/malli.cljc +++ b/modules/reitit-malli/src/reitit/coercion/malli.cljc @@ -134,7 +134,7 @@ :options nil}) (defn -get-apidocs-openapi - [coercion {:keys [parameters responses content-types] :or {content-types ["application/json"]}} options] + [_ {:keys [parameters responses content-types] :or {content-types ["application/json"]}} options] (let [{:keys [body request multipart]} parameters parameters (dissoc parameters :request :body :multipart) ->schema-object (fn [schema opts] @@ -177,10 +177,10 @@ [content-type {:schema schema}]))) content-types)) (into {} - (map (fn [[content-type requestBody]] - (let [schema (->schema-object requestBody {:in :requestBody - :type :schema - :content-type content-type})] + (map (fn [[content-type {:keys [schema]}]] + (let [schema (->schema-object schema {:in :requestBody + :type :schema + :content-type content-type})] [content-type {:schema schema}]))) (:content request)))}}) (when multipart @@ -207,7 +207,7 @@ content-types)) (when content (into {} - (map (fn [[content-type schema]] + (map (fn [[content-type {:keys [schema]}]] (let [schema (->schema-object schema {:in :responses :type :schema :content-type content-type})] diff --git a/modules/reitit-ring/src/reitit/ring.cljc b/modules/reitit-ring/src/reitit/ring.cljc index 7a4cde7f..eee8452c 100644 --- a/modules/reitit-ring/src/reitit/ring.cljc +++ b/modules/reitit-ring/src/reitit/ring.cljc @@ -39,12 +39,12 @@ [[http-method? :responses any? :body] f] ;; openapi3 parameters and responses - [[:parameters :request :content any?] f] - [[http-method? :parameters :request :content any?] f] + [[:parameters :request :content any? :schema] f] + [[http-method? :parameters :request :content any? :schema] f] [[:parameters :request :body] f] [[http-method? :parameters :request :body] f] - [[:responses any? :content any?] f] - [[http-method? :responses any? :content any?] f]])) + [[:responses any? :content any? :schema] f] + [[http-method? :responses any? :content any? :schema] f]])) (defn -compile-coercion [{:keys [coercion] :as data}] (cond-> data coercion (impl/path-update (-update-paths #(coercion/-compile-model coercion % nil))))) diff --git a/modules/reitit-schema/src/reitit/coercion/schema.cljc b/modules/reitit-schema/src/reitit/coercion/schema.cljc index 3dc50bb1..b746c516 100644 --- a/modules/reitit-schema/src/reitit/coercion/schema.cljc +++ b/modules/reitit-schema/src/reitit/coercion/schema.cljc @@ -47,9 +47,9 @@ (reify coercion/Coercion (-get-name [_] :schema) (-get-options [_] opts) - (-get-apidocs [this specification {:keys [parameters responses content-types] - :or {content-types ["application/json"]}}] - ;; TODO: this looks identical to spec, refactor when schema is done. + (-get-apidocs [_ specification {:keys [parameters responses content-types] + :or {content-types ["application/json"]}}] + ;; TODO: this looks identical to spec, refactor when schema is done. (case specification :swagger (swagger/swagger-spec (merge @@ -62,35 +62,38 @@ (for [[k response] responses] [k (set/rename-keys response {:body :schema})]))}))) :openapi (merge - (when (seq (dissoc parameters :body :request :multipart)) - (openapi/openapi-spec {::openapi/parameters (dissoc parameters :body :request)})) - (when (:body parameters) - {:requestBody (openapi/openapi-spec - {::openapi/content (zipmap content-types (repeat (:body parameters)))})}) - (when (:request parameters) - {:requestBody (openapi/openapi-spec - {::openapi/content (merge - (when-let [default (get-in parameters [:request :body])] - (zipmap content-types (repeat default))) - (:content (:request parameters)))})}) - (when (:multipart parameters) - {:requestBody - (openapi/openapi-spec - {::openapi/content {"multipart/form-data" (:multipart parameters)}})}) - (when responses - {:responses - (into - (empty responses) - (for [[k {:keys [body content] :as response}] responses] - [k (merge - (select-keys response [:description]) - (when (or body content) - (openapi/openapi-spec - {::openapi/content (merge - (when body - (zipmap content-types (repeat body))) - (when response - (:content response)))})))]))})) + (when (seq (dissoc parameters :body :request :multipart)) + (openapi/openapi-spec {::openapi/parameters (dissoc parameters :body :request)})) + (when (:body parameters) + {:requestBody (openapi/openapi-spec + {::openapi/content (zipmap content-types (repeat (:body parameters)))})}) + (when (:request parameters) + {:requestBody (openapi/openapi-spec + {::openapi/content (merge + (when-let [default (get-in parameters [:request :body])] + (zipmap content-types (repeat default))) + (->> (for [[content-type {:keys [schema]}] (:content (:request parameters))] + [content-type schema]) + (into {})))})}) + (when (:multipart parameters) + {:requestBody + (openapi/openapi-spec + {::openapi/content {"multipart/form-data" (:multipart parameters)}})}) + (when responses + {:responses + (into + (empty responses) + (for [[k {:keys [body content] :as response}] responses] + [k (merge + (select-keys response [:description]) + (when (or body content) + (openapi/openapi-spec + {::openapi/content (merge + (when body + (zipmap content-types (repeat body))) + (->> (for [[content-type {:keys [schema]}] (:content response)] + [content-type schema]) + (into {})))})))]))})) (throw (ex-info diff --git a/modules/reitit-spec/src/reitit/coercion/spec.cljc b/modules/reitit-spec/src/reitit/coercion/spec.cljc index 40110843..848fc3a4 100644 --- a/modules/reitit-spec/src/reitit/coercion/spec.cljc +++ b/modules/reitit-spec/src/reitit/coercion/spec.cljc @@ -113,7 +113,9 @@ {::openapi/content (merge (when-let [default (get-in parameters [:request :body])] (zipmap content-types (repeat default))) - (:content (:request parameters)))})}) + (->> (for [[content-type {:keys [schema]}] (:content (:request parameters))] + [content-type schema]) + (into {})))})}) (when (:multipart parameters) {:requestBody (openapi/openapi-spec @@ -130,8 +132,9 @@ {::openapi/content (merge (when body (zipmap content-types (repeat (:body response)))) - (when response - (:content response)))})))]))})) + (->> (for [[content-type {:keys [schema]}] (:content response)] + [content-type schema]) + (into {})))})))]))})) (throw (ex-info (str "Can't produce Spec apidocs for " specification) diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index 605f5e04..b8bc6f32 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -101,7 +101,7 @@ {:get {:summary "plus" :tags [:plus :schema] :parameters {:query {:x s/Int, :y s/Int} - :path {:z s/Int}} + :path {:z s/Int}} :openapi {:responses {400 {:content {"application/json" {:schema {:type "string"}}} :description "kosh"}}} :responses {200 {:description "success" @@ -566,20 +566,20 @@ (deftest per-content-type-test (doseq [[coercion ->schema] - [[#'malli/coercion (fn [nom] [:map [nom :string]])] - [#'schema/coercion (fn [nom] {nom s/Str})] - [#'spec/coercion (fn [nom] {nom string?})]]] + [[malli/coercion (fn [nom] [:map [nom :string]])] + [schema/coercion (fn [nom] {nom s/Str})] + [spec/coercion (fn [nom] {nom string?})]]] (testing (str coercion) (let [app (ring/ring-handler (ring/router [["/parameters" {:post {:description "parameters" - :coercion @coercion - :parameters {:request {:content {"application/json" (->schema :b) - "application/edn" (->schema :c)}}} + :coercion coercion + :parameters {:request {:content {"application/json" {:schema (->schema :b)} + "application/edn" {:schema (->schema :c)}}}} :responses {200 {:description "success" - :content {"application/json" (->schema :ok) - "application/edn" (->schema :edn)}}} + :content {"application/json" {:schema (->schema :ok)} + "application/edn" {:schema (->schema :edn)}}}} :handler (fn [req] {:status 200 :body (-> req :parameters :request)})}}] @@ -594,41 +594,42 @@ spec (-> {:request-method :get :uri "/openapi.json"} app - :body)] + :body) + spec-coercion (= coercion spec/coercion)] (testing "body parameter" - (is (match? (merge {:type "object" - :properties {:b {:type "string"}} - :required ["b"]} - (when-not (#{#'spec/coercion} coercion) - {:additionalProperties false})) - (-> spec - (get-in [:paths "/parameters" :post :requestBody :content "application/json" :schema]) - normalize))) - (is (match? (merge {:type "object" - :properties {:c {:type "string"}} - :required ["c"]} - (when-not (#{#'spec/coercion} coercion) - {:additionalProperties false})) - (-> spec - (get-in [:paths "/parameters" :post :requestBody :content "application/edn" :schema]) - normalize)))) + (is (= (merge {:type "object" + :properties {:b {:type "string"}} + :required ["b"]} + (when-not spec-coercion + {:additionalProperties false})) + (-> spec + (get-in [:paths "/parameters" :post :requestBody :content "application/json" :schema]) + normalize))) + (is (= (merge {:type "object" + :properties {:c {:type "string"}} + :required ["c"]} + (when-not spec-coercion + {:additionalProperties false})) + (-> spec + (get-in [:paths "/parameters" :post :requestBody :content "application/edn" :schema]) + normalize)))) (testing "body response" - (is (match? (merge {:type "object" - :properties {:ok {:type "string"}} - :required ["ok"]} - (when-not (#{#'spec/coercion} coercion) - {:additionalProperties false})) - (-> spec - (get-in [:paths "/parameters" :post :responses 200 :content "application/json" :schema]) - normalize))) - (is (match? (merge {:type "object" - :properties {:edn {:type "string"}} - :required ["edn"]} - (when-not (#{#'spec/coercion} coercion) - {:additionalProperties false})) - (-> spec - (get-in [:paths "/parameters" :post :responses 200 :content "application/edn" :schema]) - normalize)))) + (is (= (merge {:type "object" + :properties {:ok {:type "string"}} + :required ["ok"]} + (when-not spec-coercion + {:additionalProperties false})) + (-> spec + (get-in [:paths "/parameters" :post :responses 200 :content "application/json" :schema]) + normalize))) + (is (= (merge {:type "object" + :properties {:edn {:type "string"}} + :required ["edn"]} + (when-not spec-coercion + {:additionalProperties false})) + (-> spec + (get-in [:paths "/parameters" :post :responses 200 :content "application/edn" :schema]) + normalize)))) (testing "validation" (let [query {:request-method :post :uri "/parameters" @@ -654,22 +655,22 @@ (deftest default-content-type-test (doseq [[coercion ->schema] - [[#'malli/coercion (fn [nom] [:map [nom :string]])] - [#'schema/coercion (fn [nom] {nom s/Str})] - [#'spec/coercion (fn [nom] {nom string?})]]] - (testing coercion + [[malli/coercion (fn [nom] [:map [nom :string]])] + [schema/coercion (fn [nom] {nom s/Str})] + [spec/coercion (fn [nom] {nom string?})]]] + (testing (str coercion) (doseq [content-type ["application/json" "application/edn"]] (testing (str "default content type " content-type) (let [app (ring/ring-handler (ring/router [["/parameters" {:post {:description "parameters" - :coercion @coercion + :coercion coercion :content-types [content-type] ;; TODO should this be under :openapi ? - :parameters {:request {:content {"application/transit" (->schema :transit)} + :parameters {:request {:content {"application/transit" {:schema (->schema :transit)}} :body (->schema :default)}} :responses {200 {:description "success" - :content {"application/transit" (->schema :transit)} + :content {"application/transit" {:schema (->schema :transit)}} :body (->schema :default)}} :handler (fn [req] {:status 200 diff --git a/test/cljc/reitit/ring_coercion_test.cljc b/test/cljc/reitit/ring_coercion_test.cljc index 10aa8409..5827bedd 100644 --- a/test/cljc/reitit/ring_coercion_test.cljc +++ b/test/cljc/reitit/ring_coercion_test.cljc @@ -608,16 +608,16 @@ (testing (str coercion) (let [app (ring/ring-handler (ring/router - ["/foo" {:post {:parameters {:request {:content {"application/json" json-request - "application/edn" edn-request} + ["/foo" {:post {:parameters {:request {:content {"application/json" {:schema json-request} + "application/edn" {:schema edn-request}} :body default-request}} - :responses {200 {:content {"application/json" json-response - "application/edn" edn-response} + :responses {200 {:content {"application/json" {:schema json-response} + "application/edn" {:schema edn-response}} :body default-response}} :handler (fn [req] {:status 200 :body (-> req :parameters :request)})}}] - {#_#_:validate reitit.ring.spec/validate + {:validate reitit.ring.spec/validate :data {:middleware [rrc/coerce-request-middleware rrc/coerce-response-middleware] :coercion coercion}})) From 1b5287724e79c82667de2d6d80b185a8001bc8d5 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Sat, 27 May 2023 19:02:10 +0300 Subject: [PATCH 03/12] format --- test/cljc/reitit/openapi_test.clj | 41 ++++++++++++++----------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index b8bc6f32..c4b9b6d4 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -517,20 +517,19 @@ (is (nil? (validate spec)))))))) (deftest multipart-test - (doseq [[coercion file-schema string-schema] - [[#'malli/coercion - reitit.ring.malli/bytes-part - :string] - [#'schema/coercion - (schema-tools.core/schema {:filename s/Str - :content-type s/Str - :bytes s/Num} - {:openapi {:type "string" - :format "binary"}}) - s/Str] - [#'spec/coercion - reitit.http.interceptors.multipart/bytes-part - string?]]] + (doseq [[coercion file-schema string-schema] [[#'malli/coercion + reitit.ring.malli/bytes-part + :string] + [#'schema/coercion + (schema-tools.core/schema {:filename s/Str + :content-type s/Str + :bytes s/Num} + {:openapi {:type "string" + :format "binary"}}) + s/Str] + [#'spec/coercion + reitit.http.interceptors.multipart/bytes-part + string?]]] (testing (str coercion) (let [app (ring/ring-handler (ring/router @@ -565,10 +564,9 @@ (is (nil? (validate spec)))))))) (deftest per-content-type-test - (doseq [[coercion ->schema] - [[malli/coercion (fn [nom] [:map [nom :string]])] - [schema/coercion (fn [nom] {nom s/Str})] - [spec/coercion (fn [nom] {nom string?})]]] + (doseq [[coercion ->schema] [[malli/coercion (fn [nom] [:map [nom :string]])] + [schema/coercion (fn [nom] {nom s/Str})] + [spec/coercion (fn [nom] {nom string?})]]] (testing (str coercion) (let [app (ring/ring-handler (ring/router @@ -654,10 +652,9 @@ (is (nil? (validate spec)))))))) (deftest default-content-type-test - (doseq [[coercion ->schema] - [[malli/coercion (fn [nom] [:map [nom :string]])] - [schema/coercion (fn [nom] {nom s/Str})] - [spec/coercion (fn [nom] {nom string?})]]] + (doseq [[coercion ->schema] [[malli/coercion (fn [nom] [:map [nom :string]])] + [schema/coercion (fn [nom] {nom s/Str})] + [spec/coercion (fn [nom] {nom string?})]]] (testing (str coercion) (doseq [content-type ["application/json" "application/edn"]] (testing (str "default content type " content-type) From 93a4246682770d98017dc3784fa90a37715deaf9 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Sun, 28 May 2023 12:07:01 +0300 Subject: [PATCH 04/12] allow default --- modules/reitit-core/src/reitit/spec.cljc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/reitit-core/src/reitit/spec.cljc b/modules/reitit-core/src/reitit/spec.cljc index 3d2a7a36..3da7d9ea 100644 --- a/modules/reitit-core/src/reitit/spec.cljc +++ b/modules/reitit-core/src/reitit/spec.cljc @@ -86,7 +86,7 @@ (s/def :reitit.core.coercion/map-model (s/keys :opt-un [:reitit.core.coercion/schema])) (s/def :reitit.core.coercion/content - (s/map-of string? :reitit.core.coercion/map-model)) + (s/map-of (s/or :string string?, :default #{:default}) :reitit.core.coercion/map-model)) (s/def :reitit.core.coercion/query :reitit.core.coercion/model) (s/def :reitit.core.coercion/body :reitit.core.coercion/model) From b1404ada6d123440100fa47444bac8d9ef77f4a7 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Sun, 28 May 2023 16:49:08 +0300 Subject: [PATCH 05/12] top-level :request coercion & stuff --- doc/ring/coercion.md | 30 ++--- modules/reitit-core/src/reitit/coercion.cljc | 100 +++++++++------- .../reitit-http/src/reitit/http/coercion.cljc | 6 +- .../src/reitit/coercion/malli.cljc | 4 +- modules/reitit-ring/src/reitit/ring.cljc | 18 ++- .../reitit-ring/src/reitit/ring/coercion.cljc | 6 +- .../src/reitit/coercion/schema.cljc | 8 +- .../reitit-spec/src/reitit/coercion/spec.cljc | 8 +- test/cljc/reitit/openapi_test.clj | 31 +++-- test/cljc/reitit/ring_coercion_test.cljc | 111 ++++++++++-------- test/cljc/reitit/swagger_test.clj | 2 +- 11 files changed, 180 insertions(+), 144 deletions(-) diff --git a/doc/ring/coercion.md b/doc/ring/coercion.md index 8ca8fb68..5080449d 100644 --- a/doc/ring/coercion.md +++ b/doc/ring/coercion.md @@ -157,21 +157,21 @@ You can also specify request and response body schemas per content-type. The syn ```clj (def app (ring/ring-handler - (ring/router - ["/api" - ["/example" {:post {:coercion reitit.coercion.schema/coercion - :parameters {:request {:content {"application/json" {:y s/Int} - "application/edn" {:z s/Int}} - ;; default if no content-type matches: - :body {:yy s/Int}}} - :responses {200 {:content {"application/json" {:w s/Int} - "application/edn" {:x s/Int}} - ;; default if no content-type matches: - :body {:ww s/Int}} - :handler ...}}]] - {:data {:middleware [rrc/coerce-exceptions-middleware - rrc/coerce-request-middleware - rrc/coerce-response-middleware]}}))) + (ring/router + ["/api" + ["/example" {:post {:coercion reitit.coercion.schema/coercion + :request {:content {"application/json" {:y s/Int} + "application/edn" {:z s/Int}} + ;; default if no content-type matches: + :body {:yy s/Int}} + :responses {200 {:content {"application/json" {:w s/Int} + "application/edn" {:x s/Int}} + ;; default if no content-type matches: + :body {:ww s/Int}} + :handler ...}}}]] + {:data {:middleware [rrc/coerce-exceptions-middleware + rrc/coerce-request-middleware + rrc/coerce-response-middleware]}}))) ``` ## Pretty printing spec errors diff --git a/modules/reitit-core/src/reitit/coercion.cljc b/modules/reitit-core/src/reitit/coercion.cljc index 19106ac0..75167027 100644 --- a/modules/reitit-core/src/reitit/coercion.cljc +++ b/modules/reitit-core/src/reitit/coercion.cljc @@ -37,7 +37,6 @@ (def ^:no-doc default-parameter-coercion {:query (->ParameterCoercion :query-params :string true true) :body (->ParameterCoercion :body-params :body false false) - :request (->ParameterCoercion :body-params :request false false) :form (->ParameterCoercion :form-params :string true true) :header (->ParameterCoercion :headers :string true true) :path (->ParameterCoercion :path-params :string true true) @@ -83,34 +82,45 @@ value) ;; TODO: support faster key walking, walk/keywordize-keys is quite slow... -(defn request-coercer [coercion type model {::keys [extract-request-format parameter-coercion serialize-failed-result] +(defn request-coercer [coercion type model {::keys [extract-request-format parameter-coercion serialize-failed-result skip] :or {extract-request-format extract-request-format-default - parameter-coercion default-parameter-coercion}}] + parameter-coercion default-parameter-coercion + skip #{}}}] (if coercion - (if-let [{:keys [keywordize? open? in style]} (parameter-coercion type)] - (let [transform (comp (if keywordize? walk/keywordize-keys identity) in) - ->open (if open? #(-open-model coercion %) identity) - format-schema-pairs (if (= :request style) - (conj (:content model) [:default {:schema (:body model)}]) - [[:default {:schema model}]]) - format->coercer (some->> (for [[format {:keys [schema]}] format-schema-pairs - :when schema - :let [type (case style :request :body style)]] - [format (-request-coercer coercion type (->open schema))]) - (filter second) - (seq) - (into {}))] - (when format->coercer - (fn [request] - (let [value (transform request) - format (extract-request-format request) - coercer (or (format->coercer format) - (format->coercer :default) - -identity-coercer) - result (coercer value format)] - (if (error? result) - (request-coercion-failed! result coercion value in request serialize-failed-result) - result)))))))) + (when-let [{:keys [keywordize? open? in style]} (parameter-coercion type)] + (when-not (skip style) + (let [transform (comp (if keywordize? walk/keywordize-keys identity) in) + ->open (if open? #(-open-model coercion %) identity) + coercer (-request-coercer coercion style (->open model))] + (when coercer + (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 serialize-failed-result) + result))))))))) + +(defn content-request-coercer [coercion {:keys [content body]} {::keys [extract-request-format serialize-failed-result] + :or {extract-request-format extract-request-format-default}}] + (when coercion + (let [in :body-params + format->coercer (some->> (concat (when body + [[:default (-request-coercer coercion :body body)]]) + (for [[format {:keys [schema]}] content, :when schema] + [format (-request-coercer coercion :body schema)])) + (filter second) (seq) (into (array-map)))] + (when format->coercer + (fn [request] + (let [value (in request) + format (extract-request-format request) + coercer (or (format->coercer format) + (format->coercer :default) + -identity-coercer) + result (coercer value format)] + (if (error? result) + (request-coercion-failed! result coercion value in request serialize-failed-result) + result))))))) (defn extract-response-format-default [request _] (-> request :muuntaja/response :format)) @@ -118,18 +128,18 @@ (defn response-coercer [coercion {:keys [content body]} {:keys [extract-response-format serialize-failed-result] :or {extract-response-format extract-response-format-default}}] (if coercion - (let [per-format-coercers (some->> (for [[format {:keys [schema]}] content - :when schema] - [format (-response-coercer coercion schema)]) - (filter second) - (seq) - (into {})) - default (when body (-response-coercer coercion body))] - (when (or per-format-coercers default) + (let [format->coercer (some->> (concat (when body + [[:default (-response-coercer coercion body)]]) + (for [[format {:keys [schema]}] content, :when schema] + [format (-response-coercer coercion schema)])) + (filter second) (seq) (into (array-map)))] + (when format->coercer (fn [request response] (let [format (extract-response-format request response) value (:body response) - coercer (get per-format-coercers format (or default -identity-coercer)) + coercer (or (format->coercer format) + (format->coercer :default) + -identity-coercer) result (coercer value format)] (if (error? result) (response-coercion-failed! result coercion value request response serialize-failed-result) @@ -153,10 +163,15 @@ (impl/fast-assoc response :body (coercer request response)) response))) -(defn request-coercers [coercion parameters opts] - (some->> (for [[k v] parameters, :when v] - [k (request-coercer coercion k v opts)]) - (filter second) (seq) (into {}))) +(defn request-coercers + ([coercion parameters opts] + (some->> (for [[k v] parameters, :when v] + [k (request-coercer coercion k v opts)]) + (filter second) (seq) (into {}))) + ([coercion parameters request opts] + (let [crc (when request (some->> (content-request-coercer coercion request opts) (array-map :request))) + rcs (request-coercers coercion parameters (cond-> opts request (assoc ::skip #{:body})))] + (if (and crc rcs) (into crc (vec rcs)) (or crc rcs))))) (defn response-coercers [coercion responses opts] (some->> (for [[status model] responses] @@ -170,8 +185,8 @@ ;; api-docs ;; -(defn -warn-unsupported-coercions [{:keys [parameters responses] :as _data}] - (when (:request parameters) +(defn -warn-unsupported-coercions [{:keys [request responses] :as _data}] + (when request (println "WARNING [reitit.coercion]: swagger apidocs don't support :request coercion")) (when (some :content (vals responses)) (println "WARNING [reitit.coercion]: swagger apidocs don't support :responses :content coercion"))) @@ -197,7 +212,6 @@ (into {})))) (-get-apidocs coercion specification)))))) - ;; ;; integration ;; diff --git a/modules/reitit-http/src/reitit/http/coercion.cljc b/modules/reitit-http/src/reitit/http/coercion.cljc index 8e63db28..4807f3a3 100644 --- a/modules/reitit-http/src/reitit/http/coercion.cljc +++ b/modules/reitit-http/src/reitit/http/coercion.cljc @@ -10,15 +10,15 @@ [] {:name ::coerce-request :spec ::rs/parameters - :compile (fn [{:keys [coercion parameters]} opts] + :compile (fn [{:keys [coercion parameters request]} opts] (cond ;; no coercion, skip (not coercion) nil ;; just coercion, don't mount - (not parameters) {} + (not (or parameters request)) {} ;; mount :else - (if-let [coercers (coercion/request-coercers coercion parameters opts)] + (if-let [coercers (coercion/request-coercers coercion parameters request opts)] {:enter (fn [ctx] (let [request (:request ctx) coerced (coercion/coerce-request coercers request) diff --git a/modules/reitit-malli/src/reitit/coercion/malli.cljc b/modules/reitit-malli/src/reitit/coercion/malli.cljc index 36daefa4..30290fba 100644 --- a/modules/reitit-malli/src/reitit/coercion/malli.cljc +++ b/modules/reitit-malli/src/reitit/coercion/malli.cljc @@ -134,8 +134,8 @@ :options nil}) (defn -get-apidocs-openapi - [_ {:keys [parameters responses content-types] :or {content-types ["application/json"]}} options] - (let [{:keys [body request multipart]} parameters + [_ {:keys [request parameters responses content-types] :or {content-types ["application/json"]}} options] + (let [{:keys [body multipart]} parameters parameters (dissoc parameters :request :body :multipart) ->schema-object (fn [schema opts] (let [current-opts (merge options opts)] diff --git a/modules/reitit-ring/src/reitit/ring.cljc b/modules/reitit-ring/src/reitit/ring.cljc index eee8452c..07332ec1 100644 --- a/modules/reitit-ring/src/reitit/ring.cljc +++ b/modules/reitit-ring/src/reitit/ring.cljc @@ -32,17 +32,23 @@ (defn -update-paths [f] (let [not-request? #(not= :request %) http-method? #(contains? http-methods %)] - [;; default parameters and responses + [;; default parameters [[:parameters not-request?] f] [[http-method? :parameters not-request?] f] + + ;; default responses [[:responses any? :body] f] [[http-method? :responses any? :body] f] - ;; openapi3 parameters and responses - [[:parameters :request :content any? :schema] f] - [[http-method? :parameters :request :content any? :schema] f] - [[:parameters :request :body] f] - [[http-method? :parameters :request :body] f] + ;; openapi3 request + [[:request :content any? :schema] f] + [[http-method? :request :content any? :schema] f] + + ;; openapi3 LEGACY body + [[:request :body] f] + [[http-method? :request :body] f] + + ;; openapi3 responses [[:responses any? :content any? :schema] f] [[http-method? :responses any? :content any? :schema] f]])) diff --git a/modules/reitit-ring/src/reitit/ring/coercion.cljc b/modules/reitit-ring/src/reitit/ring/coercion.cljc index efbe83f7..8d7cbe0f 100644 --- a/modules/reitit-ring/src/reitit/ring/coercion.cljc +++ b/modules/reitit-ring/src/reitit/ring/coercion.cljc @@ -24,15 +24,15 @@ and :parameters from route data, otherwise does not mount." {:name ::coerce-request :spec ::rs/parameters - :compile (fn [{:keys [coercion parameters]} opts] + :compile (fn [{:keys [coercion parameters request]} opts] (cond ;; no coercion, skip (not coercion) nil ;; just coercion, don't mount - (not parameters) {} + (not (or parameters request)) {} ;; mount :else - (if-let [coercers (coercion/request-coercers coercion parameters opts)] + (if-let [coercers (coercion/request-coercers coercion parameters request opts)] (fn [handler] (fn ([request] diff --git a/modules/reitit-schema/src/reitit/coercion/schema.cljc b/modules/reitit-schema/src/reitit/coercion/schema.cljc index b746c516..a6beeae7 100644 --- a/modules/reitit-schema/src/reitit/coercion/schema.cljc +++ b/modules/reitit-schema/src/reitit/coercion/schema.cljc @@ -47,7 +47,7 @@ (reify coercion/Coercion (-get-name [_] :schema) (-get-options [_] opts) - (-get-apidocs [_ specification {:keys [parameters responses content-types] + (-get-apidocs [_ specification {:keys [request parameters responses content-types] :or {content-types ["application/json"]}}] ;; TODO: this looks identical to spec, refactor when schema is done. (case specification @@ -67,12 +67,12 @@ (when (:body parameters) {:requestBody (openapi/openapi-spec {::openapi/content (zipmap content-types (repeat (:body parameters)))})}) - (when (:request parameters) + (when request {:requestBody (openapi/openapi-spec {::openapi/content (merge - (when-let [default (get-in parameters [:request :body])] + (when-let [default (:body request)] (zipmap content-types (repeat default))) - (->> (for [[content-type {:keys [schema]}] (:content (:request parameters))] + (->> (for [[content-type {:keys [schema]}] (:content request)] [content-type schema]) (into {})))})}) (when (:multipart parameters) diff --git a/modules/reitit-spec/src/reitit/coercion/spec.cljc b/modules/reitit-spec/src/reitit/coercion/spec.cljc index 848fc3a4..ca7b724d 100644 --- a/modules/reitit-spec/src/reitit/coercion/spec.cljc +++ b/modules/reitit-spec/src/reitit/coercion/spec.cljc @@ -88,7 +88,7 @@ (reify coercion/Coercion (-get-name [_] :spec) (-get-options [_] opts) - (-get-apidocs [this specification {:keys [parameters responses content-types] + (-get-apidocs [this specification {:keys [request parameters responses content-types] :or {content-types ["application/json"]}}] (case specification :swagger (swagger/swagger-spec @@ -108,12 +108,12 @@ (when (:body parameters) {:requestBody (openapi/openapi-spec {::openapi/content (zipmap content-types (repeat (:body parameters)))})}) - (when (:request parameters) + (when request {:requestBody (openapi/openapi-spec {::openapi/content (merge - (when-let [default (get-in parameters [:request :body])] + (when-let [default (:body request)] (zipmap content-types (repeat default))) - (->> (for [[content-type {:keys [schema]}] (:content (:request parameters))] + (->> (for [[content-type {:keys [schema]}] (:content request)] [content-type schema]) (into {})))})}) (when (:multipart parameters) diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index c4b9b6d4..31af695a 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -457,8 +457,8 @@ [["/examples" {:post {:decription "examples" :coercion @coercion - :parameters {:query (->schema :q) - :request {:body (->schema :b)}} + :request {:body (->schema :b)} + :parameters {:query (->schema :q)} :responses {200 {:description "success" :body (->schema :ok)}} :openapi {:requestBody @@ -573,8 +573,8 @@ [["/parameters" {:post {:description "parameters" :coercion coercion - :parameters {:request {:content {"application/json" {:schema (->schema :b)} - "application/edn" {:schema (->schema :c)}}}} + :request {:content {"application/json" {:schema (->schema :b)} + "application/edn" {:schema (->schema :c)}}} :responses {200 {:description "success" :content {"application/json" {:schema (->schema :ok)} "application/edn" {:schema (->schema :edn)}}}} @@ -664,8 +664,8 @@ {:post {:description "parameters" :coercion coercion :content-types [content-type] ;; TODO should this be under :openapi ? - :parameters {:request {:content {"application/transit" {:schema (->schema :transit)}} - :body (->schema :default)}} + :request {:content {"application/transit" {:schema (->schema :transit)}} + :body (->schema :default)} :responses {200 {:description "success" :content {"application/transit" {:schema (->schema :transit)}} :body (->schema :default)}} @@ -705,16 +705,15 @@ [["/parameters" {:post {:description "parameters" :coercion malli/coercion - :parameters {:request - {:body - [:schema - {:registry {"friend" [:map - [:age int?] - [:pet [:ref "pet"]]] - "pet" [:map - [:name :string] - [:friends [:vector [:ref "friend"]]]]}} - "friend"]}} + :request {:body + [:schema + {:registry {"friend" [:map + [:age int?] + [:pet [:ref "pet"]]] + "pet" [:map + [:name :string] + [:friends [:vector [:ref "friend"]]]]}} + "friend"]} :handler (fn [req] {:status 200 :body (-> req :parameters :request)})}}] diff --git a/test/cljc/reitit/ring_coercion_test.cljc b/test/cljc/reitit/ring_coercion_test.cljc index 5827bedd..2c919f9c 100644 --- a/test/cljc/reitit/ring_coercion_test.cljc +++ b/test/cljc/reitit/ring_coercion_test.cljc @@ -606,53 +606,70 @@ {:request any? :response (clojure.spec.alpha/spec #{:end})} {:request any? :response (clojure.spec.alpha/spec #{:default})}]]] (testing (str coercion) - (let [app (ring/ring-handler - (ring/router - ["/foo" {:post {:parameters {:request {:content {"application/json" {:schema json-request} - "application/edn" {:schema edn-request}} - :body default-request}} - :responses {200 {:content {"application/json" {:schema json-response} - "application/edn" {:schema edn-response}} - :body default-response}} - :handler (fn [req] - {:status 200 - :body (-> req :parameters :request)})}}] - {:validate reitit.ring.spec/validate - :data {:middleware [rrc/coerce-request-middleware - rrc/coerce-response-middleware] - :coercion coercion}})) - call (fn [request] - (try - (app request) - (catch ExceptionInfo e - (select-keys (ex-data e) [:type :in])))) - request (fn [request-format response-format body] - {:request-method :post - :uri "/foo" - :muuntaja/request {:format request-format} - :muuntaja/response {:format response-format} - :body-params body})] - (testing "succesful call" - (is (= {:status 200 :body {:request :json, :response :json}} - (call (request "application/json" "application/json" {:request :json :response :json})))) - (is (= {:status 200 :body {:request :edn, :response :json}} - (call (request "application/edn" "application/json" {:request :edn :response :json})))) - (is (= {:status 200 :body {:request :default, :response :default}} - (call (request "application/transit" "application/transit" {:request :default :response :default}))))) - (testing "request validation fails" - (is (= {:type :reitit.coercion/request-coercion :in [:request :body-params]} - (call (request "application/edn" "application/json" {:request :json :response :json})))) - (is (= {:type :reitit.coercion/request-coercion :in [:request :body-params]} - (call (request "application/json" "application/json" {:request :edn :response :json})))) - (is (= {:type :reitit.coercion/request-coercion :in [:request :body-params]} - (call (request "application/transit" "application/json" {:request :edn :response :json}))))) - (testing "response validation fails" - (is (= {:type :reitit.coercion/response-coercion :in [:response :body]} - (call (request "application/json" "application/json" {:request :json :response :edn})))) - (is (= {:type :reitit.coercion/response-coercion :in [:response :body]} - (call (request "application/json" "application/edn" {:request :json :response :json})))) - (is (= {:type :reitit.coercion/response-coercion :in [:response :body]} - (call (request "application/json" "application/transit" {:request :json :response :json}))))))))) + (doseq [app [(ring/ring-handler + (ring/router + ["/foo" {:post {:request {:content {"application/json" {:schema json-request} + "application/edn" {:schema edn-request}} + :body default-request} + :responses {200 {:content {"application/json" {:schema json-response} + "application/edn" {:schema edn-response}} + :body default-response}} + :handler (fn [req] + {:status 200 + :body (-> req :parameters :request)})}}] + {:validate reitit.ring.spec/validate + :data {:middleware [rrc/coerce-request-middleware + rrc/coerce-response-middleware] + :coercion coercion}})) + (ring/ring-handler + (ring/router + ["/foo" {:post {:request {:content {"application/json" {:schema json-request} + "application/edn" {:schema edn-request} + :default {:schema default-request}} + :body json-request} ;; not applied as :default exists + :responses {200 {:content {"application/json" {:schema json-response} + "application/edn" {:schema edn-response} + :default {:schema default-response}} + :body json-response}} ;; not applied as :default exists + :handler (fn [req] + {:status 200 + :body (-> req :parameters :request)})}}] + {:validate reitit.ring.spec/validate + :data {:middleware [rrc/coerce-request-middleware + rrc/coerce-response-middleware] + :coercion coercion}}))]] + (let [call (fn [request] + (try + (app request) + (catch ExceptionInfo e + (select-keys (ex-data e) [:type :in])))) + request (fn [request-format response-format body] + {:request-method :post + :uri "/foo" + :muuntaja/request {:format request-format} + :muuntaja/response {:format response-format} + :body-params body})] + (testing "succesful call" + (is (= {:status 200 :body {:request :json, :response :json}} + (call (request "application/json" "application/json" {:request :json :response :json})))) + (is (= {:status 200 :body {:request :edn, :response :json}} + (call (request "application/edn" "application/json" {:request :edn :response :json})))) + (is (= {:status 200 :body {:request :default, :response :default}} + (call (request "application/transit" "application/transit" {:request :default :response :default}))))) + (testing "request validation fails" + (is (= {:type :reitit.coercion/request-coercion :in [:request :body-params]} + (call (request "application/edn" "application/json" {:request :json :response :json})))) + (is (= {:type :reitit.coercion/request-coercion :in [:request :body-params]} + (call (request "application/json" "application/json" {:request :edn :response :json})))) + (is (= {:type :reitit.coercion/request-coercion :in [:request :body-params]} + (call (request "application/transit" "application/json" {:request :edn :response :json}))))) + (testing "response validation fails" + (is (= {:type :reitit.coercion/response-coercion :in [:response :body]} + (call (request "application/json" "application/json" {:request :json :response :edn})))) + (is (= {:type :reitit.coercion/response-coercion :in [:response :body]} + (call (request "application/json" "application/edn" {:request :json :response :json})))) + (is (= {:type :reitit.coercion/response-coercion :in [:response :body]} + (call (request "application/json" "application/transit" {:request :json :response :json})))))))))) #?(:clj diff --git a/test/cljc/reitit/swagger_test.clj b/test/cljc/reitit/swagger_test.clj index c00f9cd7..2862edf1 100644 --- a/test/cljc/reitit/swagger_test.clj +++ b/test/cljc/reitit/swagger_test.clj @@ -401,7 +401,7 @@ (ring/router [["/parameters" {:post {:coercion spec/coercion - :parameters {:request {:content {"application/json" {:x string?}}}} + :request {:content {"application/json" {:x string?}}} :handler identity}}] ["/swagger.json" {:get {:no-doc true From 07281547514d00094390dbb8054ff573123427a8 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Mon, 29 May 2023 12:07:54 +0300 Subject: [PATCH 06/12] name the doseq-tests --- test/cljc/reitit/ring_coercion_test.cljc | 132 ++++++++++++----------- 1 file changed, 68 insertions(+), 64 deletions(-) diff --git a/test/cljc/reitit/ring_coercion_test.cljc b/test/cljc/reitit/ring_coercion_test.cljc index 2c919f9c..ec2bd1fe 100644 --- a/test/cljc/reitit/ring_coercion_test.cljc +++ b/test/cljc/reitit/ring_coercion_test.cljc @@ -606,70 +606,74 @@ {:request any? :response (clojure.spec.alpha/spec #{:end})} {:request any? :response (clojure.spec.alpha/spec #{:default})}]]] (testing (str coercion) - (doseq [app [(ring/ring-handler - (ring/router - ["/foo" {:post {:request {:content {"application/json" {:schema json-request} - "application/edn" {:schema edn-request}} - :body default-request} - :responses {200 {:content {"application/json" {:schema json-response} - "application/edn" {:schema edn-response}} - :body default-response}} - :handler (fn [req] - {:status 200 - :body (-> req :parameters :request)})}}] - {:validate reitit.ring.spec/validate - :data {:middleware [rrc/coerce-request-middleware - rrc/coerce-response-middleware] - :coercion coercion}})) - (ring/ring-handler - (ring/router - ["/foo" {:post {:request {:content {"application/json" {:schema json-request} - "application/edn" {:schema edn-request} - :default {:schema default-request}} - :body json-request} ;; not applied as :default exists - :responses {200 {:content {"application/json" {:schema json-response} - "application/edn" {:schema edn-response} - :default {:schema default-response}} - :body json-response}} ;; not applied as :default exists - :handler (fn [req] - {:status 200 - :body (-> req :parameters :request)})}}] - {:validate reitit.ring.spec/validate - :data {:middleware [rrc/coerce-request-middleware - rrc/coerce-response-middleware] - :coercion coercion}}))]] - (let [call (fn [request] - (try - (app request) - (catch ExceptionInfo e - (select-keys (ex-data e) [:type :in])))) - request (fn [request-format response-format body] - {:request-method :post - :uri "/foo" - :muuntaja/request {:format request-format} - :muuntaja/response {:format response-format} - :body-params body})] - (testing "succesful call" - (is (= {:status 200 :body {:request :json, :response :json}} - (call (request "application/json" "application/json" {:request :json :response :json})))) - (is (= {:status 200 :body {:request :edn, :response :json}} - (call (request "application/edn" "application/json" {:request :edn :response :json})))) - (is (= {:status 200 :body {:request :default, :response :default}} - (call (request "application/transit" "application/transit" {:request :default :response :default}))))) - (testing "request validation fails" - (is (= {:type :reitit.coercion/request-coercion :in [:request :body-params]} - (call (request "application/edn" "application/json" {:request :json :response :json})))) - (is (= {:type :reitit.coercion/request-coercion :in [:request :body-params]} - (call (request "application/json" "application/json" {:request :edn :response :json})))) - (is (= {:type :reitit.coercion/request-coercion :in [:request :body-params]} - (call (request "application/transit" "application/json" {:request :edn :response :json}))))) - (testing "response validation fails" - (is (= {:type :reitit.coercion/response-coercion :in [:response :body]} - (call (request "application/json" "application/json" {:request :json :response :edn})))) - (is (= {:type :reitit.coercion/response-coercion :in [:response :body]} - (call (request "application/json" "application/edn" {:request :json :response :json})))) - (is (= {:type :reitit.coercion/response-coercion :in [:response :body]} - (call (request "application/json" "application/transit" {:request :json :response :json})))))))))) + (doseq [{:keys [name app]} + [{:name "using top-level :body" + :app (ring/ring-handler + (ring/router + ["/foo" {:post {:request {:content {"application/json" {:schema json-request} + "application/edn" {:schema edn-request}} + :body default-request} + :responses {200 {:content {"application/json" {:schema json-response} + "application/edn" {:schema edn-response}} + :body default-response}} + :handler (fn [req] + {:status 200 + :body (-> req :parameters :request)})}}] + {:validate reitit.ring.spec/validate + :data {:middleware [rrc/coerce-request-middleware + rrc/coerce-response-middleware] + :coercion coercion}}))} + {:name "using :default content" + :app (ring/ring-handler + (ring/router + ["/foo" {:post {:request {:content {"application/json" {:schema json-request} + "application/edn" {:schema edn-request} + :default {:schema default-request}} + :body json-request} ;; not applied as :default exists + :responses {200 {:content {"application/json" {:schema json-response} + "application/edn" {:schema edn-response} + :default {:schema default-response}} + :body json-response}} ;; not applied as :default exists + :handler (fn [req] + {:status 200 + :body (-> req :parameters :request)})}}] + {:validate reitit.ring.spec/validate + :data {:middleware [rrc/coerce-request-middleware + rrc/coerce-response-middleware] + :coercion coercion}}))}]] + (testing name + (let [call (fn [request] + (try + (app request) + (catch ExceptionInfo e + (select-keys (ex-data e) [:type :in])))) + request (fn [request-format response-format body] + {:request-method :post + :uri "/foo" + :muuntaja/request {:format request-format} + :muuntaja/response {:format response-format} + :body-params body})] + (testing "succesful call" + (is (= {:status 200 :body {:request :json, :response :json}} + (call (request "application/json" "application/json" {:request :json :response :json})))) + (is (= {:status 200 :body {:request :edn, :response :json}} + (call (request "application/edn" "application/json" {:request :edn :response :json})))) + (is (= {:status 200 :body {:request :default, :response :default}} + (call (request "application/transit" "application/transit" {:request :default :response :default}))))) + (testing "request validation fails" + (is (= {:type :reitit.coercion/request-coercion :in [:request :body-params]} + (call (request "application/edn" "application/json" {:request :json :response :json})))) + (is (= {:type :reitit.coercion/request-coercion :in [:request :body-params]} + (call (request "application/json" "application/json" {:request :edn :response :json})))) + (is (= {:type :reitit.coercion/request-coercion :in [:request :body-params]} + (call (request "application/transit" "application/json" {:request :edn :response :json}))))) + (testing "response validation fails" + (is (= {:type :reitit.coercion/response-coercion :in [:response :body]} + (call (request "application/json" "application/json" {:request :json :response :edn})))) + (is (= {:type :reitit.coercion/response-coercion :in [:response :body]} + (call (request "application/json" "application/edn" {:request :json :response :json})))) + (is (= {:type :reitit.coercion/response-coercion :in [:response :body]} + (call (request "application/json" "application/transit" {:request :json :response :json}))))))))))) #?(:clj From 12f0970e393dbdb841758a68d1b149d4e759f727 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Fri, 18 Aug 2023 15:04:47 +0300 Subject: [PATCH 07/12] fix example --- doc/ring/coercion.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/ring/coercion.md b/doc/ring/coercion.md index 5080449d..5ba6a70e 100644 --- a/doc/ring/coercion.md +++ b/doc/ring/coercion.md @@ -167,8 +167,8 @@ You can also specify request and response body schemas per content-type. The syn :responses {200 {:content {"application/json" {:w s/Int} "application/edn" {:x s/Int}} ;; default if no content-type matches: - :body {:ww s/Int}} - :handler ...}}}]] + :body {:ww s/Int}}} + :handler ...}}]] {:data {:middleware [rrc/coerce-exceptions-middleware rrc/coerce-request-middleware rrc/coerce-response-middleware]}}))) From 81dfe45b72e24c18fbda4c4ca10a46dcfcb43761 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Fri, 18 Aug 2023 15:17:01 +0300 Subject: [PATCH 08/12] fix example --- doc/ring/coercion.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/ring/coercion.md b/doc/ring/coercion.md index 5ba6a70e..32f51e68 100644 --- a/doc/ring/coercion.md +++ b/doc/ring/coercion.md @@ -160,12 +160,12 @@ You can also specify request and response body schemas per content-type. The syn (ring/router ["/api" ["/example" {:post {:coercion reitit.coercion.schema/coercion - :request {:content {"application/json" {:y s/Int} - "application/edn" {:z s/Int}} + :request {:content {"application/json" {:schema {:y s/Int}} + "application/edn" {:schema {:z s/Int}}} ;; default if no content-type matches: :body {:yy s/Int}} - :responses {200 {:content {"application/json" {:w s/Int} - "application/edn" {:x s/Int}} + :responses {200 {:content {"application/json" {:schema {:w s/Int}} + "application/edn" {:schema {:x s/Int}}} ;; default if no content-type matches: :body {:ww s/Int}}} :handler ...}}]] From d8e9819e0ab033619e870465a0e228a38f574f57 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Fri, 18 Aug 2023 16:47:20 +0300 Subject: [PATCH 09/12] fix responses & request --- modules/reitit-core/src/reitit/coercion.cljc | 12 ++++++---- .../src/reitit/coercion/malli.cljc | 23 +++++++++---------- .../src/reitit/coercion/schema.cljc | 13 ++++++----- .../reitit-spec/src/reitit/coercion/spec.cljc | 20 +++++++++------- 4 files changed, 38 insertions(+), 30 deletions(-) diff --git a/modules/reitit-core/src/reitit/coercion.cljc b/modules/reitit-core/src/reitit/coercion.cljc index 75167027..f3b58053 100644 --- a/modules/reitit-core/src/reitit/coercion.cljc +++ b/modules/reitit-core/src/reitit/coercion.cljc @@ -101,6 +101,10 @@ (request-coercion-failed! result coercion value in request serialize-failed-result) result))))))))) +(defn get-default-schema [request-or-response] + (or (-> request-or-response :content :default :schema) + (:body request-or-response))) + (defn content-request-coercer [coercion {:keys [content body]} {::keys [extract-request-format serialize-failed-result] :or {extract-request-format extract-request-format-default}}] (when coercion @@ -129,10 +133,10 @@ :or {extract-response-format extract-response-format-default}}] (if coercion (let [format->coercer (some->> (concat (when body - [[:default (-response-coercer coercion body)]]) - (for [[format {:keys [schema]}] content, :when schema] - [format (-response-coercer coercion schema)])) - (filter second) (seq) (into (array-map)))] + [[:default (-response-coercer coercion body)]]) + (for [[format {:keys [schema]}] content, :when schema] + [format (-response-coercer coercion schema)])) + (filter second) (seq) (into (array-map)))] (when format->coercer (fn [request response] (let [format (extract-response-format request response) diff --git a/modules/reitit-malli/src/reitit/coercion/malli.cljc b/modules/reitit-malli/src/reitit/coercion/malli.cljc index 30290fba..91a155fa 100644 --- a/modules/reitit-malli/src/reitit/coercion/malli.cljc +++ b/modules/reitit-malli/src/reitit/coercion/malli.cljc @@ -168,12 +168,12 @@ ;; request allow to different :requestBody per content-type {:requestBody {:content (merge - (when (:body request) + (when-let [default (coercion/get-default-schema request)] (into {} (map (fn [content-type] - (let [schema (->schema-object (:body request) {:in :requestBody - :type :schema - :content-type content-type})] + (let [schema (->schema-object default {:in :requestBody + :type :schema + :content-type content-type})] [content-type {:schema schema}]))) content-types)) (into {} @@ -194,15 +194,14 @@ (when responses {:responses (into {} - (map (fn [[status {:keys [body content] - :as response}]] + (map (fn [[status {:keys [content], :as response}]] (let [content (merge - (when body + (when-let [default (coercion/get-default-schema response)] (into {} (map (fn [content-type] - (let [schema (->schema-object body {:in :responses - :type :schema - :content-type content-type})] + (let [schema (->schema-object default {:in :responses + :type :schema + :content-type content-type})] [content-type {:schema schema}]))) content-types)) (when content @@ -215,8 +214,8 @@ content)))] [status (merge (select-keys response [:description]) (when content - {:content content}))]))) - responses)})))) + {:content content}))])) + responses))})))) (defn create ([] diff --git a/modules/reitit-schema/src/reitit/coercion/schema.cljc b/modules/reitit-schema/src/reitit/coercion/schema.cljc index a6beeae7..0ffa4e2b 100644 --- a/modules/reitit-schema/src/reitit/coercion/schema.cljc +++ b/modules/reitit-schema/src/reitit/coercion/schema.cljc @@ -70,7 +70,7 @@ (when request {:requestBody (openapi/openapi-spec {::openapi/content (merge - (when-let [default (:body request)] + (when-let [default (coercion/get-default-schema request)] (zipmap content-types (repeat default))) (->> (for [[content-type {:keys [schema]}] (:content request)] [content-type schema]) @@ -83,15 +83,16 @@ {:responses (into (empty responses) - (for [[k {:keys [body content] :as response}] responses] + (for [[k {:keys [content] :as response}] responses + :let [default (coercion/get-default-schema response)]] [k (merge (select-keys response [:description]) - (when (or body content) + (when (or content default) (openapi/openapi-spec {::openapi/content (merge - (when body - (zipmap content-types (repeat body))) - (->> (for [[content-type {:keys [schema]}] (:content response)] + (when default + (zipmap content-types (repeat default))) + (->> (for [[content-type {:keys [schema]}] content] [content-type schema]) (into {})))})))]))})) diff --git a/modules/reitit-spec/src/reitit/coercion/spec.cljc b/modules/reitit-spec/src/reitit/coercion/spec.cljc index ca7b724d..8043a78a 100644 --- a/modules/reitit-spec/src/reitit/coercion/spec.cljc +++ b/modules/reitit-spec/src/reitit/coercion/spec.cljc @@ -83,13 +83,16 @@ :string {:default string-transformer} :response {:default no-op-transformer}}}) +(defn get-request-default-body [request] + (or (-> request :content :default :schema) (:body request))) + (defn create [{:keys [transformers coerce-response?] :as opts}] ^{:type ::coercion/coercion} (reify coercion/Coercion (-get-name [_] :spec) (-get-options [_] opts) - (-get-apidocs [this specification {:keys [request parameters responses content-types] - :or {content-types ["application/json"]}}] + (-get-apidocs [_ specification {:keys [request parameters responses content-types] + :or {content-types ["application/json"]}}] (case specification :swagger (swagger/swagger-spec (merge @@ -111,7 +114,7 @@ (when request {:requestBody (openapi/openapi-spec {::openapi/content (merge - (when-let [default (:body request)] + (when-let [default (coercion/get-default-schema request)] (zipmap content-types (repeat default))) (->> (for [[content-type {:keys [schema]}] (:content request)] [content-type schema]) @@ -124,15 +127,16 @@ {:responses (into (empty responses) - (for [[k {:keys [body content] :as response}] responses] + (for [[k {:keys [content] :as response}] responses + :let [default (coercion/get-default-schema response)]] [k (merge (select-keys response [:description]) - (when (or body content) + (when (or content default) (openapi/openapi-spec {::openapi/content (merge - (when body - (zipmap content-types (repeat (:body response)))) - (->> (for [[content-type {:keys [schema]}] (:content response)] + (when default + (zipmap content-types (repeat default))) + (->> (for [[content-type {:keys [schema]}] content] [content-type schema]) (into {})))})))]))})) (throw From 226ca889b6ff2d76ca0c139c8959512c3db14ca9 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Fri, 18 Aug 2023 17:17:56 +0300 Subject: [PATCH 10/12] openapi content tests --- .../src/reitit/coercion/malli.cljc | 36 +++++++++--------- .../src/reitit/coercion/schema.cljc | 13 ++++--- .../reitit-spec/src/reitit/coercion/spec.cljc | 16 ++++---- test/cljc/reitit/openapi_test.clj | 37 +++++++++++++++---- 4 files changed, 65 insertions(+), 37 deletions(-) diff --git a/modules/reitit-malli/src/reitit/coercion/malli.cljc b/modules/reitit-malli/src/reitit/coercion/malli.cljc index 91a155fa..4a4dc9ea 100644 --- a/modules/reitit-malli/src/reitit/coercion/malli.cljc +++ b/modules/reitit-malli/src/reitit/coercion/malli.cljc @@ -195,23 +195,25 @@ {:responses (into {} (map (fn [[status {:keys [content], :as response}]] - (let [content (merge - (when-let [default (coercion/get-default-schema response)] - (into {} - (map (fn [content-type] - (let [schema (->schema-object default {:in :responses - :type :schema - :content-type content-type})] - [content-type {:schema schema}]))) - content-types)) - (when content - (into {} - (map (fn [[content-type {:keys [schema]}]] - (let [schema (->schema-object schema {:in :responses - :type :schema - :content-type content-type})] - [content-type {:schema schema}]))) - content)))] + (let [default (coercion/get-default-schema response) + content (-> (merge + (when default + (into {} + (map (fn [content-type] + (let [schema (->schema-object default {:in :responses + :type :schema + :content-type content-type})] + [content-type {:schema schema}]))) + content-types)) + (when content + (into {} + (map (fn [[content-type {:keys [schema]}]] + (let [schema (->schema-object schema {:in :responses + :type :schema + :content-type content-type})] + [content-type {:schema schema}]))) + content))) + (dissoc :default))] [status (merge (select-keys response [:description]) (when content {:content content}))])) diff --git a/modules/reitit-schema/src/reitit/coercion/schema.cljc b/modules/reitit-schema/src/reitit/coercion/schema.cljc index 0ffa4e2b..e0822ad9 100644 --- a/modules/reitit-schema/src/reitit/coercion/schema.cljc +++ b/modules/reitit-schema/src/reitit/coercion/schema.cljc @@ -89,12 +89,13 @@ (select-keys response [:description]) (when (or content default) (openapi/openapi-spec - {::openapi/content (merge - (when default - (zipmap content-types (repeat default))) - (->> (for [[content-type {:keys [schema]}] content] - [content-type schema]) - (into {})))})))]))})) + {::openapi/content (-> (merge + (when default + (zipmap content-types (repeat default))) + (->> (for [[content-type {:keys [schema]}] content] + [content-type schema]) + (into {}))) + (dissoc :default))})))]))})) (throw (ex-info diff --git a/modules/reitit-spec/src/reitit/coercion/spec.cljc b/modules/reitit-spec/src/reitit/coercion/spec.cljc index 8043a78a..b988d140 100644 --- a/modules/reitit-spec/src/reitit/coercion/spec.cljc +++ b/modules/reitit-spec/src/reitit/coercion/spec.cljc @@ -128,17 +128,19 @@ (into (empty responses) (for [[k {:keys [content] :as response}] responses - :let [default (coercion/get-default-schema response)]] + :let [default (coercion/get-default-schema response) + content-types (remove #{:default} content-types)]] [k (merge (select-keys response [:description]) (when (or content default) (openapi/openapi-spec - {::openapi/content (merge - (when default - (zipmap content-types (repeat default))) - (->> (for [[content-type {:keys [schema]}] content] - [content-type schema]) - (into {})))})))]))})) + {::openapi/content (-> (merge + (when default + (zipmap content-types (repeat default))) + (->> (for [[content-type {:keys [schema]}] content] + [content-type schema]) + (into {}))) + (dissoc :default))})))]))})) (throw (ex-info (str "Can't produce Spec apidocs for " specification) diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index 31af695a..564b1a6d 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -65,7 +65,10 @@ :description "kosh"}}} :responses {200 {:description "success" :body {:total int?}} - 500 {:description "fail"}} + 500 {:description "fail"} + 504 {:description "default" + :content {:default {:schema {:error string?}}} + :body {:masked string?}}} :handler (fn [{{{:keys [z]} :path xs :body} :parameters}] {:status 200, :body {:total (+ (reduce + xs) z)}})}}]] @@ -91,7 +94,10 @@ :content {"application/json" {:schema {:type "string"}}}}}} :responses {200 {:description "success" :body [:map [:total int?]]} - 500 {:description "fail"}} + 500 {:description "fail"} + 504 {:description "default" + :content {:default {:schema {:error string?}}} + :body {:masked string?}}} :handler (fn [{{{:keys [z]} :path xs :body} :parameters}] {:status 200, :body {:total (+ (reduce + xs) z)}})}}]] @@ -117,7 +123,10 @@ :description "kosh"}}} :responses {200 {:description "success" :body {:total s/Int}} - 500 {:description "fail"}} + 500 {:description "fail"} + 504 {:description "default" + :content {:default {:schema {:error s/Str}}} + :body {:masked s/Str}}} :handler (fn [{{{:keys [z]} :path xs :body} :parameters}] {:status 200, :body {:total (+ (reduce + xs) z)}})}}]]] @@ -193,7 +202,11 @@ :type "object"}}}} 400 {:content {"application/json" {:schema {:type "string"}}} :description "kosh"} - 500 {:description "fail"}} + 500 {:description "fail"} + 504 {:description "default" + :content {"application/json" {:schema {:properties {"error" {:type "string"}} + :required ["error"] + :type "object"}}}}} :summary "plus with body"}} "/api/malli/plus/{z}" {:get {:parameters [{:in "query" :name :x @@ -231,7 +244,12 @@ :type "object"}}}} 400 {:description "kosh" :content {"application/json" {:schema {:type "string"}}}} - 500 {:description "fail"}} + 500 {:description "fail"} + 504 {:description "default" + :content {"application/json" {:schema {:additionalProperties false + :properties {:error {:type "string"}} + :required [:error] + :type "object"}}}}} :summary "plus with body"}} "/api/schema/plus/{z}" {:get {:parameters [{:description "" :in "query" @@ -280,10 +298,15 @@ :type "object"}}}} 400 {:description "kosh" :content {"application/json" {:schema {:type "string"}}}} - 500 {:description "fail"}} + 500 {:description "fail"} + 504 {:description "default" + :content {"application/json" {:schema {:additionalProperties false + :properties {"error" {:type "string"}} + :required ["error"] + :type "object"}}}}} :summary "plus with body"}}}}] (is (= expected spec)) - (is (nil? (validate spec)))))) + (is (= nil (validate spec)))))) (defn spec-paths [app uri] (-> {:request-method :get, :uri uri} app :body :paths keys)) From adef7ad06ee453e5c203da025dbec9c01f31fef9 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Wed, 23 Aug 2023 16:43:34 +0300 Subject: [PATCH 11/12] read openapi metadata into openapi description --- modules/reitit-core/src/reitit/coercion.cljc | 4 ++ .../src/reitit/coercion/malli.cljc | 32 ++++++++---- test/cljc/reitit/openapi_test.clj | 50 +++++++++++++++++++ 3 files changed, 75 insertions(+), 11 deletions(-) diff --git a/modules/reitit-core/src/reitit/coercion.cljc b/modules/reitit-core/src/reitit/coercion.cljc index f3b58053..ca8cf0fd 100644 --- a/modules/reitit-core/src/reitit/coercion.cljc +++ b/modules/reitit-core/src/reitit/coercion.cljc @@ -105,6 +105,10 @@ (or (-> request-or-response :content :default :schema) (:body request-or-response))) +(defn get-default [request-or-response] + (or (-> request-or-response :content :default) + (some->> request-or-response :body (assoc {} :schema)))) + (defn content-request-coercer [coercion {:keys [content body]} {::keys [extract-request-format serialize-failed-result] :or {extract-request-format extract-request-format-default}}] (when coercion diff --git a/modules/reitit-malli/src/reitit/coercion/malli.cljc b/modules/reitit-malli/src/reitit/coercion/malli.cljc index 4a4dc9ea..e4f3ab2d 100644 --- a/modules/reitit-malli/src/reitit/coercion/malli.cljc +++ b/modules/reitit-malli/src/reitit/coercion/malli.cljc @@ -133,13 +133,22 @@ ;; malli options :options nil}) +;; TODO: this is now seems like a generic transforming function that could be used in all of malli, spec, schema +;; ... just tranform the schemas in place +;; also, this has internally massive amount of duplicate code, could be simplified +;; ... tests too (defn -get-apidocs-openapi [_ {:keys [request parameters responses content-types] :or {content-types ["application/json"]}} options] (let [{:keys [body multipart]} parameters parameters (dissoc parameters :request :body :multipart) ->schema-object (fn [schema opts] (let [current-opts (merge options opts)] - (json-schema/transform schema current-opts)))] + (json-schema/transform schema current-opts))) + ->content (fn [data schema] + (merge + {:schema schema} + (select-keys data [:description :examples]) + (:openapi data)))] (merge (when (seq parameters) {:parameters @@ -168,20 +177,21 @@ ;; request allow to different :requestBody per content-type {:requestBody {:content (merge - (when-let [default (coercion/get-default-schema request)] + (select-keys request [:description]) + (when-let [{:keys [schema] :as data} (coercion/get-default request)] (into {} (map (fn [content-type] - (let [schema (->schema-object default {:in :requestBody - :type :schema - :content-type content-type})] - [content-type {:schema schema}]))) + (let [schema (->schema-object schema {:in :requestBody + :type :schema + :content-type content-type})] + [content-type (->content data schema)]))) content-types)) (into {} - (map (fn [[content-type {:keys [schema]}]] + (map (fn [[content-type {:keys [schema] :as data}]] (let [schema (->schema-object schema {:in :requestBody :type :schema :content-type content-type})] - [content-type {:schema schema}]))) + [content-type (->content data schema)]))) (:content request)))}}) (when multipart {:requestBody @@ -203,15 +213,15 @@ (let [schema (->schema-object default {:in :responses :type :schema :content-type content-type})] - [content-type {:schema schema}]))) + [content-type (->content nil schema)]))) content-types)) (when content (into {} - (map (fn [[content-type {:keys [schema]}]] + (map (fn [[content-type {:keys [schema] :as data}]] (let [schema (->schema-object schema {:in :responses :type :schema :content-type content-type})] - [content-type {:schema schema}]))) + [content-type (->content data schema)]))) content))) (dissoc :default))] [status (merge (select-keys response [:description]) diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index 564b1a6d..f147c407 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -774,3 +774,53 @@ spec)) (testing "spec is valid" (is (nil? (validate spec)))))) + +(deftest openapi-malli-tests + (let [app (ring/ring-handler + (ring/router + [["/openapi.json" + {:get {:no-doc true + :handler (openapi/create-openapi-handler)}}] + + ["/malli" {:coercion malli/coercion} + ["/plus" {:post {:summary "plus with body" + :request {:description "body description" + :content {"application/json" {:schema {:x int?, :y int?} + :examples {"1+1" {:x 1, :y 1} + "1+2" {:x 1, :y 2}} + :openapi {:example {:x 2, :y 2}}}}} + :responses {200 {:description "success" + :content {"application/json" {:schema {:total int?} + :examples {"2" {:total 2} + "3" {:total 3}} + :openapi {:example {:total 4}}}}}} + :handler (fn [request] + (let [{:keys [x y]} (-> request :parameters :body)] + {:status 200, :body {:total (+ x y)}}))}}]]] + + {:validate reitit.ring.spec/validate + :data {:middleware [openapi/openapi-feature + rrc/coerce-exceptions-middleware + rrc/coerce-request-middleware + rrc/coerce-response-middleware]}}))] + (is (= {"/malli/plus" {:post {:requestBody {:content {:description "body description", + "application/json" {:schema {:type "object", + :properties {:x {:type "integer"}, + :y {:type "integer"}}, + :required [:x :y], + :additionalProperties false}, + :examples {"1+1" {:x 1, :y 1}, "1+2" {:x 1, :y 2}}, + :example {:x 2, :y 2}}}}, + :responses {200 {:description "success", + :content {"application/json" {:schema {:type "object", + :properties {:total {:type "integer"}}, + :required [:total], + :additionalProperties false}, + :examples {"2" {:total 2}, "3" {:total 3}}, + :example {:total 4}}}}}, + :summary "plus with body"}}}) + (-> {:request-method :get + :uri "/openapi.json"} + (app) + :body + :paths)))) From 05cbed815fcd5aa8cbbc6d47724477a5efeb795a Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Thu, 24 Aug 2023 08:38:18 +0300 Subject: [PATCH 12/12] review comment fixes --- modules/reitit-core/src/reitit/coercion.cljc | 6 +++--- modules/reitit-spec/src/reitit/coercion/spec.cljc | 3 --- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/modules/reitit-core/src/reitit/coercion.cljc b/modules/reitit-core/src/reitit/coercion.cljc index ca8cf0fd..75b78d79 100644 --- a/modules/reitit-core/src/reitit/coercion.cljc +++ b/modules/reitit-core/src/reitit/coercion.cljc @@ -176,9 +176,9 @@ (some->> (for [[k v] parameters, :when v] [k (request-coercer coercion k v opts)]) (filter second) (seq) (into {}))) - ([coercion parameters request opts] - (let [crc (when request (some->> (content-request-coercer coercion request opts) (array-map :request))) - rcs (request-coercers coercion parameters (cond-> opts request (assoc ::skip #{:body})))] + ([coercion parameters route-request opts] + (let [crc (when route-request (some->> (content-request-coercer coercion route-request opts) (array-map :request))) + rcs (request-coercers coercion parameters (cond-> opts route-request (assoc ::skip #{:body})))] (if (and crc rcs) (into crc (vec rcs)) (or crc rcs))))) (defn response-coercers [coercion responses opts] diff --git a/modules/reitit-spec/src/reitit/coercion/spec.cljc b/modules/reitit-spec/src/reitit/coercion/spec.cljc index b988d140..98ff9e37 100644 --- a/modules/reitit-spec/src/reitit/coercion/spec.cljc +++ b/modules/reitit-spec/src/reitit/coercion/spec.cljc @@ -83,9 +83,6 @@ :string {:default string-transformer} :response {:default no-op-transformer}}}) -(defn get-request-default-body [request] - (or (-> request :content :default :schema) (:body request))) - (defn create [{:keys [transformers coerce-response?] :as opts}] ^{:type ::coercion/coercion} (reify coercion/Coercion