diff --git a/modules/reitit-core/src/reitit/coercion.cljc b/modules/reitit-core/src/reitit/coercion.cljc index f3b580532..ca8cf0fd8 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 4a4dc9eab..e4f3ab2d3 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 564b1a6d6..f147c4072 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))))