mirror of
https://github.com/metosin/reitit.git
synced 2025-12-18 08:51:12 +00:00
Support operationId in reitit-swagger
OpenAPI Specification allows the operationId to be added to the "Operation Object" alongside e.g. summary and description. This commit introduces the support of this element in the reitit-swagger module and extends the tests. One test shows the correct use of operationId where both are distinct and one shows the failing of the swagger creation when the IDs are not distinct. - Spec: https://swagger.io/specification/#operation-object - Adds the support for operationId - Adds operationId in two places of the swagger test - Adds a test that checks exception on duplicate IDs - Closes #451
This commit is contained in:
parent
3a6985eb71
commit
e095cd2efa
2 changed files with 50 additions and 5 deletions
|
|
@ -12,9 +12,11 @@
|
||||||
(s/def ::tags (s/coll-of (s/or :keyword keyword? :string string?) :kind #{}))
|
(s/def ::tags (s/coll-of (s/or :keyword keyword? :string string?) :kind #{}))
|
||||||
(s/def ::summary string?)
|
(s/def ::summary string?)
|
||||||
(s/def ::description string?)
|
(s/def ::description string?)
|
||||||
|
(s/def ::operationId string?)
|
||||||
|
(s/def ::operationIds (s/coll-of ::operationId :distinct true))
|
||||||
|
|
||||||
(s/def ::swagger (s/keys :opt-un [::id]))
|
(s/def ::swagger (s/keys :opt-un [::id]))
|
||||||
(s/def ::spec (s/keys :opt-un [::swagger ::no-doc ::tags ::summary ::description]))
|
(s/def ::spec (s/keys :opt-un [::swagger ::no-doc ::tags ::summary ::description ::operationId]))
|
||||||
|
|
||||||
(def swagger-feature
|
(def swagger-feature
|
||||||
"Feature for handling swagger-documentation for routes.
|
"Feature for handling swagger-documentation for routes.
|
||||||
|
|
@ -75,13 +77,14 @@
|
||||||
(let [{:keys [id] :or {id ::default} :as swagger} (-> match :result request-method :data :swagger)
|
(let [{:keys [id] :or {id ::default} :as swagger} (-> match :result request-method :data :swagger)
|
||||||
ids (trie/into-set id)
|
ids (trie/into-set id)
|
||||||
strip-top-level-keys #(dissoc % :id :info :host :basePath :definitions :securityDefinitions)
|
strip-top-level-keys #(dissoc % :id :info :host :basePath :definitions :securityDefinitions)
|
||||||
strip-endpoint-keys #(dissoc % :id :parameters :responses :summary :description)
|
strip-endpoint-keys #(dissoc % :id :parameters :responses :summary :description :operationId)
|
||||||
swagger (->> (strip-endpoint-keys swagger)
|
swagger (->> (strip-endpoint-keys swagger)
|
||||||
(merge {:swagger "2.0"
|
(merge {:swagger "2.0"
|
||||||
:x-id ids}))
|
:x-id ids}))
|
||||||
accept-route (fn [route]
|
accept-route (fn [route]
|
||||||
(-> route second :swagger :id (or ::default) (trie/into-set) (set/intersection ids) seq))
|
(-> route second :swagger :id (or ::default) (trie/into-set) (set/intersection ids) seq))
|
||||||
base-swagger-spec {:responses ^:displace {:default {:description ""}}}
|
base-swagger-spec {:responses ^:displace {:default {:description ""}}}
|
||||||
|
oid-acc (atom [])
|
||||||
transform-endpoint (fn [[method {{:keys [coercion no-doc swagger] :as data} :data
|
transform-endpoint (fn [[method {{:keys [coercion no-doc swagger] :as data} :data
|
||||||
middleware :middleware
|
middleware :middleware
|
||||||
interceptors :interceptors}]]
|
interceptors :interceptors}]]
|
||||||
|
|
@ -94,12 +97,20 @@
|
||||||
(if coercion
|
(if coercion
|
||||||
(coercion/get-apidocs coercion :swagger data))
|
(coercion/get-apidocs coercion :swagger data))
|
||||||
(select-keys data [:tags :summary :description])
|
(select-keys data [:tags :summary :description])
|
||||||
|
(let [oid (select-keys data [:operationId])
|
||||||
|
oid-val (:operationId oid)
|
||||||
|
_ (when (not (nil? oid-val))
|
||||||
|
(reset! oid-acc (conj @oid-acc oid-val)))]
|
||||||
|
oid)
|
||||||
(strip-top-level-keys swagger))]))
|
(strip-top-level-keys swagger))]))
|
||||||
transform-path (fn [[p _ c]]
|
transform-path (fn [[p _ c]]
|
||||||
(if-let [endpoint (some->> c (keep transform-endpoint) (seq) (into {}))]
|
(if-let [endpoint (some->> c (keep transform-endpoint) (seq) (into {}))]
|
||||||
[(swagger-path p (r/options router)) endpoint]))
|
[(swagger-path p (r/options router)) endpoint]))
|
||||||
map-in-order #(->> % (apply concat) (apply array-map))
|
map-in-order #(->> % (apply concat) (apply array-map))
|
||||||
paths (->> router (r/compiled-routes) (filter accept-route) (map transform-path) map-in-order)]
|
paths (->> router (r/compiled-routes) (filter accept-route) (map transform-path) map-in-order)
|
||||||
|
_ (when (not (s/valid? ::operationIds @oid-acc))
|
||||||
|
(throw (ex-info (s/explain-str ::operationIds @oid-acc) {:operation-ids @oid-acc
|
||||||
|
:error "operationIds are not distinct"})))]
|
||||||
{:status 200
|
{:status 200
|
||||||
:body (meta-merge swagger {:paths paths})}))
|
:body (meta-merge swagger {:paths paths})}))
|
||||||
([req res raise]
|
([req res raise]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
(ns reitit.swagger-test
|
(ns reitit.swagger-test
|
||||||
(:require [clojure.test :refer [deftest is testing]]
|
(:require [clojure.test :refer :all]
|
||||||
[reitit.ring :as ring]
|
[reitit.ring :as ring]
|
||||||
[reitit.swagger :as swagger]
|
[reitit.swagger :as swagger]
|
||||||
[reitit.swagger-ui :as swagger-ui]
|
[reitit.swagger-ui :as swagger-ui]
|
||||||
|
|
@ -25,11 +25,13 @@
|
||||||
["/spec" {:coercion spec/coercion}
|
["/spec" {:coercion spec/coercion}
|
||||||
["/plus/:z"
|
["/plus/:z"
|
||||||
{:patch {:summary "patch"
|
{:patch {:summary "patch"
|
||||||
|
:operationId "Patch"
|
||||||
:handler (constantly {:status 200})}
|
:handler (constantly {:status 200})}
|
||||||
:options {:summary "options"
|
:options {:summary "options"
|
||||||
:middleware [{:data {:swagger {:responses {200 {:description "200"}}}}}]
|
:middleware [{:data {:swagger {:responses {200 {:description "200"}}}}}]
|
||||||
:handler (constantly {:status 200})}
|
:handler (constantly {:status 200})}
|
||||||
:get {:summary "plus"
|
:get {:summary "plus"
|
||||||
|
:operationId "GetPlus"
|
||||||
:parameters {:query {:x int?, :y int?}
|
:parameters {:query {:x int?, :y int?}
|
||||||
:path {:z int?}}
|
:path {:z int?}}
|
||||||
:swagger {:responses {400 {:schema {:type "string"}
|
:swagger {:responses {400 {:schema {:type "string"}
|
||||||
|
|
@ -101,6 +103,32 @@
|
||||||
rrc/coerce-request-middleware
|
rrc/coerce-request-middleware
|
||||||
rrc/coerce-response-middleware]}})))
|
rrc/coerce-response-middleware]}})))
|
||||||
|
|
||||||
|
(def failing-app
|
||||||
|
(ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
["/api"
|
||||||
|
{:swagger {:id ::math}}
|
||||||
|
|
||||||
|
["/swagger.json"
|
||||||
|
{:get {:no-doc true
|
||||||
|
:swagger {:info {:title "my-api"}}
|
||||||
|
:handler (swagger/create-swagger-handler)}}]
|
||||||
|
|
||||||
|
["/spec" {:coercion spec/coercion}
|
||||||
|
["/plus/:z"
|
||||||
|
{:patch {:summary "patch"
|
||||||
|
:operationId "Patch"
|
||||||
|
:handler (constantly {:status 200})}
|
||||||
|
:options {:summary "options"
|
||||||
|
:operationId "Patch"
|
||||||
|
:middleware [{:data {:swagger {:responses {200 {:description "200"}}}}}]
|
||||||
|
:handler (constantly {:status 200})}}]]]
|
||||||
|
|
||||||
|
{:data {:middleware [swagger/swagger-feature
|
||||||
|
rrc/coerce-exceptions-middleware
|
||||||
|
rrc/coerce-request-middleware
|
||||||
|
rrc/coerce-response-middleware]}})))
|
||||||
|
|
||||||
(require '[fipp.edn])
|
(require '[fipp.edn])
|
||||||
(deftest swagger-test
|
(deftest swagger-test
|
||||||
(testing "endpoints work"
|
(testing "endpoints work"
|
||||||
|
|
@ -118,6 +146,10 @@
|
||||||
(app {:request-method :get
|
(app {:request-method :get
|
||||||
:uri "/api/schema/plus/3"
|
:uri "/api/schema/plus/3"
|
||||||
:query-params {:x "2", :y "1"}})))))
|
:query-params {:x "2", :y "1"}})))))
|
||||||
|
|
||||||
|
(testing "failing swagger-spec"
|
||||||
|
(is (thrown? clojure.lang.ExceptionInfo (:body (failing-app {:request-method :get
|
||||||
|
:uri "/api/swagger.json"})))))
|
||||||
(testing "swagger-spec"
|
(testing "swagger-spec"
|
||||||
(let [spec (:body (app {:request-method :get
|
(let [spec (:body (app {:request-method :get
|
||||||
:uri "/api/swagger.json"}))
|
:uri "/api/swagger.json"}))
|
||||||
|
|
@ -126,6 +158,7 @@
|
||||||
:info {:title "my-api"}
|
:info {:title "my-api"}
|
||||||
:paths {"/api/spec/plus/{z}" {:patch {:parameters []
|
:paths {"/api/spec/plus/{z}" {:patch {:parameters []
|
||||||
:summary "patch"
|
:summary "patch"
|
||||||
|
:operationId "Patch"
|
||||||
:responses {:default {:description ""}}}
|
:responses {:default {:description ""}}}
|
||||||
:options {:parameters []
|
:options {:parameters []
|
||||||
:summary "options"
|
:summary "options"
|
||||||
|
|
@ -156,7 +189,8 @@
|
||||||
400 {:schema {:type "string"}
|
400 {:schema {:type "string"}
|
||||||
:description "kosh"}
|
:description "kosh"}
|
||||||
500 {:description "fail"}}
|
500 {:description "fail"}}
|
||||||
:summary "plus"}
|
:summary "plus"
|
||||||
|
:operationId "GetPlus"}
|
||||||
:post {:parameters [{:in "body",
|
:post {:parameters [{:in "body",
|
||||||
:name "body",
|
:name "body",
|
||||||
:description "",
|
:description "",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue