diff --git a/modules/reitit-malli/src/reitit/coercion/malli.cljc b/modules/reitit-malli/src/reitit/coercion/malli.cljc index 5d4a863a..9b66f8fa 100644 --- a/modules/reitit-malli/src/reitit/coercion/malli.cljc +++ b/modules/reitit-malli/src/reitit/coercion/malli.cljc @@ -141,7 +141,6 @@ (let [current-opts (merge options opts)] (json-schema/transform (coercion/-compile-model coercion schema current-opts) current-opts)))] - (merge (when (seq parameters) {:parameters @@ -165,17 +164,26 @@ :content-type content-type})] [content-type {:schema schema}]))) content-types)}}) + (when request ;; request allow to different :requestBody per content-type {:requestBody - {:content - (into {} - (map (fn [[content-type requestBody]] - (let [schema (->schema-object requestBody {:in :requestBody - :type :schema - :content-type content-type})] - [content-type {:schema schema}]))) - (:content request))}}) + {:content (merge + (when (:body request) + (into {} + (map (fn [content-type] + (let [schema (->schema-object (:body request) {:in :requestBody + :type :schema + :content-type content-type})] + [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})] + [content-type {:schema schema}]))) + (:content request)))}}) (when responses {:responses (into {} diff --git a/modules/reitit-schema/src/reitit/coercion/schema.cljc b/modules/reitit-schema/src/reitit/coercion/schema.cljc index c9f22a0f..07dc5ce6 100644 --- a/modules/reitit-schema/src/reitit/coercion/schema.cljc +++ b/modules/reitit-schema/src/reitit/coercion/schema.cljc @@ -47,8 +47,9 @@ (reify coercion/Coercion (-get-name [_] :schema) (-get-options [_] opts) - (-get-apidocs [this specification {:keys [parameters responses]}] - ;; TODO: this looks identical to spec, refactor when schema is done. + (-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. (case specification :swagger (swagger/swagger-spec (merge @@ -76,24 +77,28 @@ (for [[k v] (dissoc parameters :body :request)] [k (coercion/-compile-model this v nil)]))})) (when (:body parameters) - {:requestBody (openapi/openapi-spec - {::openapi/content {"application/json" (:body parameters)}})}) + {:requestBody (openapi/openapi-spec + {::openapi/content (zipmap content-types (repeat (:body parameters)))})}) (when (:request parameters) - {:requestBody (openapi/openapi-spec - {::openapi/content (:content (: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 responses {:responses (into (empty responses) - (for [[k response] responses] + (for [[k {:keys [body content] :as response}] responses] [k (merge (select-keys response [:description]) - (when (:body response) + (when (or body content) (openapi/openapi-spec - {::openapi/content {"application/json" (coercion/-compile-model this (:body response) nil)}})) - (when (:content response) - (openapi/openapi-spec - {::openapi/content (:content response)})))]))})) + {::openapi/content (merge + (when body + (zipmap content-types (repeat (coercion/-compile-model this body nil)))) + (when response + (:content response)))})))]))})) (throw (ex-info diff --git a/modules/reitit-spec/src/reitit/coercion/spec.cljc b/modules/reitit-spec/src/reitit/coercion/spec.cljc index 26647fb3..31050a01 100644 --- a/modules/reitit-spec/src/reitit/coercion/spec.cljc +++ b/modules/reitit-spec/src/reitit/coercion/spec.cljc @@ -86,7 +86,8 @@ (reify coercion/Coercion (-get-name [_] :spec) (-get-options [_] opts) - (-get-apidocs [this specification {:keys [parameters responses]}] + (-get-apidocs [this specification {:keys [parameters responses content-types] + :or {content-types ["application/json"]}}] (case specification :swagger (swagger/swagger-spec (merge @@ -114,28 +115,32 @@ (for [[k v] (dissoc parameters :body :request)] [k (coercion/-compile-model this v nil)]))}) (when (:body parameters) - {:requestBody (openapi/openapi-spec - {::openapi/content {"application/json" (coercion/-compile-model this (:body parameters) nil)}})}) + {:requestBody (openapi/openapi-spec + {::openapi/content (zipmap content-types (repeat (coercion/-compile-model this (:body parameters) nil)))})}) (when (:request parameters) - {:requestBody (openapi/openapi-spec - {::openapi/content (into {} + {:requestBody (openapi/openapi-spec + {::openapi/content (merge + (when-let [default (get-in parameters [:request :body])] + (zipmap content-types (repeat (coercion/-compile-model this default nil)))) + (into {} (for [[format model] (:content (:request parameters))] - [format (coercion/-compile-model this model nil)]))})}) + [format (coercion/-compile-model this model nil)])))})}) (when responses {:responses (into (empty responses) - (for [[k response] responses] + (for [[k {:keys [body content] :as response}] responses] [k (merge (select-keys response [:description]) - (when (:body response) + (when (or body content) (openapi/openapi-spec - {::openapi/content {"application/json" (coercion/-compile-model this (:body response) nil)}})) - (when (:content response) - (openapi/openapi-spec - {::openapi/content (into {} - (for [[format model] (:content response)] - [format (coercion/-compile-model this model nil)]))})))]))}))) + {::openapi/content (merge + (when body + (zipmap content-types (repeat (coercion/-compile-model this (:body response) nil)))) + (when response + (into {} + (for [[format model] (:content response)] + [format (coercion/-compile-model this model nil)]))))})))]))}))) (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 dab5f798..0f999a01 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -2,6 +2,7 @@ (:require [clojure.test :refer [deftest is testing]] [jsonista.core :as j] [matcher-combinators.test :refer [match?]] + [matcher-combinators.matchers :as matchers] [muuntaja.core :as m] [reitit.coercion.malli :as malli] [reitit.coercion.schema :as schema] @@ -457,3 +458,43 @@ (app (assoc query :body-params {:z 1})) (catch clojure.lang.ExceptionInfo e (select-keys (ex-data e) [:type :in])))))))))))) + +(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 + (doseq [content-type ["application/json" "application/edn"]] + (testing (str "default content type " content-type) + (let [app (ring/ring-handler + (ring/router + [["/parameters" + {:post {:coercion @coercion + :content-types [content-type] ;; TODO should this be under :openapi ? + :parameters {:request {:content {"application/transit" (->schema :transit)} + :body (->schema :default)}} + :responses {200 {:content {"application/transit" (->schema :transit)} + :body (->schema :default)}} + :handler (fn [req] + {:status 200 + :body (-> req :parameters :request)})}}] + ["/openapi.json" + {:get {:handler (openapi/create-openapi-handler) + :no-doc true}}]] + {:data {:middleware [rrc/coerce-request-middleware + rrc/coerce-response-middleware]}})) + spec (-> {:request-method :get + :uri "/openapi.json"} + app + :body)] + (testing "body parameter" + (is (match? (matchers/in-any-order [content-type "application/transit"]) + (-> spec + (get-in [:paths "/parameters" :post :requestBody :content]) + keys)))) + (testing "body response" + (is (match? (matchers/in-any-order [content-type "application/transit"]) + (-> spec + (get-in [:paths "/parameters" :post :responses 200 :content]) + keys))))))))))