diff --git a/modules/reitit-openapi/src/reitit/openapi.clj b/modules/reitit-openapi/src/reitit/openapi.clj index 497b811e..aac112a2 100644 --- a/modules/reitit-openapi/src/reitit/openapi.clj +++ b/modules/reitit-openapi/src/reitit/openapi.clj @@ -125,23 +125,24 @@ (when request ;; request allow to different :requestBody per content-type {:requestBody - {:content (merge - (select-keys request [:description]) - (when-let [{:keys [schema] :as data} (coercion/get-default request)] - (into {} - (map (fn [content-type] - (let [schema (->schema-object schema {:in :requestBody - :type :schema - :content-type content-type})] - [content-type (->content data schema)]))) - request-content-types)) - (into {} - (map (fn [[content-type {:keys [schema] :as data}]] - (let [schema (->schema-object schema {:in :requestBody - :type :schema - :content-type content-type})] - [content-type (->content data schema)]))) - (dissoc (:content request) :default)))}}) + (merge + (select-keys request [:description]) + {:content (merge + (when-let [{:keys [schema] :as data} (coercion/get-default request)] + (into {} + (map (fn [content-type] + (let [schema (->schema-object schema {:in :requestBody + :type :schema + :content-type content-type})] + [content-type (->content data schema)]))) + request-content-types)) + (into {} + (map (fn [[content-type {:keys [schema] :as data}]] + (let [schema (->schema-object schema {:in :requestBody + :type :schema + :content-type content-type})] + [content-type (->content data schema)]))) + (dissoc (:content request) :default)))})}) (when multipart {:requestBody {:content diff --git a/package-lock.json b/package-lock.json index 853c0ce9..a77a7f95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "reitit", "devDependencies": { - "@seriousme/openapi-schema-validator": "^2.1.0", + "@seriousme/openapi-schema-validator": "^2.2.1", "karma": "^4.1.0", "karma-chrome-launcher": "^2.2.0", "karma-cli": "^2.0.0", @@ -15,20 +15,31 @@ } }, "node_modules/@seriousme/openapi-schema-validator": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@seriousme/openapi-schema-validator/-/openapi-schema-validator-2.1.0.tgz", - "integrity": "sha512-J461zq7Qj4N/SQlUiyXFelGqtKJenW9DnTjX5fraLk9Lmybq7B6goBieAlMf3D2W+grrVz/hSDodB0faoD9y2Q==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@seriousme/openapi-schema-validator/-/openapi-schema-validator-2.2.1.tgz", + "integrity": "sha512-I+6l2vZ4qx+RyUo8GNnIbeqbv5ao1enSdNFPJ7x3slIVLU8aSBf228qpo+6iNWbRMK4ktF91jsv5KN7PpaJQtg==", "dev": true, "dependencies": { - "ajv": "^8.11.0", + "ajv": "^8.12.0", "ajv-draft-04": "^1.0.0", "ajv-formats": "^2.1.1", - "js-yaml": "^4.1.0" + "js-yaml": "^4.1.0", + "minimist": "^1.2.8" }, "bin": { + "bundle-api": "bin/bundle-api-cli.js", "validate-api": "bin/validate-api-cli.js" } }, + "node_modules/@seriousme/openapi-schema-validator/node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", diff --git a/package.json b/package.json index aa7c5d4a..a9fb84d3 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "reitit", "private": true, "devDependencies": { - "@seriousme/openapi-schema-validator": "^2.1.0", + "@seriousme/openapi-schema-validator": "^2.2.1", "karma": "^4.1.0", "karma-chrome-launcher": "^2.2.0", "karma-cli": "^2.0.0", diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index 8d77dd4b..51247337 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -869,19 +869,20 @@ (ring/router [["/openapi.json" {:get {:no-doc true + :openapi {:info {:title "" :version "0.0.1"}} :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}} + :examples {"1+1" {:value {:x 1, :y 1}} + "1+2" {:value {: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}} + :examples {"2" {:value {:total 2}} + "3" {:value {:total 3}}} :openapi {:example {:total 4}}}}}} :handler (fn [request] (let [{:keys [x y]} (-> request :parameters :body)] @@ -891,49 +892,51 @@ :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", + rrc/coerce-response-middleware]}})) + spec (:body (app {:request-method :get :uri "/openapi.json"}))] + (is (= {"/malli/plus" {:post {:requestBody {:description "body description", + :content {"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}}, + :examples {"1+1" {:value {:x 1, :y 1}} + "1+2" {:value {: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}}, + :examples {"2" {:value {:total 2}}, + "3" {:value {:total 3}}}, :example {:total 4}}}}}, :summary "plus with body"}}} - (-> {:request-method :get - :uri "/openapi.json"} - (app) - :body - :paths)))) + (:paths spec))) + (is (nil? (validate spec)))) (testing "ref schemas" (let [registry (merge (mc/base-schemas) (mc/type-schemas) - {::plus [:map [:x :int] [:y ::y]] - ::y :int}) + {"plus" [:map [:x :int] [:y "y"]] + "y" :int}) app (ring/ring-handler (ring/router [["/openapi.json" {:get {:no-doc true + :openapi {:info {:title "" :version "0.0.1"}} :handler (openapi/create-openapi-handler)}}] ["/post" {:post {:coercion malli/coercion - :parameters {:body (mc/schema ::plus {:registry registry})} + :parameters {:body (mc/schema "plus" {:registry registry})} :handler identity}}] ["/get" {:get {:coercion malli/coercion - :parameters {:query (mc/schema ::plus {:registry registry})} + :parameters {:query (mc/schema "plus" {:registry registry})} :handler identity}}]])) spec (:body (app {:request-method :get :uri "/openapi.json"}))] (is (= {:openapi "3.1.0" :x-id #{:reitit.openapi/default} + :info {:title "" :version "0.0.1"} :paths {"/get" {:get {:parameters [{:in "query" :name :x :required true @@ -941,25 +944,27 @@ {:in "query" :name :y :required true - :schema {:$ref "#/components/schemas/reitit.openapi-test~1y"}}]}} + :schema {:$ref "#/components/schemas/y"}}]}} "/post" {:post {:requestBody {:content {"application/json" {:schema - {:$ref "#/components/schemas/reitit.openapi-test~1plus"}}}}}}} + {:$ref "#/components/schemas/plus"}}}}}}} :components {:schemas - {"reitit.openapi-test/y" {:type "integer"} - "reitit.openapi-test/plus" {:type "object" - :properties {:x {:type "integer"} - :y {:$ref "#/components/schemas/reitit.openapi-test~1y"}} - :required [:x :y]}}}} - spec)))) + {"y" {:type "integer"} + "plus" {:type "object" + :properties {:x {:type "integer"} + :y {:$ref "#/components/schemas/y"}} + :required [:x :y]}}}} + spec)) + (is (nil? (validate spec))))) (testing "var schemas" (let [app (ring/ring-handler (ring/router [["/openapi.json" {:get {:no-doc true + :openapi {:info {:title "" :version "0.0.1"}} :handler (openapi/create-openapi-handler)}}] ["/post" {:post {:coercion malli/coercion @@ -972,6 +977,7 @@ spec (:body (app {:request-method :get :uri "/openapi.json"}))] (is (= {:openapi "3.1.0" :x-id #{:reitit.openapi/default} + :info {:title "" :version "0.0.1"} :paths {"/post" {:post @@ -999,4 +1005,9 @@ :y {:$ref "#/components/schemas/reitit.openapi-test~1Y"}} :required [:x :y]} "reitit.openapi-test/Y" {:type "integer"}}}} - spec))))) + spec)) + ;; TODO: the OAS 3.1 json schema disallows "/" in :components :schemas keys, + ;; even though the text of the spec allows it. See: + ;; https://github.com/seriousme/openapi-schema-validator/blob/772375bf4895f0e641d103c27140cdd1d2afc34e/schemas/v3.1/schema.json#L282 + #_ + (is (nil? (validate spec))))))