feat: openapi default request/response schemas

use a default schema from :request/:response :body for all specified
:content-types
This commit is contained in:
Joel Kaasinen 2023-03-06 09:12:56 +02:00
parent c3a3ca9f95
commit 4c990fb44f
4 changed files with 94 additions and 35 deletions

View file

@ -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 {}

View file

@ -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

View file

@ -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)

View file

@ -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))))))))))