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