mirror of
https://github.com/metosin/reitit.git
synced 2025-12-21 01:51: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)]
|
||||
(json-schema/transform (coercion/-compile-model coercion schema current-opts)
|
||||
current-opts)))]
|
||||
|
||||
(merge
|
||||
(when (seq parameters)
|
||||
{:parameters
|
||||
|
|
@ -165,17 +164,26 @@
|
|||
:content-type content-type})]
|
||||
[content-type {:schema schema}])))
|
||||
content-types)}})
|
||||
|
||||
(when request
|
||||
;; request allow to different :requestBody per content-type
|
||||
{:requestBody
|
||||
{:content
|
||||
(into {}
|
||||
(map (fn [[content-type requestBody]]
|
||||
(let [schema (->schema-object requestBody {:in :requestBody
|
||||
:type :schema
|
||||
:content-type content-type})]
|
||||
[content-type {:schema schema}])))
|
||||
(:content request))}})
|
||||
{: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 {}
|
||||
(map (fn [[content-type requestBody]]
|
||||
(let [schema (->schema-object requestBody {:in :requestBody
|
||||
:type :schema
|
||||
:content-type content-type})]
|
||||
[content-type {:schema schema}])))
|
||||
(:content request)))}})
|
||||
(when responses
|
||||
{:responses
|
||||
(into {}
|
||||
|
|
|
|||
|
|
@ -47,8 +47,9 @@
|
|||
(reify coercion/Coercion
|
||||
(-get-name [_] :schema)
|
||||
(-get-options [_] opts)
|
||||
(-get-apidocs [this specification {:keys [parameters responses]}]
|
||||
;; TODO: this looks identical to spec, refactor when schema is done.
|
||||
(-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.
|
||||
(case specification
|
||||
:swagger (swagger/swagger-spec
|
||||
(merge
|
||||
|
|
@ -76,24 +77,28 @@
|
|||
(for [[k v] (dissoc parameters :body :request)]
|
||||
[k (coercion/-compile-model this v nil)]))}))
|
||||
(when (:body parameters)
|
||||
{:requestBody (openapi/openapi-spec
|
||||
{::openapi/content {"application/json" (:body parameters)}})})
|
||||
{:requestBody (openapi/openapi-spec
|
||||
{::openapi/content (zipmap content-types (repeat (:body parameters)))})})
|
||||
(when (:request parameters)
|
||||
{:requestBody (openapi/openapi-spec
|
||||
{::openapi/content (:content (:request parameters))})})
|
||||
{:requestBody (openapi/openapi-spec
|
||||
{::openapi/content (merge
|
||||
(when-let [default (get-in parameters [:request :body])]
|
||||
(zipmap content-types (repeat default)))
|
||||
(:content (:request parameters)))})})
|
||||
(when responses
|
||||
{:responses
|
||||
(into
|
||||
(empty responses)
|
||||
(for [[k response] responses]
|
||||
(for [[k {:keys [body content] :as response}] responses]
|
||||
[k (merge
|
||||
(select-keys response [:description])
|
||||
(when (:body response)
|
||||
(when (or body content)
|
||||
(openapi/openapi-spec
|
||||
{::openapi/content {"application/json" (coercion/-compile-model this (:body response) nil)}}))
|
||||
(when (:content response)
|
||||
(openapi/openapi-spec
|
||||
{::openapi/content (:content response)})))]))}))
|
||||
{::openapi/content (merge
|
||||
(when body
|
||||
(zipmap content-types (repeat (coercion/-compile-model this body nil))))
|
||||
(when response
|
||||
(:content response)))})))]))}))
|
||||
|
||||
(throw
|
||||
(ex-info
|
||||
|
|
|
|||
|
|
@ -86,7 +86,8 @@
|
|||
(reify coercion/Coercion
|
||||
(-get-name [_] :spec)
|
||||
(-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
|
||||
:swagger (swagger/swagger-spec
|
||||
(merge
|
||||
|
|
@ -114,28 +115,32 @@
|
|||
(for [[k v] (dissoc parameters :body :request)]
|
||||
[k (coercion/-compile-model this v nil)]))})
|
||||
(when (:body parameters)
|
||||
{:requestBody (openapi/openapi-spec
|
||||
{::openapi/content {"application/json" (coercion/-compile-model this (:body parameters) nil)}})})
|
||||
{:requestBody (openapi/openapi-spec
|
||||
{::openapi/content (zipmap content-types (repeat (coercion/-compile-model this (:body parameters) nil)))})})
|
||||
(when (:request parameters)
|
||||
{:requestBody (openapi/openapi-spec
|
||||
{::openapi/content (into {}
|
||||
{:requestBody (openapi/openapi-spec
|
||||
{::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))]
|
||||
[format (coercion/-compile-model this model nil)]))})})
|
||||
[format (coercion/-compile-model this model nil)])))})})
|
||||
(when responses
|
||||
{:responses
|
||||
(into
|
||||
(empty responses)
|
||||
(for [[k response] responses]
|
||||
(for [[k {:keys [body content] :as response}] responses]
|
||||
[k (merge
|
||||
(select-keys response [:description])
|
||||
(when (:body response)
|
||||
(when (or body content)
|
||||
(openapi/openapi-spec
|
||||
{::openapi/content {"application/json" (coercion/-compile-model this (:body response) nil)}}))
|
||||
(when (:content response)
|
||||
(openapi/openapi-spec
|
||||
{::openapi/content (into {}
|
||||
(for [[format model] (:content response)]
|
||||
[format (coercion/-compile-model this model nil)]))})))]))})))
|
||||
{::openapi/content (merge
|
||||
(when body
|
||||
(zipmap content-types (repeat (coercion/-compile-model this (:body response) nil))))
|
||||
(when response
|
||||
(into {}
|
||||
(for [[format model] (:content response)]
|
||||
[format (coercion/-compile-model this model nil)]))))})))]))})))
|
||||
(throw
|
||||
(ex-info
|
||||
(str "Can't produce Spec apidocs for " specification)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
(:require [clojure.test :refer [deftest is testing]]
|
||||
[jsonista.core :as j]
|
||||
[matcher-combinators.test :refer [match?]]
|
||||
[matcher-combinators.matchers :as matchers]
|
||||
[muuntaja.core :as m]
|
||||
[reitit.coercion.malli :as malli]
|
||||
[reitit.coercion.schema :as schema]
|
||||
|
|
@ -457,3 +458,43 @@
|
|||
(app (assoc query :body-params {:z 1}))
|
||||
(catch clojure.lang.ExceptionInfo e
|
||||
(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