From f322597c04375e0d0032310e9d99abfc6eaf96c2 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Wed, 15 Mar 2023 10:22:26 +0200 Subject: [PATCH 01/14] feat: openapi3 multipart support for spec --- .../reitit/http/interceptors/multipart.clj | 8 +++-- .../reitit-spec/src/reitit/coercion/spec.cljc | 8 ++++- test/cljc/reitit/openapi_test.clj | 34 +++++++++++++++++++ 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/modules/reitit-interceptors/src/reitit/http/interceptors/multipart.clj b/modules/reitit-interceptors/src/reitit/http/interceptors/multipart.clj index d616c689..4a2d4ba1 100644 --- a/modules/reitit-interceptors/src/reitit/http/interceptors/multipart.clj +++ b/modules/reitit-interceptors/src/reitit/http/interceptors/multipart.clj @@ -19,13 +19,17 @@ "Spec for file param created by ring.middleware.multipart-params.temp-file store." (st/spec {:spec (s/keys :req-un [::filename ::content-type ::tempfile ::size]) - :swagger/type "file"})) + :swagger/type "file" + :openapi {:type "string" + :format "binary"}})) (def bytes-part "Spec for file param created by ring.middleware.multipart-params.byte-array store." (st/spec {:spec (s/keys :req-un [::filename ::content-type ::bytes]) - :swagger/type "file"})) + :swagger/type "file" + :openapi {:type "string" + :format "binary"}})) (defn- coerced-request [request coercers] (if-let [coerced (if coercers (coercion/coerce-request coercers request))] diff --git a/modules/reitit-spec/src/reitit/coercion/spec.cljc b/modules/reitit-spec/src/reitit/coercion/spec.cljc index d453dd61..d5edd78b 100644 --- a/modules/reitit-spec/src/reitit/coercion/spec.cljc +++ b/modules/reitit-spec/src/reitit/coercion/spec.cljc @@ -108,7 +108,7 @@ (update $ :schema #(coercion/-compile-model this % nil)) $))]))}))) :openapi (merge - (when (seq (dissoc parameters :body :request)) + (when (seq (dissoc parameters :body :request :multipart)) (openapi/openapi-spec {::openapi/parameters (into (empty parameters) (for [[k v] (dissoc parameters :body :request)] @@ -124,6 +124,12 @@ (into {} (for [[format model] (:content (:request parameters))] [format (coercion/-compile-model this model nil)])))})}) + (when (:multipart parameters) + {:requestBody + (openapi/openapi-spec + {::openapi/content + {"multipart/form-data" + (coercion/-compile-model this (:multipart parameters) nil)}})}) (when responses {:responses (into diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index 3db5533f..0216ac8e 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -8,6 +8,7 @@ [reitit.coercion.malli :as malli] [reitit.coercion.schema :as schema] [reitit.coercion.spec :as spec] + [reitit.http.interceptors.multipart] [reitit.openapi :as openapi] [reitit.ring :as ring] [reitit.ring.spec] @@ -429,6 +430,39 @@ (testing "spec is valid" (is (nil? (validate spec)))))))) +(deftest multipart-test + (doseq [[coercion file-schema] + [#_[#'malli/coercion (fn [nom] [:map [nom :string]])] + #_[#'schema/coercion (fn [nom] {nom s/Str})] + [#'spec/coercion reitit.http.interceptors.multipart/bytes-part]]] + (testing coercion + (let [app (ring/ring-handler + (ring/router + [["/upload" + {:post {:decription "upload" + :coercion @coercion + :parameters {:multipart {:file file-schema}} + :handler identity}}] + ["/openapi.json" + {:get {:handler (openapi/create-openapi-handler) + :openapi {:info {:title "" :version "0.0.1"}} + :no-doc true}}]] + {:data {:middleware [openapi/openapi-feature]}})) + spec (-> {:request-method :get + :uri "/openapi.json"} + app + :body)] + (testing "multipart body" + (is (= {:requestBody {:content {"multipart/form-data" {:schema {:type "object" + :properties {"file" {:type "string" + :format "binary"}} + :required ["file"]}}}}} + (-> spec + (get-in [:paths "/upload" :post]) + #_normalize)))) + (testing "spec is valid" + (is (nil? (validate spec)))))))) + (deftest per-content-type-test (doseq [[coercion ->schema] [[#'malli/coercion (fn [nom] [:map [nom :string]])] From 1c65f533cfaacf931c6757599ac0aa3ca13219ca Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Wed, 15 Mar 2023 15:48:53 +0200 Subject: [PATCH 02/14] feat: openapi3 multipart support for malli --- .../src/reitit/coercion/malli.cljc | 12 +++++++++-- test/cljc/reitit/openapi_test.clj | 21 ++++++++++++------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/modules/reitit-malli/src/reitit/coercion/malli.cljc b/modules/reitit-malli/src/reitit/coercion/malli.cljc index 9b66f8fa..99226230 100644 --- a/modules/reitit-malli/src/reitit/coercion/malli.cljc +++ b/modules/reitit-malli/src/reitit/coercion/malli.cljc @@ -135,8 +135,8 @@ (defn -get-apidocs-openapi [coercion {:keys [parameters responses content-types] :or {content-types ["application/json"]}} options] - (let [{:keys [body request]} parameters - parameters (dissoc parameters :request :body) + (let [{:keys [body request multipart]} parameters + parameters (dissoc parameters :request :body :multipart) ->schema-object (fn [schema opts] (let [current-opts (merge options opts)] (json-schema/transform (coercion/-compile-model coercion schema current-opts) @@ -184,6 +184,14 @@ :content-type content-type})] [content-type {:schema schema}]))) (:content request)))}}) + (when multipart + {:requestBody + {:content + {"multipart/form-data" + {:schema + (->schema-object multipart {:in :requestBody + :type :schema + :content-type "multipart/form-data"})}}}}) (when responses {:responses (into {} diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index 0216ac8e..3f789a47 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -432,7 +432,11 @@ (deftest multipart-test (doseq [[coercion file-schema] - [#_[#'malli/coercion (fn [nom] [:map [nom :string]])] + [[#'malli/coercion [:map {:json-schema {:type "string" + :format "binary"}} + [:filename :string] + [:content-type :string] + [:bytes :int]]] #_[#'schema/coercion (fn [nom] {nom s/Str})] [#'spec/coercion reitit.http.interceptors.multipart/bytes-part]]] (testing coercion @@ -453,13 +457,16 @@ app :body)] (testing "multipart body" - (is (= {:requestBody {:content {"multipart/form-data" {:schema {:type "object" - :properties {"file" {:type "string" - :format "binary"}} - :required ["file"]}}}}} + (is (nil? (get-in spec [:paths "/upload" :post :parameters]))) + (is (= (merge {:type "object" + :properties {:file {:type "string" + :format "binary"}} + :required ["file"]} + (when-not (= #'spec/coercion coercion) + {:additionalProperties false})) (-> spec - (get-in [:paths "/upload" :post]) - #_normalize)))) + (get-in [:paths "/upload" :post :requestBody :content "multipart/form-data" :schema]) + normalize)))) (testing "spec is valid" (is (nil? (validate spec)))))))) From 60f53d7e7cdcab77430782a8341e78aac7c6afb0 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Wed, 15 Mar 2023 17:32:49 +0200 Subject: [PATCH 03/14] deps: upgrade schema-tools --- project.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project.clj b/project.clj index 72936964..eebcf7d9 100644 --- a/project.clj +++ b/project.clj @@ -33,7 +33,7 @@ [metosin/reitit-pedestal "0.6.0"] [metosin/ring-swagger-ui "4.15.5"] [metosin/spec-tools "0.10.5"] - [metosin/schema-tools "0.12.3"] + [metosin/schema-tools "0.13.0"] [metosin/muuntaja "0.6.8"] [metosin/jsonista "0.3.7"] [metosin/sieppari "0.0.0-alpha13"] @@ -86,7 +86,7 @@ [org.clojure/clojurescript "1.10.773"] ;; modules dependencies - [metosin/schema-tools "0.12.3"] + [metosin/schema-tools "0.13.0"] [metosin/spec-tools "0.10.5"] [metosin/muuntaja "0.6.8"] [metosin/sieppari "0.0.0-alpha13"] From acbcec1ed902f11a6d07595a2a531b6d7b0829ef Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Wed, 15 Mar 2023 14:22:21 +0200 Subject: [PATCH 04/14] feat: openapi3 multipart support for schema --- modules/reitit-schema/src/reitit/coercion/schema.cljc | 6 +++++- test/cljc/reitit/openapi_test.clj | 7 ++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/modules/reitit-schema/src/reitit/coercion/schema.cljc b/modules/reitit-schema/src/reitit/coercion/schema.cljc index 07dc5ce6..ce66e153 100644 --- a/modules/reitit-schema/src/reitit/coercion/schema.cljc +++ b/modules/reitit-schema/src/reitit/coercion/schema.cljc @@ -70,7 +70,7 @@ (update $ :schema #(coercion/-compile-model this % nil)) $))]))}))) :openapi (merge - (when (seq (dissoc parameters :body :request)) + (when (seq (dissoc parameters :body :request :multipart)) (openapi/openapi-spec {::openapi/parameters (into (empty parameters) @@ -85,6 +85,10 @@ (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 diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index 3f789a47..80d18f8b 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -15,6 +15,7 @@ [reitit.ring.coercion :as rrc] [reitit.swagger-ui :as swagger-ui] [schema.core :as s] + [schema-tools.core] [spec-tools.data-spec :as ds])) (defn validate @@ -437,7 +438,11 @@ [:filename :string] [:content-type :string] [:bytes :int]]] - #_[#'schema/coercion (fn [nom] {nom s/Str})] + [#'schema/coercion (schema-tools.core/schema {:filename s/Str + :content-type s/Str + :bytes s/Num} + {:openapi {:type "string" + :format "binary"}})] [#'spec/coercion reitit.http.interceptors.multipart/bytes-part]]] (testing coercion (let [app (ring/ring-handler From de2d810b7cae7f4fe062d5f6817139b127a2f006 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Wed, 15 Mar 2023 16:51:01 +0200 Subject: [PATCH 05/14] test: multiple parts in multipart-test --- test/cljc/reitit/openapi_test.clj | 38 +++++++++++++++++++------------ 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index 80d18f8b..7e9bfcda 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -432,25 +432,32 @@ (is (nil? (validate spec)))))))) (deftest multipart-test - (doseq [[coercion file-schema] - [[#'malli/coercion [:map {:json-schema {:type "string" - :format "binary"}} - [:filename :string] - [:content-type :string] - [:bytes :int]]] - [#'schema/coercion (schema-tools.core/schema {:filename s/Str - :content-type s/Str - :bytes s/Num} - {:openapi {:type "string" - :format "binary"}})] - [#'spec/coercion reitit.http.interceptors.multipart/bytes-part]]] + (doseq [[coercion file-schema string-schema] + [[#'malli/coercion + [:map {:json-schema {:type "string" + :format "binary"}} + [:filename :string] + [:content-type :string] + [:bytes :int]] + :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 coercion (let [app (ring/ring-handler (ring/router [["/upload" {:post {:decription "upload" :coercion @coercion - :parameters {:multipart {:file file-schema}} + :parameters {:multipart {:file file-schema + :more string-schema}} :handler identity}}] ["/openapi.json" {:get {:handler (openapi/create-openapi-handler) @@ -465,8 +472,9 @@ (is (nil? (get-in spec [:paths "/upload" :post :parameters]))) (is (= (merge {:type "object" :properties {:file {:type "string" - :format "binary"}} - :required ["file"]} + :format "binary"} + :more {:type "string"}} + :required ["file" "more"]} (when-not (= #'spec/coercion coercion) {:additionalProperties false})) (-> spec From 83b747c7c66fd3d72d00d8c1a6d3596622c769f6 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Thu, 16 Mar 2023 09:39:34 +0200 Subject: [PATCH 06/14] doc: mention :multipart in doc/ring/coercion.md --- doc/ring/coercion.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/doc/ring/coercion.md b/doc/ring/coercion.md index 930c10df..8de57c1a 100644 --- a/doc/ring/coercion.md +++ b/doc/ring/coercion.md @@ -4,14 +4,15 @@ Basic coercion is explained in detail [in the Coercion Guide](../coercion/coerci The following request parameters are currently supported: -| type | request source | -|------------|--------------------------------------------------| -| `:query` | `:query-params` | -| `:body` | `:body-params` | -| `:request` | `:body-params`, allows per-content-type coercion | -| `:form` | `:form-params` | -| `:header` | `:header-params` | -| `:path` | `:path-params` | +| type | request source | +|--------------|--------------------------------------------------| +| `:query` | `:query-params` | +| `:body` | `:body-params` | +| `:request` | `:body-params`, allows per-content-type coercion | +| `:form` | `:form-params` | +| `:header` | `:header-params` | +| `:path` | `:path-params` | +| `:multipart` | `:multipart-params`, only properly supports `clojure.spec`, see [Default Middleware](default_middleware.md) | To enable coercion, the following things need to be done: From b5c9ee274d41842c22b4c101bd2505bfc1ce588f Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Thu, 16 Mar 2023 09:53:38 +0200 Subject: [PATCH 07/14] feat: generate correct openapi for reitit.ring.malli --- modules/reitit-malli/src/reitit/ring/malli.cljc | 6 ++++-- test/cljc/reitit/openapi_test.clj | 7 ++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/modules/reitit-malli/src/reitit/ring/malli.cljc b/modules/reitit-malli/src/reitit/ring/malli.cljc index a7987625..1ccc1959 100644 --- a/modules/reitit-malli/src/reitit/ring/malli.cljc +++ b/modules/reitit-malli/src/reitit/ring/malli.cljc @@ -4,7 +4,8 @@ #?(:clj (def temp-file-part "Schema for file param created by ring.middleware.multipart-params.temp-file store." - [:map {:json-schema {:type "file"}} + [:map {:json-schema {:type "string" + :format "binary"}} [:filename string?] [:content-type string?] [:size int?] @@ -13,7 +14,8 @@ #?(:clj (def bytes-part "Schema for file param created by ring.middleware.multipart-params.byte-array store." - [:map {:json-schema {:type "file"}} + [:map {:json-schema {:type "string" + :format "binary"}} [:filename string?] [:content-type string?] [:bytes bytes?]])) diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index 7e9bfcda..563125ed 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -11,6 +11,7 @@ [reitit.http.interceptors.multipart] [reitit.openapi :as openapi] [reitit.ring :as ring] + [reitit.ring.malli] [reitit.ring.spec] [reitit.ring.coercion :as rrc] [reitit.swagger-ui :as swagger-ui] @@ -434,11 +435,7 @@ (deftest multipart-test (doseq [[coercion file-schema string-schema] [[#'malli/coercion - [:map {:json-schema {:type "string" - :format "binary"}} - [:filename :string] - [:content-type :string] - [:bytes :int]] + reitit.ring.malli/bytes-part :string] [#'schema/coercion (schema-tools.core/schema {:filename s/Str From 224acf930e79f915905c9a00e9f5380b8319c233 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Thu, 16 Mar 2023 10:05:23 +0200 Subject: [PATCH 08/14] doc: openapi3 in examples/ring-malli-swagger --- examples/ring-malli-swagger/README.md | 6 +++++- examples/ring-malli-swagger/project.clj | 3 ++- .../ring-malli-swagger/src/example/server.clj | 18 ++++++++++++++++-- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/examples/ring-malli-swagger/README.md b/examples/ring-malli-swagger/README.md index 3a2144c2..6162591b 100644 --- a/examples/ring-malli-swagger/README.md +++ b/examples/ring-malli-swagger/README.md @@ -7,6 +7,10 @@ (start) ``` +- Swagger spec served at +- Openapi spec served at +- Swagger UI served at + To test the endpoints using [httpie](https://httpie.org/): ```bash @@ -20,4 +24,4 @@ http GET :3000/swagger.json ## License -Copyright © 2017-2019 Metosin Oy +Copyright © 2017-2023 Metosin Oy diff --git a/examples/ring-malli-swagger/project.clj b/examples/ring-malli-swagger/project.clj index 406e05c6..a2991dae 100644 --- a/examples/ring-malli-swagger/project.clj +++ b/examples/ring-malli-swagger/project.clj @@ -3,6 +3,7 @@ :dependencies [[org.clojure/clojure "1.10.0"] [metosin/jsonista "0.2.6"] [ring/ring-jetty-adapter "1.7.1"] - [metosin/reitit "0.6.0"]] + [metosin/reitit "0.6.0"] + [metosin/ring-swagger-ui "5.0.0-alpha.0"]] :repl-options {:init-ns example.server} :profiles {:dev {:dependencies [[ring/ring-mock "0.3.2"]]}}) diff --git a/examples/ring-malli-swagger/src/example/server.clj b/examples/ring-malli-swagger/src/example/server.clj index bc9c3861..1b3d880d 100644 --- a/examples/ring-malli-swagger/src/example/server.clj +++ b/examples/ring-malli-swagger/src/example/server.clj @@ -1,6 +1,7 @@ (ns example.server (:require [reitit.ring :as ring] [reitit.coercion.malli] + [reitit.openapi :as openapi] [reitit.ring.malli] [reitit.swagger :as swagger] [reitit.swagger-ui :as swagger-ui] @@ -24,10 +25,17 @@ [["/swagger.json" {:get {:no-doc true :swagger {:info {:title "my-api" - :description "with [malli](https://github.com/metosin/malli) and reitit-ring"} + :description "swagger docs with [malli](https://github.com/metosin/malli) and reitit-ring" + :version "0.0.1"} :tags [{:name "files", :description "file api"} {:name "math", :description "math api"}]} :handler (swagger/create-swagger-handler)}}] + ["/openapi.json" + {:get {:no-doc true + :openapi {:info {:title "my-api" + :description "openapi3 docs with [malli](https://github.com/metosin/malli) and reitit-ring" + :version "0.0.1"}} + :handler (openapi/create-openapi-handler)}}] ["/files" {:swagger {:tags ["files"]}} @@ -44,6 +52,8 @@ ["/download" {:get {:summary "downloads a file" :swagger {:produces ["image/png"]} + :responses {200 {:description "an image" + :content {"image/png" any?}}} :handler (fn [_] {:status 200 :headers {"Content-Type" "image/png"} @@ -96,8 +106,9 @@ ;; malli options :options nil}) :muuntaja m/instance - :middleware [;; swagger feature + :middleware [;; swagger & openapi swagger/swagger-feature + openapi/openapi-feature ;; query-params & form-params parameters/parameters-middleware ;; content-negotiation @@ -118,6 +129,9 @@ (swagger-ui/create-swagger-ui-handler {:path "/" :config {:validatorUrl nil + :urls [{:name "swagger", :url "swagger.json"} + {:name "openapi", :url "openapi.json"}] + :urls.primaryName "openapi" :operationsSorter "alpha"}}) (ring/create-default-handler)))) From 6c3db021636c43da8499361785b6dca682927c1d Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Thu, 16 Mar 2023 10:08:06 +0200 Subject: [PATCH 09/14] doc: openapi content type for file download in examples/http-swagger --- examples/http-swagger/src/example/server.clj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/http-swagger/src/example/server.clj b/examples/http-swagger/src/example/server.clj index c7251d9c..d0a9164e 100644 --- a/examples/http-swagger/src/example/server.clj +++ b/examples/http-swagger/src/example/server.clj @@ -76,6 +76,8 @@ ["/download" {:get {:summary "downloads a file" :swagger {:produces ["image/png"]} + :responses {200 {:description "an image" + :content {"image/png" any?}}} :handler (fn [_] {:status 200 :headers {"Content-Type" "image/png"} From bf8d0ba1ef6a116cf5ca271083951dc8ec58439a Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Thu, 16 Mar 2023 10:18:14 +0200 Subject: [PATCH 10/14] doc: don't say :multipart only works with spec --- doc/ring/coercion.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ring/coercion.md b/doc/ring/coercion.md index 8de57c1a..8ca8fb68 100644 --- a/doc/ring/coercion.md +++ b/doc/ring/coercion.md @@ -12,7 +12,7 @@ The following request parameters are currently supported: | `:form` | `:form-params` | | `:header` | `:header-params` | | `:path` | `:path-params` | -| `:multipart` | `:multipart-params`, only properly supports `clojure.spec`, see [Default Middleware](default_middleware.md) | +| `:multipart` | `:multipart-params`, see [Default Middleware](default_middleware.md) | To enable coercion, the following things need to be done: From 9a99ed96b2def8058904038ceed7158fb18a5eb8 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Thu, 16 Mar 2023 10:19:30 +0200 Subject: [PATCH 11/14] doc: link to examples/ring-malli-swagger from doc/ring/openapi.md --- doc/ring/openapi.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/ring/openapi.md b/doc/ring/openapi.md index 3a1f5fcf..d3e127cf 100644 --- a/doc/ring/openapi.md +++ b/doc/ring/openapi.md @@ -5,7 +5,9 @@ Reitit can generate [OpenAPI 3.1.0](https://spec.openapis.org/oas/v3.1.0) documentation. The feature works similarly to [Swagger documentation](swagger.md). -The [http-swagger example](../../examples/http-swagger) also has OpenAPI documentation. +The [http-swagger](../../examples/http-swagger) and +[ring-malli-swagger](../../examples/ring-malli-swagger) examples also +have OpenAPI documentation. ## OpenAPI data From 389f4a29da3b4b7c196ea1ddb5ac7668a2e8e98e Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Fri, 17 Mar 2023 10:12:15 +0200 Subject: [PATCH 12/14] format --- examples/ring-malli-swagger/src/example/server.clj | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/ring-malli-swagger/src/example/server.clj b/examples/ring-malli-swagger/src/example/server.clj index 1b3d880d..a213edaa 100644 --- a/examples/ring-malli-swagger/src/example/server.clj +++ b/examples/ring-malli-swagger/src/example/server.clj @@ -31,11 +31,11 @@ {:name "math", :description "math api"}]} :handler (swagger/create-swagger-handler)}}] ["/openapi.json" - {:get {:no-doc true - :openapi {:info {:title "my-api" - :description "openapi3 docs with [malli](https://github.com/metosin/malli) and reitit-ring" - :version "0.0.1"}} - :handler (openapi/create-openapi-handler)}}] + {:get {:no-doc true + :openapi {:info {:title "my-api" + :description "openapi3 docs with [malli](https://github.com/metosin/malli) and reitit-ring" + :version "0.0.1"}} + :handler (openapi/create-openapi-handler)}}] ["/files" {:swagger {:tags ["files"]}} From d8e28e153b1733986708895d2bc15fbc0de20c93 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Fri, 17 Mar 2023 14:43:49 +0200 Subject: [PATCH 13/14] fix: swagger multipart support 1. For spec we were including some extra stuff in the parameter specification: {:description "", :in "formData", :name "file", :properties {"bytes" {:format "byte", :type "string"}, "content-type" {:type "string"}, "filename" {:type "string"}}, :required ["filename" "content-type" "bytes"], :type "file"} 2. For malli the :type changed from "file" to "string" because of openapi changes. Now openapi and swagger both get the right type. 3. Test for swagger multipart support --- .../reitit/http/interceptors/multipart.clj | 4 +- .../reitit-malli/src/reitit/ring/malli.cljc | 6 ++- test/cljc/reitit/swagger_test.clj | 49 +++++++++++++++++++ 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/modules/reitit-interceptors/src/reitit/http/interceptors/multipart.clj b/modules/reitit-interceptors/src/reitit/http/interceptors/multipart.clj index 4a2d4ba1..b4bd02e5 100644 --- a/modules/reitit-interceptors/src/reitit/http/interceptors/multipart.clj +++ b/modules/reitit-interceptors/src/reitit/http/interceptors/multipart.clj @@ -19,7 +19,7 @@ "Spec for file param created by ring.middleware.multipart-params.temp-file store." (st/spec {:spec (s/keys :req-un [::filename ::content-type ::tempfile ::size]) - :swagger/type "file" + :swagger {:type "file"} :openapi {:type "string" :format "binary"}})) @@ -27,7 +27,7 @@ "Spec for file param created by ring.middleware.multipart-params.byte-array store." (st/spec {:spec (s/keys :req-un [::filename ::content-type ::bytes]) - :swagger/type "file" + :swagger {:type "file"} :openapi {:type "string" :format "binary"}})) diff --git a/modules/reitit-malli/src/reitit/ring/malli.cljc b/modules/reitit-malli/src/reitit/ring/malli.cljc index 1ccc1959..9815fef5 100644 --- a/modules/reitit-malli/src/reitit/ring/malli.cljc +++ b/modules/reitit-malli/src/reitit/ring/malli.cljc @@ -4,7 +4,8 @@ #?(:clj (def temp-file-part "Schema for file param created by ring.middleware.multipart-params.temp-file store." - [:map {:json-schema {:type "string" + [:map {:swagger {:type "file"} + :json-schema {:type "string" :format "binary"}} [:filename string?] [:content-type string?] @@ -14,7 +15,8 @@ #?(:clj (def bytes-part "Schema for file param created by ring.middleware.multipart-params.byte-array store." - [:map {:json-schema {:type "string" + [:map {:swagger {:type "file"} + :json-schema {:type "string" :format "binary"}} [:filename string?] [:content-type string?] diff --git a/test/cljc/reitit/swagger_test.clj b/test/cljc/reitit/swagger_test.clj index 542b7ebd..d2d18c31 100644 --- a/test/cljc/reitit/swagger_test.clj +++ b/test/cljc/reitit/swagger_test.clj @@ -1,16 +1,27 @@ (ns reitit.swagger-test (:require [clojure.test :refer [deftest is testing]] + [jsonista.core :as j] [muuntaja.core :as m] [reitit.coercion.malli :as malli] [reitit.coercion.schema :as schema] [reitit.coercion.spec :as spec] + [reitit.http.interceptors.multipart] [reitit.ring :as ring] + [reitit.ring.malli] [reitit.ring.coercion :as rrc] [reitit.swagger :as swagger] [reitit.swagger-ui :as swagger-ui] [schema.core :as s] [spec-tools.data-spec :as ds])) +(defn- normalize + "Normalize format of swagger spec by converting it to json and back. + Handles differences like :q vs \"q\" in swagger generation." + [data] + (-> data + j/write-value-as-string + (j/read-value j/keyword-keys-object-mapper))) + (def app (ring/ring-handler (ring/router @@ -410,3 +421,41 @@ :handler (swagger/create-swagger-handler)}}]])) output (with-out-str (app {:request-method :get, :uri "/swagger.json"}))] (is (.contains output "WARN"))))) + +(deftest multipart-test + (doseq [[coercion file-schema string-schema] + [[#'malli/coercion + reitit.ring.malli/bytes-part + :string] + [#'spec/coercion + reitit.http.interceptors.multipart/bytes-part + string?]]] + (testing coercion + (let [app (ring/ring-handler + (ring/router + [["/upload" + {:post {:decription "upload" + :coercion @coercion + :parameters {:multipart {:file file-schema + :more string-schema}} + :handler identity}}] + ["/swagger.json" + {:get {:no-doc true + :handler (swagger/create-swagger-handler)}}]] + {:data {:middleware [swagger/swagger-feature]}})) + spec (-> {:request-method :get + :uri "/swagger.json"} + app + :body)] + (is (= [{:description "" + :in "formData" + :name "file" + :required true + :type "file"} + {:description "" + :in "formData" + :name "more" + :required true + :type "string"}] + (normalize + (get-in spec [:paths "/upload" :post :parameters])))))))) From 8272b651e37d6bdbda471f4e2da9ff3f3427974a Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Fri, 17 Mar 2023 14:58:28 +0200 Subject: [PATCH 14/14] doc: examples/ring-malli-swagger: share tags between swagger&openapi --- examples/ring-malli-swagger/src/example/server.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/ring-malli-swagger/src/example/server.clj b/examples/ring-malli-swagger/src/example/server.clj index a213edaa..b7ab91b4 100644 --- a/examples/ring-malli-swagger/src/example/server.clj +++ b/examples/ring-malli-swagger/src/example/server.clj @@ -38,7 +38,7 @@ :handler (openapi/create-openapi-handler)}}] ["/files" - {:swagger {:tags ["files"]}} + {:tags ["files"]} ["/upload" {:post {:summary "upload a file" @@ -62,7 +62,7 @@ (io/input-stream))})}}]] ["/math" - {:swagger {:tags ["math"]}} + {:tags ["math"]} ["/plus" {:get {:summary "plus with malli query parameters"