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}}))