feat: allow :default response status code again

it is an old feature, but didn't have a test, so it was broken by #715

also add a test so we don't break it again
This commit is contained in:
Joel Kaasinen 2025-04-11 08:27:44 +03:00
parent 78cf477b88
commit dd835e73a8
3 changed files with 81 additions and 25 deletions

View file

@ -184,8 +184,8 @@
(defn response-coercers [coercion responses opts]
(some->> (for [[status model] responses]
(do
(when-not (int? status)
(throw (ex-info "Response status must be int" {:status status})))
(when-not (or (= :default status) (int? status))
(throw (ex-info "Response status must be int or :default" {:status status})))
[status (response-coercer coercion model opts)]))
(filter second) (seq) (into {})))

View file

@ -67,9 +67,9 @@
:responses {200 {:description "success"
:body {:total int?}}
500 {:description "fail"}
504 {:description "default"
:content {:default {:schema {:error string?}}}
:body {:masked string?}}}
:default {:description "default"
:content {:default {:schema {:error string?}}}
:body {:masked string?}}}
:handler (fn [{{{:keys [z]} :path
xs :body} :parameters}]
{:status 200, :body {:total (+ (reduce + xs) z)}})}}]]
@ -96,9 +96,9 @@
:responses {200 {:description "success"
:body [:map [:total int?]]}
500 {:description "fail"}
504 {:description "default"
:content {:default {:schema {:error string?}}}
:body {:masked string?}}}
:default {:description "default"
:content {:default {:schema {:error string?}}}
:body {:masked string?}}}
:handler (fn [{{{:keys [z]} :path
xs :body} :parameters}]
{:status 200, :body {:total (+ (reduce + xs) z)}})}}]]
@ -125,9 +125,9 @@
:responses {200 {:description "success"
:body {:total s/Int}}
500 {:description "fail"}
504 {:description "default"
:content {:default {:schema {:error s/Str}}}
:body {:masked s/Str}}}
:default {:description "default"
:content {:default {:schema {:error s/Str}}}
:body {:masked s/Str}}}
:handler (fn [{{{:keys [z]} :path
xs :body} :parameters}]
{:status 200, :body {:total (+ (reduce + xs) z)}})}}]]]
@ -200,10 +200,10 @@
400 {:content {"application/json" {:schema {:type "string"}}}
:description "kosh"}
500 {:description "fail"}
504 {:description "default"
:content {"application/json" {:schema {:properties {"error" {:type "string"}}
:required ["error"]
:type "object"}}}}}
:default {:description "default"
:content {"application/json" {:schema {:properties {"error" {:type "string"}}
:required ["error"]
:type "object"}}}}}
:summary "plus with body"}}
"/api/malli/plus/{z}" {:get {:parameters [{:in "query"
:name :x
@ -242,11 +242,11 @@
400 {:description "kosh"
:content {"application/json" {:schema {:type "string"}}}}
500 {:description "fail"}
504 {:description "default"
:content {"application/json" {:schema {:additionalProperties false
:properties {:error {:type "string"}}
:required [:error]
:type "object"}}}}}
:default {:description "default"
:content {"application/json" {:schema {:additionalProperties false
:properties {:error {:type "string"}}
:required [:error]
:type "object"}}}}}
:summary "plus with body"}}
"/api/schema/plus/{z}" {:get {:parameters [{:in "query"
:name "x"
@ -292,11 +292,11 @@
400 {:description "kosh"
:content {"application/json" {:schema {:type "string"}}}}
500 {:description "fail"}
504 {:description "default"
:content {"application/json" {:schema {:additionalProperties false
:properties {"error" {:type "string"}}
:required ["error"]
:type "object"}}}}}
:default {:description "default"
:content {"application/json" {:schema {:additionalProperties false
:properties {"error" {:type "string"}}
:required ["error"]
:type "object"}}}}}
:summary "plus with body"}}}}]
(is (= expected spec))
(is (= nil (validate spec))))))

View file

@ -680,6 +680,62 @@
(call (request "application/json" "application/transit" {:request :json :response :json}))))))))))))
#?(:clj
(deftest response-coercion-test
(doseq [[coercion schema-200 schema-default]
[[malli/coercion
[:map [:a :int]]
[:map [:b :int]]]
[schema/coercion
{:a s/Int}
{:b s/Int}]
[spec/coercion
{:a int?}
{:b int?}]]]
(testing (str coercion)
(let [app (ring/ring-handler
(ring/router
["/foo" {:post {:responses {200 {:content {:default {:schema schema-200}}}
:default {:content {"application/json" {:schema schema-default}}}}
:handler (fn [req]
{:status (-> req :body-params :status)
:body (-> req :body-params :response)})}}]
{:validate reitit.ring.spec/validate
:data {:middleware [rrc/coerce-request-middleware
rrc/coerce-response-middleware]
:coercion coercion}}))
call (fn [request]
(try
(app request)
(catch ExceptionInfo e
(select-keys (ex-data e) [:type :in]))))
request (fn [body]
{:request-method :post
:uri "/foo"
:muuntaja/request {:format "application/json"}
:muuntaja/response {:format (:format body "application/json")}
:body-params body})]
(testing "explicit response schema"
(is (= {:status 200 :body {:a 1}}
(call (request {:status 200 :response {:a 1}})))
"valid response")
(is (= {:type :reitit.coercion/response-coercion, :in [:response :body]}
(call (request {:status 200 :response {:b 1}})))
"invalid response")
(is (= {:type :reitit.coercion/response-coercion, :in [:response :body]}
(call (request {:status 200 :response {:b 1} :format "application/edn"})))
"invalid response, different content-type"))
(testing "default response schema"
(is (= {:status 300 :body {:b 2}}
(call (request {:status 300 :response {:b 2}})))
"valid response")
(is (= {:type :reitit.coercion/response-coercion, :in [:response :body]}
(call (request {:status 300 :response {:a 2}})))
"invalid response")
(is (= {:status 300 :body "anything goes!"}
(call (request {:status 300 :response "anything goes!" :format "application/edn"})))
"no coercion applied due to content-type")))))))
#?(:clj
(deftest muuntaja-test
(let [app (ring/ring-handler