mirror of
https://github.com/metosin/reitit.git
synced 2025-12-16 16:01:11 +00:00
welcome swagger-ui!
This commit is contained in:
parent
e137b8baec
commit
8a320164a4
10 changed files with 125 additions and 60 deletions
|
|
@ -1,6 +1,7 @@
|
|||
(ns example.server
|
||||
(:require [reitit.ring :as ring]
|
||||
[reitit.swagger :as swagger]
|
||||
[reitit.swagger-ui :as swagger-ui]
|
||||
[reitit.ring.coercion :as rrc]
|
||||
[reitit.coercion.spec :as spec]
|
||||
[reitit.coercion.schema :as schema]
|
||||
|
|
@ -50,9 +51,15 @@
|
|||
swagger/swagger-feature
|
||||
rrc/coerce-exceptions-middleware
|
||||
rrc/coerce-request-middleware
|
||||
rrc/coerce-response-middleware]}})
|
||||
rrc/coerce-response-middleware]
|
||||
:swagger {:produces #{"application/json"
|
||||
"application/edn"
|
||||
"application/transit+json"}
|
||||
:consumes #{"application/json"
|
||||
"application/edn"
|
||||
"application/transit+json"}}}})
|
||||
(ring/routes
|
||||
(swagger/create-swagger-ui-handler
|
||||
(swagger-ui/create-swagger-ui-handler
|
||||
{:path "", :url "/api/swagger.json"})
|
||||
(ring/create-default-handler))))
|
||||
|
||||
|
|
|
|||
11
modules/reitit-swagger-ui/project.clj
Normal file
11
modules/reitit-swagger-ui/project.clj
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
(defproject metosin/reitit-swagger-ui "0.1.1-SNAPSHOT"
|
||||
:description "Reitit: Swagger-ui support"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
:url "http://www.eclipse.org/legal/epl-v10.html"}
|
||||
:plugins [[lein-parent "0.3.2"]]
|
||||
:parent-project {:path "../../project.clj"
|
||||
:inherit [:deploy-repositories :managed-dependencies]}
|
||||
:dependencies [[metosin/reitit-ring]
|
||||
[metosin/jsonista]
|
||||
[metosin/ring-swagger-ui]])
|
||||
52
modules/reitit-swagger-ui/src/reitit/swagger_ui.cljc
Normal file
52
modules/reitit-swagger-ui/src/reitit/swagger_ui.cljc
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
(ns reitit.swagger-ui
|
||||
(:require [clojure.string :as str]
|
||||
[reitit.ring :as ring]
|
||||
#?@(:clj [
|
||||
[jsonista.core :as j]])))
|
||||
|
||||
#?(:clj
|
||||
(defn create-swagger-ui-handler
|
||||
"Creates a ring handler which can be used to serve swagger-ui.
|
||||
|
||||
| key | description |
|
||||
| -----------------|-------------|
|
||||
| :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:`
|
||||
| :root | optional resource root, defaults to `\"swagger-ui\"`
|
||||
| :url | path to swagger endpoint, defaults to `/swagger.json`
|
||||
| :path | optional path to mount the handler to. Works only if mounted outside of a router.
|
||||
| :config | parameters passed to swaggger-ui, keys transformed into camelCase.
|
||||
|
||||
See https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md
|
||||
for all available :config options
|
||||
|
||||
Examples:
|
||||
|
||||
;; with defaults
|
||||
(create-swagger-ui-handler)
|
||||
|
||||
;; with path and url set, swagger validator disabled
|
||||
(swagger-ui/create-swagger-ui-handler
|
||||
{:path \"\"
|
||||
:url \"/api/swagger.json\"
|
||||
:config {:validator-url nil})"
|
||||
([]
|
||||
(create-swagger-ui-handler nil))
|
||||
([options]
|
||||
(let [mixed-case (fn [k]
|
||||
(let [[f & rest] (str/split (name k) #"-")]
|
||||
(apply str (str/lower-case f) (map str/capitalize rest))))
|
||||
mixed-case-key (fn [[k v]] [(mixed-case k) v])
|
||||
config-json (fn [{:keys [url config]}] (j/write-value-as-string (merge config {:url url})))
|
||||
conf-js (fn [opts] (str "window.API_CONF = " (config-json opts) ";"))
|
||||
options (as-> options $
|
||||
(update $ :root (fnil identity "swagger-ui"))
|
||||
(update $ :url (fnil identity "/swagger.json"))
|
||||
(update $ :config #(->> % (map mixed-case-key) (into {})))
|
||||
(assoc $ :paths {"conf.js" {:headers {"Content-Type" "application/javascript"}
|
||||
:status 200
|
||||
:body (conf-js $)}
|
||||
"config.json" {:headers {"Content-Type" "application/json"}
|
||||
:status 200
|
||||
:body (config-json $)}}))]
|
||||
(ring/routes
|
||||
(ring/create-resource-handler options))))))
|
||||
|
|
@ -6,6 +6,4 @@
|
|||
:plugins [[lein-parent "0.3.2"]]
|
||||
:parent-project {:path "../../project.clj"
|
||||
:inherit [:deploy-repositories :managed-dependencies]}
|
||||
:dependencies [[metosin/reitit-ring]
|
||||
[metosin/jsonista]
|
||||
[metosin/ring-swagger-ui]])
|
||||
:dependencies [[metosin/reitit-core]])
|
||||
|
|
|
|||
|
|
@ -3,13 +3,9 @@
|
|||
[meta-merge.core :refer [meta-merge]]
|
||||
[clojure.spec.alpha :as s]
|
||||
[clojure.set :as set]
|
||||
[clojure.string :as str]
|
||||
[reitit.ring :as ring]
|
||||
[reitit.coercion :as coercion]
|
||||
#?@(:clj [
|
||||
[jsonista.core :as j]])))
|
||||
[reitit.coercion :as coercion]))
|
||||
|
||||
(s/def ::id keyword?)
|
||||
(s/def ::id (s/or :keyword keyword? :set (s/coll-of keyword? :into #{})))
|
||||
(s/def ::no-doc boolean?)
|
||||
(s/def ::tags (s/coll-of (s/or :keyword keyword? :string string?) :kind #{}))
|
||||
(s/def ::summary string?)
|
||||
|
|
@ -25,16 +21,11 @@
|
|||
documentation keys for the route data. Should be accompanied by a
|
||||
[[swagger-spec-handler]] to expose the swagger spec.
|
||||
|
||||
Swagger-specific keys:
|
||||
|
||||
| key | description |
|
||||
| --------------|-------------|
|
||||
| :swagger | map of any swagger-data. Must have `:id` to identify the api
|
||||
|
||||
The following common keys also contribute to swagger spec:
|
||||
New route data keys contributing to swagger docs:
|
||||
|
||||
| key | description |
|
||||
| --------------|-------------|
|
||||
| :swagger | map of any swagger-data. Must have `:id` (keyword or sequence of keywords) to identify the api
|
||||
| :no-doc | optional boolean to exclude endpoint from api docs
|
||||
| :tags | optional set of strings of keywords tags for an endpoint api docs
|
||||
| :summary | optional short string summary of an endpoint
|
||||
|
|
@ -42,6 +33,8 @@
|
|||
|
||||
Also the coercion keys contribute to swagger spec:
|
||||
|
||||
| key | description |
|
||||
| --------------|-------------|
|
||||
| :parameters | optional input parameters for a route, in a format defined by the coercion
|
||||
| :responses | optional descriptions of responess, in a format defined by coercion
|
||||
|
||||
|
|
@ -70,12 +63,16 @@
|
|||
:spec ::spec})
|
||||
|
||||
(defn create-swagger-handler []
|
||||
"Create a ring handler to emit swagger spec."
|
||||
"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."
|
||||
(fn [{:keys [::r/router ::r/match :request-method]}]
|
||||
(let [{:keys [id] :as swagger} (-> match :result request-method :data :swagger)
|
||||
swagger (->> (set/rename-keys swagger {:id :x-id})
|
||||
(merge {:swagger "2.0"}))
|
||||
accept-route #(-> % second :swagger :id (= id))
|
||||
->set (fn [x] (if (or (set? x) (sequential? x)) (set x) (conj #{} x)))
|
||||
ids (->set id)
|
||||
swagger (->> (dissoc swagger :id)
|
||||
(merge {:swagger "2.0"
|
||||
:x-id ids}))
|
||||
accept-route #(-> % second :swagger :id ->set (set/intersection ids) seq)
|
||||
transform-endpoint (fn [[method {{:keys [coercion no-doc swagger] :as data} :data}]]
|
||||
(if (and data (not no-doc))
|
||||
[method
|
||||
|
|
@ -91,36 +88,3 @@
|
|||
(let [paths (->> router (r/routes) (filter accept-route) (map transform-path) (into {}))]
|
||||
{:status 200
|
||||
:body (meta-merge swagger {:paths paths})})))))
|
||||
|
||||
#?(:clj
|
||||
(defn create-swagger-ui-handler
|
||||
"Creates a ring handler which can be used to serve swagger-ui.
|
||||
|
||||
| key | description |
|
||||
| -----------------|-------------|
|
||||
| :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:`
|
||||
| :root | optional resource root, defaults to `\"swagger-ui\"`
|
||||
| :url | path to swagger endpoint, defaults to `/swagger.json`
|
||||
| :path | optional path to mount the handler to. Works only if mounted outside of a router.
|
||||
| :config | parameters passed to swaggger-ui, keys transformed into camelCase."
|
||||
([]
|
||||
(create-swagger-ui-handler nil))
|
||||
([options]
|
||||
(let [mixed-case (fn [k]
|
||||
(let [[f & rest] (str/split (name k) #"-")]
|
||||
(apply str (str/lower-case f) (map str/capitalize rest))))
|
||||
mixed-case-key (fn [[k v]] [(mixed-case k) v])
|
||||
config-json (fn [{:keys [url config]}] (j/write-value-as-string (merge config {:url url})))
|
||||
conf-js (fn [opts] (str "window.API_CONF = " (config-json opts) ";"))
|
||||
options (as-> options $
|
||||
(update $ :root (fnil identity "swagger-ui"))
|
||||
(update $ :url (fnil identity "/swagger.json"))
|
||||
(update $ :config #(->> % (map mixed-case-key) (into {})))
|
||||
(assoc $ :paths {"conf.js" {:headers {"Content-Type" "application/javascript"}
|
||||
:status 200
|
||||
:body (conf-js $)}
|
||||
"config.json" {:headers {"Content-Type" "application/json"}
|
||||
:status 200
|
||||
:body (config-json $)}}))]
|
||||
(ring/routes
|
||||
(ring/create-resource-handler options))))))
|
||||
|
|
|
|||
|
|
@ -10,4 +10,5 @@
|
|||
[metosin/reitit-ring]
|
||||
[metosin/reitit-spec]
|
||||
[metosin/reitit-schema]
|
||||
[metosin/reitit-swagger]])
|
||||
[metosin/reitit-swagger]
|
||||
[metosin/reitit-swagger-ui]])
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
[metosin/reitit-spec "0.1.1-SNAPSHOT"]
|
||||
[metosin/reitit-schema "0.1.1-SNAPSHOT"]
|
||||
[metosin/reitit-swagger "0.1.1-SNAPSHOT"]
|
||||
[metosin/reitit-swagger-ui "0.1.1-SNAPSHOT"]
|
||||
|
||||
[meta-merge "1.0.0"]
|
||||
[ring/ring-core "1.6.3"]
|
||||
|
|
@ -38,7 +39,8 @@
|
|||
"modules/reitit-ring/src"
|
||||
"modules/reitit-spec/src"
|
||||
"modules/reitit-schema/src"
|
||||
"modules/reitit-swagger/src"]
|
||||
"modules/reitit-swagger/src"
|
||||
"modules/reitit-swagger-ui/src"]
|
||||
|
||||
:dependencies [[org.clojure/clojure "1.9.0"]
|
||||
[org.clojure/clojurescript "1.10.238"]
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
set -e
|
||||
|
||||
# Modules
|
||||
for ext in reitit-core reitit-ring reitit-spec reitit-schema reitit-swagger reitit; do
|
||||
for ext in reitit-core reitit-ring reitit-spec reitit-schema reitit-swagger reitit-swagger-ui reitit; do
|
||||
cd modules/$ext; lein "$@"; cd ../..;
|
||||
done
|
||||
|
|
|
|||
|
|
@ -84,11 +84,11 @@
|
|||
(app valid-request))))
|
||||
|
||||
(testing "invalid request"
|
||||
(let [{:keys [status body]} (app invalid-request)]
|
||||
(let [{:keys [status]} (app invalid-request)]
|
||||
(is (= 400 status))))
|
||||
|
||||
(testing "invalid response"
|
||||
(let [{:keys [status body]} (app invalid-request2)]
|
||||
(let [{:keys [status]} (app invalid-request2)]
|
||||
(is (= 500 status))))))))
|
||||
|
||||
(deftest schema-coercion-test
|
||||
|
|
|
|||
|
|
@ -98,3 +98,33 @@
|
|||
:type "object"}}}
|
||||
:summary "plus"}}}}
|
||||
spec)))))
|
||||
|
||||
(deftest multiple-swagger-apis-test
|
||||
(let [ping-route ["/ping" {:get (constantly "ping")}]
|
||||
spec-route ["/swagger.json"
|
||||
{:get {:no-doc true
|
||||
:handler (swagger/create-swagger-handler)}}]
|
||||
app (ring/ring-handler
|
||||
(ring/router
|
||||
[["/common" {:swagger {:id #{::one ::two}}}
|
||||
ping-route]
|
||||
|
||||
["/one" {:swagger {:id ::one}}
|
||||
ping-route
|
||||
spec-route]
|
||||
|
||||
["/two" {:swagger {:id ::two}}
|
||||
ping-route
|
||||
spec-route
|
||||
["/deep" {:swagger {:id ::one}}
|
||||
ping-route]]
|
||||
["/one-two" {:swagger {:id #{::one ::two}}}
|
||||
spec-route]]))
|
||||
spec-paths (fn [uri]
|
||||
(-> {:request-method :get, :uri uri} app :body :paths keys))]
|
||||
(is (= ["/common/ping" "/one/ping" "/two/deep/ping"]
|
||||
(spec-paths "/one/swagger.json")))
|
||||
(is (= ["/common/ping" "/two/ping"]
|
||||
(spec-paths "/two/swagger.json")))
|
||||
(is (= ["/common/ping" "/one/ping" "/two/ping" "/two/deep/ping"]
|
||||
(spec-paths "/one-two/swagger.json")))))
|
||||
|
|
|
|||
Loading…
Reference in a new issue