Skip to content

Commit

Permalink
welcome swagger-ui!
Browse files Browse the repository at this point in the history
  • Loading branch information
ikitommi committed May 14, 2018
1 parent e137b8b commit 8a32016
Show file tree
Hide file tree
Showing 10 changed files with 125 additions and 60 deletions.
11 changes: 9 additions & 2 deletions examples/ring-swagger/src/example/server.clj
Original file line number Diff line number Diff line change
@@ -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]
Expand Down Expand Up @@ -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))))

Expand Down
11 changes: 11 additions & 0 deletions modules/reitit-swagger-ui/project.clj
Original file line number Diff line number Diff line change
@@ -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 changes: 52 additions & 0 deletions modules/reitit-swagger-ui/src/reitit/swagger_ui.cljc
Original file line number Diff line number Diff line change
@@ -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))))))
4 changes: 1 addition & 3 deletions modules/reitit-swagger/project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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]])
64 changes: 14 additions & 50 deletions modules/reitit-swagger/src/reitit/swagger.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -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?)
Expand All @@ -25,23 +21,20 @@
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
| :description | optional long description of an endpoint. Supports http://spec.commonmark.org/
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
Expand Down Expand Up @@ -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
Expand All @@ -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))))))
3 changes: 2 additions & 1 deletion modules/reitit/project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@
[metosin/reitit-ring]
[metosin/reitit-spec]
[metosin/reitit-schema]
[metosin/reitit-swagger]])
[metosin/reitit-swagger]
[metosin/reitit-swagger-ui]])
4 changes: 3 additions & 1 deletion project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand All @@ -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"]
Expand Down
2 changes: 1 addition & 1 deletion scripts/lein-modules
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 2 additions & 2 deletions test/cljc/reitit/ring_coercion_test.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 30 additions & 0 deletions test/cljc/reitit/swagger_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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")))))

0 comments on commit 8a32016

Please sign in to comment.