From f1e6d37dcf18715cd31bec0625dd401cd33a2af4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Varela?= Date: Fri, 9 Feb 2024 09:59:11 +0200 Subject: [PATCH 1/7] fix: don't output `:default` in openapi request body --- modules/reitit-openapi/src/reitit/openapi.cljc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/reitit-openapi/src/reitit/openapi.cljc b/modules/reitit-openapi/src/reitit/openapi.cljc index b845e889..4a1b485b 100644 --- a/modules/reitit-openapi/src/reitit/openapi.cljc +++ b/modules/reitit-openapi/src/reitit/openapi.cljc @@ -130,7 +130,7 @@ :type :schema :content-type content-type})] [content-type (->content data schema)]))) - (:content request)))}}) + (dissoc (:content request) :default)))}}) (when multipart {:requestBody {:content From ed280f9a337ae53be98d0f2ef41548ddec90d17f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Varela?= Date: Fri, 9 Feb 2024 10:26:57 +0200 Subject: [PATCH 2/7] feature: fetch openapi content types from muuntaja (level 1 integration in #636) --- .../reitit-openapi/src/reitit/openapi.cljc | 13 +- test/cljc/reitit/openapi_test.clj | 153 +++++++++++++----- 2 files changed, 121 insertions(+), 45 deletions(-) diff --git a/modules/reitit-openapi/src/reitit/openapi.cljc b/modules/reitit-openapi/src/reitit/openapi.cljc index 4a1b485b..7d3a3b45 100644 --- a/modules/reitit-openapi/src/reitit/openapi.cljc +++ b/modules/reitit-openapi/src/reitit/openapi.cljc @@ -3,6 +3,7 @@ [clojure.spec.alpha :as s] [clojure.string :as str] [meta-merge.core :refer [meta-merge]] + [muuntaja.core :as m] [reitit.coercion :as coercion] [reitit.core :as r] [reitit.trie :as trie])) @@ -76,9 +77,7 @@ (-> path (trie/normalize opts) (str/replace #"\{\*" "{"))) (defn -get-apidocs-openapi - [coercion {:keys [request parameters responses openapi/request-content-types openapi/response-content-types] - :or {request-content-types ["application/json"] - response-content-types ["application/json"]}}] + [coercion {:keys [request muuntaja parameters responses openapi/request-content-types openapi/response-content-types]}] (let [{:keys [body multipart]} parameters parameters (dissoc parameters :request :body :multipart) ->content (fn [data schema] @@ -86,7 +85,13 @@ {:schema schema} (select-keys data [:description :examples]) (:openapi data))) - ->schema-object #(coercion/-get-model-apidocs coercion :openapi %1 %2)] + ->schema-object #(coercion/-get-model-apidocs coercion :openapi %1 %2) + request-content-types (or request-content-types + (when muuntaja (m/decodes muuntaja)) + ["application/json"]) + response-content-types (or response-content-types + (when muuntaja (m/encodes muuntaja)) + ["application/json"])] (merge (when (seq parameters) {:parameters diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index c950e1c1..a3e8347d 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -685,52 +685,123 @@ (testing "spec is valid" (is (nil? (validate spec)))))))) + (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 (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 - :openapi/request-content-types [content-type] - :openapi/response-content-types [content-type "application/response"] - :request {:content {"application/transit" {:schema (->schema :transit)}} - :body (->schema :default)} - :responses {200 {:description "success" - :content {"application/transit" {:schema (->schema :transit)}} - :body (->schema :default)}} - :handler (fn [req] - {:status 200 - :body (-> req :parameters :request)})}}] - ["/openapi.json" - {:get {:handler (openapi/create-openapi-handler) - :openapi {:info {:title "" :version "0.0.1"}} - :no-doc true}}]] - {:validate reitit.ring.spec/validate - :data {:middleware [openapi/openapi-feature - 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" "application/response"]) - (-> spec - (get-in [:paths "/parameters" :post :responses 200 :content]) - keys)))) - (testing "spec is valid" - (is (nil? (validate spec)))))))))) + (let [app (ring/ring-handler + (ring/router + [["/explicit-content-type" + {:post {:description "parameters" + :coercion coercion + :request {:content {"application/json" {:schema (->schema :b)} + "application/edn" {:schema (->schema :c)}}} + :responses {200 {:description "success" + :content {"application/json" {:schema (->schema :ok)} + "application/edn" {:schema (->schema :edn)}}}} + :handler (fn [req] + {:status 200 + :body (-> req :parameters :request)})}}] + ["/muuntaja" + {:post {:description "default content types from muuntaja" + :coercion coercion + ;;; TODO: test the :parameters syntax + :request {:content {:default {:schema (->schema :b)} + "application/reitit-request" {:schema (->schema :ok)}}} + :responses {200 {:description "success" + :content {:default {:schema (->schema :ok)} + "application/reitit-response" {:schema (->schema :ok)}}}} + :handler (fn [req] + {:status 200 + :body (-> req :parameters :request)})}}] + ["/override-default-content-type" + {:post {:description "override default content types from muuntaja" + :coercion coercion + :openapi/request-content-types ["application/request"] + :openapi/response-content-types ["application/response"] + ;;; TODO: test the :parameters syntax + :request {:content {:default {:schema (->schema :b)}}} + :responses {200 {:description "success" + :content {:default {:schema (->schema :ok)}}}} + :handler (fn [req] + {:status 200 + :body (-> req :parameters :request)})}}] + ["/legacy" + {:post {:description "default content types from muuntaja, legacy syntax" + :coercion coercion + ;;; TODO: test the :parameters syntax + :request {:body {:schema (->schema :b)}} + :responses {200 {:description "success" + :body {:schema (->schema :ok)}}} + :handler (fn [req] + {:status 200 + :body (-> req :parameters :request)})}}] + ["/openapi.json" + {:get {:handler (openapi/create-openapi-handler) + :openapi {:info {:title "" :version "0.0.1"}} + :no-doc true}}]] + {:validate reitit.ring.spec/validate + :data {:muuntaja (m/create (-> m/default-options + (update-in [:formats] select-keys ["application/transit+json"]) + (assoc :default-format "application/transit+json"))) + :middleware [openapi/openapi-feature + rrc/coerce-request-middleware + rrc/coerce-response-middleware]}})) + spec (-> {:request-method :get + :uri "/openapi.json"} + app + :body) + spec-coercion (= coercion spec/coercion)] + (testing "explicit content types" + (testing "body parameter" + (is (= ["application/edn" "application/json"] + (-> spec + (get-in [:paths "/explicit-content-type" :post :requestBody :content]) + keys + sort)))) + (testing "body response" + (is (= ["application/edn" "application/json"] + (-> spec + (get-in [:paths "/explicit-content-type" :post :responses 200 :content]) + keys + sort))))) + (testing "muuntaja content types" + (testing "body parameter" + (is (= ["application/transit+json" "application/reitit-request"] + (-> spec + (get-in [:paths "/muuntaja" :post :requestBody :content]) + keys)))) + (testing "body response" + (is (= ["application/transit+json" "application/reitit-response"] + (-> spec + (get-in [:paths "/muuntaja" :post :responses 200 :content]) + keys))))) + (testing "overridden muuntaja content types" + (testing "body parameter" + (is (= ["application/request"] + (-> spec + (get-in [:paths "/override-default-content-type" :post :requestBody :content]) + keys)))) + (testing "body response" + (is (= ["application/response"] + (-> spec + (get-in [:paths "/override-default-content-type" :post :responses 200 :content]) + keys))))) + (testing "legacy syntax muuntaja content types" + (testing "body parameter" + (is (= ["application/transit+json"] + (-> spec + (get-in [:paths "/legacy" :post :requestBody :content]) + keys)))) + (testing "body response" + (is (= ["application/transit+json"] + (-> spec + (get-in [:paths "/legacy" :post :responses 200 :content]) + keys))))) + (testing "spec is valid" + (is (nil? (validate spec)))))))) (deftest recursive-test ;; Recursive schemas only properly supported for malli From d2c00026e6e408f794107261444aabb142d14b5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Varela?= Date: Fri, 9 Feb 2024 10:45:20 +0200 Subject: [PATCH 3/7] doc: Update docs for fetching content types from Muuntaja instance --- CHANGELOG.md | 1 + doc/ring/openapi.md | 18 +++++++++++++----- examples/openapi/src/example/server.clj | 2 -- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2af3d67e..a46fb8f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ We use [Break Versioning][breakver]. The version numbers follow a `. Date: Fri, 9 Feb 2024 11:52:00 +0200 Subject: [PATCH 4/7] doc: make :default stand out as special --- doc/ring/openapi.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ring/openapi.md b/doc/ring/openapi.md index 4a788841..620ec1b4 100644 --- a/doc/ring/openapi.md +++ b/doc/ring/openapi.md @@ -109,7 +109,7 @@ openapi example](../../examples/openapi). :value (pr-str {:color :red :pineapple true})}}}}}} ``` -The default content types map to the content types supported by the Muuntaja +The special `:default` content types map to the content types supported by the Muuntaja instance. You can override these by using the `:openapi/request-content-types` and `:openapi/response-content-types` keys, which must contain vector of supported content types. If there is no Muuntaja instance, and these keys are From cb1c5e8748d1ad9b87fa349d40522157dd962479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Varela?= Date: Fri, 9 Feb 2024 12:05:43 +0200 Subject: [PATCH 5/7] made openapi clj, not cljc --- modules/reitit-openapi/src/reitit/{openapi.cljc => openapi.clj} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename modules/reitit-openapi/src/reitit/{openapi.cljc => openapi.clj} (99%) diff --git a/modules/reitit-openapi/src/reitit/openapi.cljc b/modules/reitit-openapi/src/reitit/openapi.clj similarity index 99% rename from modules/reitit-openapi/src/reitit/openapi.cljc rename to modules/reitit-openapi/src/reitit/openapi.clj index 7d3a3b45..8e49de77 100644 --- a/modules/reitit-openapi/src/reitit/openapi.cljc +++ b/modules/reitit-openapi/src/reitit/openapi.clj @@ -211,5 +211,5 @@ ([req res raise] (try (res (create-openapi req)) - (catch #?(:clj Exception :cljs :default) e + (catch Exception e (raise e)))))) From 6c9b280fa28ce52e5792b1b23a7aaee6e595a838 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Varela?= Date: Fri, 9 Feb 2024 12:07:54 +0200 Subject: [PATCH 6/7] doc: add notice about OpenAPI support being clj only --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a46fb8f4..adb71b16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ We use [Break Versioning][breakver]. The version numbers follow a `. Date: Fri, 9 Feb 2024 12:14:35 +0200 Subject: [PATCH 7/7] fix: added muuntaja dependency for the openapi module --- modules/reitit-openapi/project.clj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/reitit-openapi/project.clj b/modules/reitit-openapi/project.clj index 18f9a590..2da2ce5b 100644 --- a/modules/reitit-openapi/project.clj +++ b/modules/reitit-openapi/project.clj @@ -9,4 +9,5 @@ :plugins [[lein-parent "0.3.9"]] :parent-project {:path "../../project.clj" :inherit [:deploy-repositories :managed-dependencies]} - :dependencies [[metosin/reitit-core]]) + :dependencies [[metosin/reitit-core] + [metosin/muuntaja]])