mirror of
https://github.com/metosin/reitit.git
synced 2025-12-17 00:11:11 +00:00
Merge remote-tracking branch 'origin/master' into fix-openapi-example
This commit is contained in:
commit
25aee5ed22
7 changed files with 146 additions and 214 deletions
|
|
@ -13,6 +13,8 @@
|
|||
(-get-name [this] "Keyword name for the coercion")
|
||||
(-get-options [this] "Coercion options")
|
||||
(-get-apidocs [this specification data] "Returns api documentation")
|
||||
;; TODO doc options:
|
||||
(-get-model-apidocs [this specification model options] "Convert model into a format that can be used in api docs")
|
||||
(-compile-model [this model name] "Compiles a model")
|
||||
(-open-model [this model] "Returns a new model which allows extra keys in maps")
|
||||
(-encode-error [this error] "Converts error in to a serializable format")
|
||||
|
|
@ -189,37 +191,6 @@
|
|||
(defn -compile-parameters [data coercion]
|
||||
(impl/path-update data [[[:parameters any?] #(-compile-model coercion % nil)]]))
|
||||
|
||||
;;
|
||||
;; api-docs
|
||||
;;
|
||||
|
||||
(defn -warn-unsupported-coercions [{:keys [request responses] :as _data}]
|
||||
(when request
|
||||
(println "WARNING [reitit.coercion]: swagger apidocs don't support :request coercion"))
|
||||
(when (some :content (vals responses))
|
||||
(println "WARNING [reitit.coercion]: swagger apidocs don't support :responses :content coercion")))
|
||||
|
||||
(defn get-apidocs [coercion specification data]
|
||||
(let [swagger-parameter {:query :query
|
||||
:body :body
|
||||
:form :formData
|
||||
:header :header
|
||||
:path :path
|
||||
:multipart :formData}]
|
||||
(case specification
|
||||
:openapi (-get-apidocs coercion specification data)
|
||||
:swagger (do
|
||||
(-warn-unsupported-coercions data)
|
||||
(->> (update
|
||||
data
|
||||
:parameters
|
||||
(fn [parameters]
|
||||
(->> parameters
|
||||
(map (fn [[k v]] [(swagger-parameter k) v]))
|
||||
(filter first)
|
||||
(into {}))))
|
||||
(-get-apidocs coercion specification))))))
|
||||
|
||||
;;
|
||||
;; integration
|
||||
;;
|
||||
|
|
|
|||
|
|
@ -133,102 +133,6 @@
|
|||
;; malli options
|
||||
:options nil})
|
||||
|
||||
;; TODO: this is now seems like a generic transforming function that could be used in all of malli, spec, schema
|
||||
;; ... just tranform the schemas in place
|
||||
;; also, this has internally massive amount of duplicate code, could be simplified
|
||||
;; ... tests too
|
||||
(defn -get-apidocs-openapi
|
||||
[_ {:keys [request parameters responses content-types] :or {content-types ["application/json"]}} options]
|
||||
(let [{:keys [body multipart]} parameters
|
||||
parameters (dissoc parameters :request :body :multipart)
|
||||
->schema-object (fn [schema opts]
|
||||
(let [current-opts (merge options opts)]
|
||||
(json-schema/transform schema current-opts)))
|
||||
->content (fn [data schema]
|
||||
(merge
|
||||
{:schema schema}
|
||||
(select-keys data [:description :examples])
|
||||
(:openapi data)))]
|
||||
(merge
|
||||
(when (seq parameters)
|
||||
{:parameters
|
||||
(->> (for [[in schema] parameters
|
||||
:let [{:keys [properties required]} (->schema-object schema {:in in :type :parameter})
|
||||
required? (partial contains? (set required))]
|
||||
[k schema] properties]
|
||||
(merge {:in (name in)
|
||||
:name k
|
||||
:required (required? k)
|
||||
:schema schema}
|
||||
(select-keys schema [:description])))
|
||||
(into []))})
|
||||
(when body
|
||||
;; body uses a single schema to describe every :requestBody
|
||||
;; the schema-object transformer should be able to transform into distinct content-types
|
||||
{:requestBody {:content (into {}
|
||||
(map (fn [content-type]
|
||||
(let [schema (->schema-object body {:in :requestBody
|
||||
:type :schema
|
||||
:content-type content-type})]
|
||||
[content-type {:schema schema}])))
|
||||
content-types)}})
|
||||
|
||||
(when request
|
||||
;; request allow to different :requestBody per content-type
|
||||
{:requestBody
|
||||
{:content (merge
|
||||
(select-keys request [:description])
|
||||
(when-let [{:keys [schema] :as data} (coercion/get-default request)]
|
||||
(into {}
|
||||
(map (fn [content-type]
|
||||
(let [schema (->schema-object schema {:in :requestBody
|
||||
:type :schema
|
||||
:content-type content-type})]
|
||||
[content-type (->content data schema)])))
|
||||
content-types))
|
||||
(into {}
|
||||
(map (fn [[content-type {:keys [schema] :as data}]]
|
||||
(let [schema (->schema-object schema {:in :requestBody
|
||||
:type :schema
|
||||
:content-type content-type})]
|
||||
[content-type (->content data schema)])))
|
||||
(:content request)))}})
|
||||
(when multipart
|
||||
{:requestBody
|
||||
{:content
|
||||
{"multipart/form-data"
|
||||
{:schema
|
||||
(->schema-object multipart {:in :requestBody
|
||||
:type :schema
|
||||
:content-type "multipart/form-data"})}}}})
|
||||
(when responses
|
||||
{:responses
|
||||
(into {}
|
||||
(map (fn [[status {:keys [content], :as response}]]
|
||||
(let [default (coercion/get-default-schema response)
|
||||
content (-> (merge
|
||||
(when default
|
||||
(into {}
|
||||
(map (fn [content-type]
|
||||
(let [schema (->schema-object default {:in :responses
|
||||
:type :schema
|
||||
:content-type content-type})]
|
||||
[content-type (->content nil schema)])))
|
||||
content-types))
|
||||
(when content
|
||||
(into {}
|
||||
(map (fn [[content-type {:keys [schema] :as data}]]
|
||||
(let [schema (->schema-object schema {:in :responses
|
||||
:type :schema
|
||||
:content-type content-type})]
|
||||
[content-type (->content data schema)])))
|
||||
content)))
|
||||
(dissoc :default))]
|
||||
[status (merge (select-keys response [:description])
|
||||
(when content
|
||||
{:content content}))]))
|
||||
responses))}))))
|
||||
|
||||
(defn create
|
||||
([]
|
||||
(create nil))
|
||||
|
|
@ -243,6 +147,13 @@
|
|||
(reify coercion/Coercion
|
||||
(-get-name [_] :malli)
|
||||
(-get-options [_] opts)
|
||||
(-get-model-apidocs [this specification model options]
|
||||
(case specification
|
||||
:openapi (json-schema/transform model (merge opts options))
|
||||
(throw
|
||||
(ex-info
|
||||
(str "Can't produce Malli apidocs for " specification)
|
||||
{:type specification, :coercion :malli}))))
|
||||
(-get-apidocs [this specification {:keys [parameters responses] :as data}]
|
||||
(case specification
|
||||
:swagger (merge
|
||||
|
|
@ -264,11 +175,11 @@
|
|||
(if (:schema $)
|
||||
(update $ :schema swagger/transform {:type :schema})
|
||||
$))]))}))
|
||||
:openapi (-get-apidocs-openapi this data options)
|
||||
;; :openapi handled in reitit.openapi/-get-apidocs-openapi
|
||||
(throw
|
||||
(ex-info
|
||||
(str "Can't produce Schema apidocs for " specification)
|
||||
{:type specification, :coercion :schema}))))
|
||||
(str "Can't produce Malli apidocs for " specification)
|
||||
{:type specification, :coercion :malli}))))
|
||||
(-compile-model [_ model _]
|
||||
(if (= 1 (count model))
|
||||
(compile (first model) options)
|
||||
|
|
|
|||
|
|
@ -73,6 +73,96 @@
|
|||
(defn- openapi-path [path opts]
|
||||
(-> path (trie/normalize opts) (str/replace #"\{\*" "{")))
|
||||
|
||||
(defn -get-apidocs-openapi
|
||||
[coercion {:keys [request parameters responses content-types] :or {content-types ["application/json"]}}]
|
||||
(let [{:keys [body multipart]} parameters
|
||||
parameters (dissoc parameters :request :body :multipart)
|
||||
->content (fn [data schema]
|
||||
(merge
|
||||
{:schema schema}
|
||||
(select-keys data [:description :examples])
|
||||
(:openapi data)))
|
||||
->schema-object #(coercion/-get-model-apidocs coercion :openapi %1 %2)]
|
||||
(merge
|
||||
(when (seq parameters)
|
||||
{:parameters
|
||||
(->> (for [[in schema] parameters
|
||||
:let [{:keys [properties required]} (->schema-object schema {:in in :type :parameter})
|
||||
required? (partial contains? (set required))]
|
||||
[k schema] properties]
|
||||
(merge {:in (name in)
|
||||
:name k
|
||||
:required (required? k)
|
||||
:schema schema}
|
||||
(select-keys schema [:description])))
|
||||
(into []))})
|
||||
(when body
|
||||
;; body uses a single schema to describe every :requestBody
|
||||
;; the schema-object transformer should be able to transform into distinct content-types
|
||||
{:requestBody {:content (into {}
|
||||
(map (fn [content-type]
|
||||
(let [schema (->schema-object body {:in :requestBody
|
||||
:type :schema
|
||||
:content-type content-type})]
|
||||
[content-type {:schema schema}])))
|
||||
content-types)}})
|
||||
|
||||
(when request
|
||||
;; request allow to different :requestBody per content-type
|
||||
{:requestBody
|
||||
{:content (merge
|
||||
(select-keys request [:description])
|
||||
(when-let [{:keys [schema] :as data} (coercion/get-default request)]
|
||||
(into {}
|
||||
(map (fn [content-type]
|
||||
(let [schema (->schema-object schema {:in :requestBody
|
||||
:type :schema
|
||||
:content-type content-type})]
|
||||
[content-type (->content data schema)])))
|
||||
content-types))
|
||||
(into {}
|
||||
(map (fn [[content-type {:keys [schema] :as data}]]
|
||||
(let [schema (->schema-object schema {:in :requestBody
|
||||
:type :schema
|
||||
:content-type content-type})]
|
||||
[content-type (->content data schema)])))
|
||||
(:content request)))}})
|
||||
(when multipart
|
||||
{:requestBody
|
||||
{:content
|
||||
{"multipart/form-data"
|
||||
{:schema
|
||||
(->schema-object multipart {:in :requestBody
|
||||
:type :schema
|
||||
:content-type "multipart/form-data"})}}}})
|
||||
(when responses
|
||||
{:responses
|
||||
(into {}
|
||||
(map (fn [[status {:keys [content], :as response}]]
|
||||
(let [default (coercion/get-default-schema response)
|
||||
content (-> (merge
|
||||
(when default
|
||||
(into {}
|
||||
(map (fn [content-type]
|
||||
(let [schema (->schema-object default {:in :responses
|
||||
:type :schema
|
||||
:content-type content-type})]
|
||||
[content-type (->content nil schema)])))
|
||||
content-types))
|
||||
(when content
|
||||
(into {}
|
||||
(map (fn [[content-type {:keys [schema] :as data}]]
|
||||
(let [schema (->schema-object schema {:in :responses
|
||||
:type :schema
|
||||
:content-type content-type})]
|
||||
[content-type (->content data schema)])))
|
||||
content)))
|
||||
(dissoc :default))]
|
||||
[status (merge (select-keys response [:description])
|
||||
(when content
|
||||
{:content content}))]))
|
||||
responses))}))))
|
||||
|
||||
(defn create-openapi-handler
|
||||
"Stability: alpha
|
||||
|
||||
|
|
@ -99,7 +189,7 @@
|
|||
(apply meta-merge (keep (comp :openapi :data) middleware))
|
||||
(apply meta-merge (keep (comp :openapi :data) interceptors))
|
||||
(if coercion
|
||||
(coercion/get-apidocs coercion :openapi data))
|
||||
(-get-apidocs-openapi coercion data))
|
||||
(select-keys data [:tags :summary :description])
|
||||
(strip-top-level-keys openapi))]))
|
||||
transform-path (fn [[p _ c]]
|
||||
|
|
|
|||
|
|
@ -47,6 +47,13 @@
|
|||
(reify coercion/Coercion
|
||||
(-get-name [_] :schema)
|
||||
(-get-options [_] opts)
|
||||
(-get-model-apidocs [_ specification model options]
|
||||
(case specification
|
||||
:openapi (openapi/transform model (merge opts options))
|
||||
(throw
|
||||
(ex-info
|
||||
(str "Can't produce Schema apidocs for " specification)
|
||||
{:type specification, :coercion :schema}))))
|
||||
(-get-apidocs [_ specification {:keys [request parameters responses content-types]
|
||||
:or {content-types ["application/json"]}}]
|
||||
;; TODO: this looks identical to spec, refactor when schema is done.
|
||||
|
|
@ -63,42 +70,7 @@
|
|||
[k (-> response
|
||||
(dissoc :content)
|
||||
(set/rename-keys {:body :schema}))]))})))
|
||||
:openapi (merge
|
||||
(when (seq (dissoc parameters :body :request :multipart))
|
||||
(openapi/openapi-spec {::openapi/parameters (dissoc parameters :body :request)}))
|
||||
(when (:body parameters)
|
||||
{:requestBody (openapi/openapi-spec
|
||||
{::openapi/content (zipmap content-types (repeat (:body parameters)))})})
|
||||
(when request
|
||||
{:requestBody (openapi/openapi-spec
|
||||
{::openapi/content (merge
|
||||
(when-let [default (coercion/get-default-schema request)]
|
||||
(zipmap content-types (repeat default)))
|
||||
(->> (for [[content-type {:keys [schema]}] (:content request)]
|
||||
[content-type schema])
|
||||
(into {})))})})
|
||||
(when (:multipart parameters)
|
||||
{:requestBody
|
||||
(openapi/openapi-spec
|
||||
{::openapi/content {"multipart/form-data" (:multipart parameters)}})})
|
||||
(when responses
|
||||
{:responses
|
||||
(into
|
||||
(empty responses)
|
||||
(for [[k {:keys [content] :as response}] responses
|
||||
:let [default (coercion/get-default-schema response)]]
|
||||
[k (merge
|
||||
(select-keys response [:description])
|
||||
(when (or content default)
|
||||
(openapi/openapi-spec
|
||||
{::openapi/content (-> (merge
|
||||
(when default
|
||||
(zipmap content-types (repeat default)))
|
||||
(->> (for [[content-type {:keys [schema]}] content]
|
||||
[content-type schema])
|
||||
(into {})))
|
||||
(dissoc :default))})))]))}))
|
||||
|
||||
;; :openapi handled in reitit.openapi/-get-apidocs-openapi
|
||||
(throw
|
||||
(ex-info
|
||||
(str "Can't produce Schema apidocs for " specification)
|
||||
|
|
|
|||
|
|
@ -88,6 +88,13 @@
|
|||
(reify coercion/Coercion
|
||||
(-get-name [_] :spec)
|
||||
(-get-options [_] opts)
|
||||
(-get-model-apidocs [_ specification model options]
|
||||
(case specification
|
||||
:openapi (openapi/transform model (merge opts options))
|
||||
(throw
|
||||
(ex-info
|
||||
(str "Can't produce Spec apidocs for " specification)
|
||||
{:type specification, :coercion :spec}))))
|
||||
(-get-apidocs [_ specification {:keys [request parameters responses content-types]
|
||||
:or {content-types ["application/json"]}}]
|
||||
(case specification
|
||||
|
|
@ -103,42 +110,7 @@
|
|||
[k (as-> response $
|
||||
(dissoc $ :content)
|
||||
(set/rename-keys $ {:body :schema}))]))})))
|
||||
:openapi (merge
|
||||
(when (seq (dissoc parameters :body :request :multipart))
|
||||
(openapi/openapi-spec {::openapi/parameters (dissoc parameters :body :request)}))
|
||||
(when (:body parameters)
|
||||
{:requestBody (openapi/openapi-spec
|
||||
{::openapi/content (zipmap content-types (repeat (:body parameters)))})})
|
||||
(when request
|
||||
{:requestBody (openapi/openapi-spec
|
||||
{::openapi/content (merge
|
||||
(when-let [default (coercion/get-default-schema request)]
|
||||
(zipmap content-types (repeat default)))
|
||||
(->> (for [[content-type {:keys [schema]}] (:content request)]
|
||||
[content-type schema])
|
||||
(into {})))})})
|
||||
(when (:multipart parameters)
|
||||
{:requestBody
|
||||
(openapi/openapi-spec
|
||||
{::openapi/content {"multipart/form-data" (:multipart parameters)}})})
|
||||
(when responses
|
||||
{:responses
|
||||
(into
|
||||
(empty responses)
|
||||
(for [[k {:keys [content] :as response}] responses
|
||||
:let [default (coercion/get-default-schema response)
|
||||
content-types (remove #{:default} content-types)]]
|
||||
[k (merge
|
||||
(select-keys response [:description])
|
||||
(when (or content default)
|
||||
(openapi/openapi-spec
|
||||
{::openapi/content (-> (merge
|
||||
(when default
|
||||
(zipmap content-types (repeat default)))
|
||||
(->> (for [[content-type {:keys [schema]}] content]
|
||||
[content-type schema])
|
||||
(into {})))
|
||||
(dissoc :default))})))]))}))
|
||||
;; :openapi handled in reitit.openapi/-get-apidocs-openapi
|
||||
(throw
|
||||
(ex-info
|
||||
(str "Can't produce Spec apidocs for " specification)
|
||||
|
|
|
|||
|
|
@ -69,6 +69,30 @@
|
|||
(defn- swagger-path [path opts]
|
||||
(-> path (trie/normalize opts) (str/replace #"\{\*" "{")))
|
||||
|
||||
(defn -warn-unsupported-coercions [{:keys [request responses] :as _data}]
|
||||
(when request
|
||||
(println "WARNING [reitit.coercion]: swagger apidocs don't support :request coercion"))
|
||||
(when (some :content (vals responses))
|
||||
(println "WARNING [reitit.coercion]: swagger apidocs don't support :responses :content coercion")))
|
||||
|
||||
(defn -get-swagger-apidocs [coercion data]
|
||||
(let [swagger-parameter {:query :query
|
||||
:body :body
|
||||
:form :formData
|
||||
:header :header
|
||||
:path :path
|
||||
:multipart :formData}]
|
||||
(-warn-unsupported-coercions data)
|
||||
(->> (update
|
||||
data
|
||||
:parameters
|
||||
(fn [parameters]
|
||||
(->> parameters
|
||||
(map (fn [[k v]] [(swagger-parameter k) v]))
|
||||
(filter first)
|
||||
(into {}))))
|
||||
(coercion/-get-apidocs coercion :swagger))))
|
||||
|
||||
(defn create-swagger-handler
|
||||
"Create a ring handler to emit swagger spec. Collects all routes from router which have
|
||||
an intersecting `[:swagger :id]` and which are not marked with `:no-doc` route data."
|
||||
|
|
@ -95,7 +119,7 @@
|
|||
(apply meta-merge (keep (comp :swagger :data) middleware))
|
||||
(apply meta-merge (keep (comp :swagger :data) interceptors))
|
||||
(if coercion
|
||||
(coercion/get-apidocs coercion :swagger data))
|
||||
(-get-swagger-apidocs coercion data))
|
||||
(select-keys data [:tags :summary :description :operationId])
|
||||
(strip-top-level-keys swagger))]))
|
||||
transform-path (fn [[p _ c]]
|
||||
|
|
|
|||
|
|
@ -157,19 +157,16 @@
|
|||
:version "0.0.1"}
|
||||
:paths {"/api/spec/plus/{z}" {:get {:parameters [{:in "query"
|
||||
:name "x"
|
||||
:description ""
|
||||
:required true
|
||||
:schema {:type "integer"
|
||||
:format "int64"}}
|
||||
{:in "query"
|
||||
:name "y"
|
||||
:description ""
|
||||
:required true
|
||||
:schema {:type "integer"
|
||||
:format "int64"}}
|
||||
{:in "path"
|
||||
:name "z"
|
||||
:description ""
|
||||
:required true
|
||||
:schema {:type "integer"
|
||||
:format "int64"}}]
|
||||
|
|
@ -188,7 +185,6 @@
|
|||
:post {:parameters [{:in "path"
|
||||
:name "z"
|
||||
:required true
|
||||
:description ""
|
||||
:schema {:type "integer"
|
||||
:format "int64"}}]
|
||||
:requestBody {:content {"application/json" {:schema {:oneOf [{:items {:type "integer"
|
||||
|
|
@ -251,21 +247,18 @@
|
|||
:required [:error]
|
||||
:type "object"}}}}}
|
||||
:summary "plus with body"}}
|
||||
"/api/schema/plus/{z}" {:get {:parameters [{:description ""
|
||||
:in "query"
|
||||
"/api/schema/plus/{z}" {:get {:parameters [{:in "query"
|
||||
:name "x"
|
||||
:required true
|
||||
:schema {:format "int32"
|
||||
:type "integer"}}
|
||||
{:description ""
|
||||
:in "query"
|
||||
{:in "query"
|
||||
:name "y"
|
||||
:required true
|
||||
:schema {:type "integer"
|
||||
:format "int32"}}
|
||||
{:in "path"
|
||||
:name "z"
|
||||
:description ""
|
||||
:required true
|
||||
:schema {:type "integer"
|
||||
:format "int32"}}]
|
||||
|
|
@ -282,7 +275,6 @@
|
|||
:summary "plus"}
|
||||
:post {:parameters [{:in "path"
|
||||
:name "z"
|
||||
:description ""
|
||||
:required true
|
||||
:schema {:type "integer"
|
||||
:format "int32"}}]
|
||||
|
|
|
|||
Loading…
Reference in a new issue