mirror of
https://github.com/metosin/reitit.git
synced 2025-12-21 10:01:11 +00:00
feat: openapi default request/response schemas
use a default schema from :request/:response :body for all specified :content-types
This commit is contained in:
parent
c3a3ca9f95
commit
4c990fb44f
4 changed files with 94 additions and 35 deletions
|
|
@ -141,7 +141,6 @@
|
||||||
(let [current-opts (merge options opts)]
|
(let [current-opts (merge options opts)]
|
||||||
(json-schema/transform (coercion/-compile-model coercion schema current-opts)
|
(json-schema/transform (coercion/-compile-model coercion schema current-opts)
|
||||||
current-opts)))]
|
current-opts)))]
|
||||||
|
|
||||||
(merge
|
(merge
|
||||||
(when (seq parameters)
|
(when (seq parameters)
|
||||||
{:parameters
|
{:parameters
|
||||||
|
|
@ -165,17 +164,26 @@
|
||||||
:content-type content-type})]
|
:content-type content-type})]
|
||||||
[content-type {:schema schema}])))
|
[content-type {:schema schema}])))
|
||||||
content-types)}})
|
content-types)}})
|
||||||
|
|
||||||
(when request
|
(when request
|
||||||
;; request allow to different :requestBody per content-type
|
;; request allow to different :requestBody per content-type
|
||||||
{:requestBody
|
{:requestBody
|
||||||
{:content
|
{:content (merge
|
||||||
|
(when (:body request)
|
||||||
|
(into {}
|
||||||
|
(map (fn [content-type]
|
||||||
|
(let [schema (->schema-object (:body request) {:in :requestBody
|
||||||
|
:type :schema
|
||||||
|
:content-type content-type})]
|
||||||
|
[content-type {:schema schema}])))
|
||||||
|
content-types))
|
||||||
(into {}
|
(into {}
|
||||||
(map (fn [[content-type requestBody]]
|
(map (fn [[content-type requestBody]]
|
||||||
(let [schema (->schema-object requestBody {:in :requestBody
|
(let [schema (->schema-object requestBody {:in :requestBody
|
||||||
:type :schema
|
:type :schema
|
||||||
:content-type content-type})]
|
:content-type content-type})]
|
||||||
[content-type {:schema schema}])))
|
[content-type {:schema schema}])))
|
||||||
(:content request))}})
|
(:content request)))}})
|
||||||
(when responses
|
(when responses
|
||||||
{:responses
|
{:responses
|
||||||
(into {}
|
(into {}
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,8 @@
|
||||||
(reify coercion/Coercion
|
(reify coercion/Coercion
|
||||||
(-get-name [_] :schema)
|
(-get-name [_] :schema)
|
||||||
(-get-options [_] opts)
|
(-get-options [_] opts)
|
||||||
(-get-apidocs [this specification {:keys [parameters responses]}]
|
(-get-apidocs [this specification {:keys [parameters responses content-types]
|
||||||
|
:or {content-types ["application/json"]}}]
|
||||||
;; TODO: this looks identical to spec, refactor when schema is done.
|
;; TODO: this looks identical to spec, refactor when schema is done.
|
||||||
(case specification
|
(case specification
|
||||||
:swagger (swagger/swagger-spec
|
:swagger (swagger/swagger-spec
|
||||||
|
|
@ -77,23 +78,27 @@
|
||||||
[k (coercion/-compile-model this v nil)]))}))
|
[k (coercion/-compile-model this v nil)]))}))
|
||||||
(when (:body parameters)
|
(when (:body parameters)
|
||||||
{:requestBody (openapi/openapi-spec
|
{:requestBody (openapi/openapi-spec
|
||||||
{::openapi/content {"application/json" (:body parameters)}})})
|
{::openapi/content (zipmap content-types (repeat (:body parameters)))})})
|
||||||
(when (:request parameters)
|
(when (:request parameters)
|
||||||
{:requestBody (openapi/openapi-spec
|
{:requestBody (openapi/openapi-spec
|
||||||
{::openapi/content (:content (:request parameters))})})
|
{::openapi/content (merge
|
||||||
|
(when-let [default (get-in parameters [:request :body])]
|
||||||
|
(zipmap content-types (repeat default)))
|
||||||
|
(:content (:request parameters)))})})
|
||||||
(when responses
|
(when responses
|
||||||
{:responses
|
{:responses
|
||||||
(into
|
(into
|
||||||
(empty responses)
|
(empty responses)
|
||||||
(for [[k response] responses]
|
(for [[k {:keys [body content] :as response}] responses]
|
||||||
[k (merge
|
[k (merge
|
||||||
(select-keys response [:description])
|
(select-keys response [:description])
|
||||||
(when (:body response)
|
(when (or body content)
|
||||||
(openapi/openapi-spec
|
(openapi/openapi-spec
|
||||||
{::openapi/content {"application/json" (coercion/-compile-model this (:body response) nil)}}))
|
{::openapi/content (merge
|
||||||
(when (:content response)
|
(when body
|
||||||
(openapi/openapi-spec
|
(zipmap content-types (repeat (coercion/-compile-model this body nil))))
|
||||||
{::openapi/content (:content response)})))]))}))
|
(when response
|
||||||
|
(:content response)))})))]))}))
|
||||||
|
|
||||||
(throw
|
(throw
|
||||||
(ex-info
|
(ex-info
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,8 @@
|
||||||
(reify coercion/Coercion
|
(reify coercion/Coercion
|
||||||
(-get-name [_] :spec)
|
(-get-name [_] :spec)
|
||||||
(-get-options [_] opts)
|
(-get-options [_] opts)
|
||||||
(-get-apidocs [this specification {:keys [parameters responses]}]
|
(-get-apidocs [this specification {:keys [parameters responses content-types]
|
||||||
|
:or {content-types ["application/json"]}}]
|
||||||
(case specification
|
(case specification
|
||||||
:swagger (swagger/swagger-spec
|
:swagger (swagger/swagger-spec
|
||||||
(merge
|
(merge
|
||||||
|
|
@ -115,27 +116,31 @@
|
||||||
[k (coercion/-compile-model this v nil)]))})
|
[k (coercion/-compile-model this v nil)]))})
|
||||||
(when (:body parameters)
|
(when (:body parameters)
|
||||||
{:requestBody (openapi/openapi-spec
|
{:requestBody (openapi/openapi-spec
|
||||||
{::openapi/content {"application/json" (coercion/-compile-model this (:body parameters) nil)}})})
|
{::openapi/content (zipmap content-types (repeat (coercion/-compile-model this (:body parameters) nil)))})})
|
||||||
(when (:request parameters)
|
(when (:request parameters)
|
||||||
{:requestBody (openapi/openapi-spec
|
{:requestBody (openapi/openapi-spec
|
||||||
{::openapi/content (into {}
|
{::openapi/content (merge
|
||||||
|
(when-let [default (get-in parameters [:request :body])]
|
||||||
|
(zipmap content-types (repeat (coercion/-compile-model this default nil))))
|
||||||
|
(into {}
|
||||||
(for [[format model] (:content (:request parameters))]
|
(for [[format model] (:content (:request parameters))]
|
||||||
[format (coercion/-compile-model this model nil)]))})})
|
[format (coercion/-compile-model this model nil)])))})})
|
||||||
(when responses
|
(when responses
|
||||||
{:responses
|
{:responses
|
||||||
(into
|
(into
|
||||||
(empty responses)
|
(empty responses)
|
||||||
(for [[k response] responses]
|
(for [[k {:keys [body content] :as response}] responses]
|
||||||
[k (merge
|
[k (merge
|
||||||
(select-keys response [:description])
|
(select-keys response [:description])
|
||||||
(when (:body response)
|
(when (or body content)
|
||||||
(openapi/openapi-spec
|
(openapi/openapi-spec
|
||||||
{::openapi/content {"application/json" (coercion/-compile-model this (:body response) nil)}}))
|
{::openapi/content (merge
|
||||||
(when (:content response)
|
(when body
|
||||||
(openapi/openapi-spec
|
(zipmap content-types (repeat (coercion/-compile-model this (:body response) nil))))
|
||||||
{::openapi/content (into {}
|
(when response
|
||||||
|
(into {}
|
||||||
(for [[format model] (:content response)]
|
(for [[format model] (:content response)]
|
||||||
[format (coercion/-compile-model this model nil)]))})))]))})))
|
[format (coercion/-compile-model this model nil)]))))})))]))})))
|
||||||
(throw
|
(throw
|
||||||
(ex-info
|
(ex-info
|
||||||
(str "Can't produce Spec apidocs for " specification)
|
(str "Can't produce Spec apidocs for " specification)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
(:require [clojure.test :refer [deftest is testing]]
|
(:require [clojure.test :refer [deftest is testing]]
|
||||||
[jsonista.core :as j]
|
[jsonista.core :as j]
|
||||||
[matcher-combinators.test :refer [match?]]
|
[matcher-combinators.test :refer [match?]]
|
||||||
|
[matcher-combinators.matchers :as matchers]
|
||||||
[muuntaja.core :as m]
|
[muuntaja.core :as m]
|
||||||
[reitit.coercion.malli :as malli]
|
[reitit.coercion.malli :as malli]
|
||||||
[reitit.coercion.schema :as schema]
|
[reitit.coercion.schema :as schema]
|
||||||
|
|
@ -457,3 +458,43 @@
|
||||||
(app (assoc query :body-params {:z 1}))
|
(app (assoc query :body-params {:z 1}))
|
||||||
(catch clojure.lang.ExceptionInfo e
|
(catch clojure.lang.ExceptionInfo e
|
||||||
(select-keys (ex-data e) [:type :in]))))))))))))
|
(select-keys (ex-data e) [:type :in]))))))))))))
|
||||||
|
|
||||||
|
(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 coercion
|
||||||
|
(doseq [content-type ["application/json" "application/edn"]]
|
||||||
|
(testing (str "default content type " content-type)
|
||||||
|
(let [app (ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
[["/parameters"
|
||||||
|
{:post {:coercion @coercion
|
||||||
|
:content-types [content-type] ;; TODO should this be under :openapi ?
|
||||||
|
:parameters {:request {:content {"application/transit" (->schema :transit)}
|
||||||
|
:body (->schema :default)}}
|
||||||
|
:responses {200 {:content {"application/transit" (->schema :transit)}
|
||||||
|
:body (->schema :default)}}
|
||||||
|
:handler (fn [req]
|
||||||
|
{:status 200
|
||||||
|
:body (-> req :parameters :request)})}}]
|
||||||
|
["/openapi.json"
|
||||||
|
{:get {:handler (openapi/create-openapi-handler)
|
||||||
|
:no-doc true}}]]
|
||||||
|
{:data {:middleware [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"])
|
||||||
|
(-> spec
|
||||||
|
(get-in [:paths "/parameters" :post :responses 200 :content])
|
||||||
|
keys))))))))))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue