Swagger Support
+[metosin/reitit-swagger "0.1.1-SNAPSHOT"]
+Reitit supports Swagger2 documentation, thanks to schema-tools and spec-tools. Documentation is extracted from route definitions, coercion :parameters and :responses and from a set of new documentation keys.
To enable swagger-documentation for a ring-router:
+-
+
- annotate you routes with swagger-data +
- mount a swagger-handler to serve the swagger-spec +
- optionally mount a swagger-ui to visualize the swagger-spec +
Swagger data
+The following route data keys contribute to the generated swagger specification:
+| 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/ | +
Coercion keys also contribute to the docs:
+| 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 | +
There is a reitit.swagger.swagger-feature, which acts as both a Middleware and an Interceptor that is not participating in any request processing - it just defines the route data specs for the routes it's mounted to. It is only needed if the route data validation is turned on.
Swagger spec
+To serve the actual Swagger Specification, there is reitit.swagger/create-swagger-handler. It takes no arguments and returns a ring-handler which collects at request-time data from all routes for the same swagger api and returns a formatted Swagger spesification as Clojure data, to be encoded by a response formatter.
If you need to post-process the generated spec, just wrap the handler with a custom Middleware or an Interceptor.
Swagger-ui
+Swagger-ui is a user interface to visualize and interact with the Swagger spesification. To make things easy, there is a pre-integrated version of the swagger-ui as a separate module.
+[metosin/reitit-swagger-ui "0.1.1-SNAPSHOT"]
+reitit.swagger-ui/create-swagger-ui-hander can be used to create a ring-handler to serve the swagger-ui. It accepts the following options:
| 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 the docs | +
We use swagger-ui from ring-swagger-ui, which can be easily configured from routing application. It stores files swagger-ui in the resource classpath.
Webjars also hosts a version of the swagger-ui.
+NOTE: Currently, swagger-ui module is just for Clojure. ClojureScript-support welcome as a PR!
+Examples
+Simple example
+-
+
- two routes in a single swagger-api
::api
+ - swagger-spec served from
"/swagger.json"
+ - swagger-ui mounted to
"/"
+
(require '[reitit.ring :as ring])
+(require '[reitit.swagger :as swagger])
+(require '[reitit.swagger-ui :as swagger-ui])
+
+(def app
+ (ring/ring-handler
+ (ring/router
+ [["/api"
+ ["/ping" {:get (constantly "ping")}]
+ ["/pong" {:post (constantly "pong")}]]
+ ["/swagger.json"
+ {:get {:no-doc true
+ :handler (swagger/create-swagger-handler)}}]]
+ {:data {:swagger {:id ::api}}}) ;; for all routes
+ (swagger-ui/create-swagger-ui-handler {:path "/"})))
+
+The generated swagger spec:
+(app {:request-method :get :uri "/swagger.json"})
+;{:status 200
+; :body {:swagger "2.0"
+; :x-id #{:user/api}
+; :paths {"/api/ping" {:get {}}
+; "/api/pong" {:post {}}}}}
+
+Swagger-ui:
+(app {:request-method :get :uri "/"})
+; ... the swagger-ui index-page, configured correctly
+
+More complete example
+-
+
clojure.specandSchemacoercion
+- swagger data (
:tags,:produces,:consumes)
+ - swagger-spec served from
"/api/swagger.json"
+ - swagger-ui mounted to
"/"
+ - Muuntaja for request & response formatting +
wrap-paramsto capture query & path parameters
+- missed routes are handled by
create-default-handler
+ - served via ring-jetty +
Whole example project is in /examples/ring-swagger.
(require '[reitit.ring :as ring]
+(require '[reitit.swagger :as swagger]
+(require '[reitit.swagger-ui :as swagger-ui]
+;; coercion
+(require '[reitit.ring.coercion :as rrc]
+(require '[reitit.coercion.spec :as spec]
+(require '[reitit.coercion.schema :as schema]
+(require '[schema.core :refer [Int]]
+;; web server
+(require '[ring.adapter.jetty :as jetty]
+(require '[ring.middleware.params]
+(require '[muuntaja.middleware]))
+
+(def 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
+ :swagger {:tags ["spec"]}}
+
+ ["/plus"
+ {:get {:summary "plus with spec"
+ :parameters {:query {:x int?, :y int?}}
+ :responses {200 {:body {:total int?}}}
+ :handler (fn [{{{:keys [x y]} :query} :parameters}]
+ {:status 200
+ :body {:total (+ x y)}})}}]]
+
+ ["/schema"
+ {:coercion schema/coercion
+ :swagger {:tags ["schema"]}}
+
+ ["/plus"
+ {:get {:summary "plus with schema"
+ :parameters {:query {:x Int, :y Int}}
+ :responses {200 {:body {:total Int}}}
+ :handler (fn [{{{:keys [x y]} :query} :parameters}]
+ {:status 200
+ :body {:total (+ x y)}})}}]]]
+
+ {:data {:middleware [ring.middleware.params/wrap-params
+ muuntaja.middleware/wrap-format
+ swagger/swagger-feature
+ rrc/coerce-exceptions-middleware
+ rrc/coerce-request-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-ui/create-swagger-ui-handler
+ {:path "", :url "/api/swagger.json"})
+ (ring/create-default-handler))))
+
+(defn start []
+ (jetty/run-jetty #'app {:port 3000, :join? false})
+ (println "server running in port 3000"))
+
+http://localhost:3000 should render now the swagger-ui:
+
Advanced
+Route data in path [:swagger :id] can be either a keyword or a sequence of keywords. This enables one route to be part of multiple swagger apis. Normal route data scoping rules rules apply.
Example with:
+-
+
- 4 routes +
- 2 swagger apis
::oneand::two
+ - 3 swagger specs +
(require '[reitit.ring :as ring])
+(require '[reitit.swagger :as swagger])
+
+(def ping-route
+ ["/ping" {:get (constantly "ping")}])
+
+(def spec-route
+ ["/swagger.json"
+ {:get {:no-doc true
+ :handler (swagger/create-swagger-handler)}}])
+
+(def 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]])))
+
+(-> {:request-method :get, :uri "/one/swagger.json"} app :body :paths keys)
+; ("/common/ping" "/one/ping" "/two/deep/ping")
+
+(-> {:request-method :get, :uri "/two/swagger.json"} app :body :paths keys)
+; ("/common/ping" "/two/ping")
+
+(-> {:request-method :get, :uri "/one-two/swagger.json"} app :body :paths keys)
+; ("/common/ping" "/one/ping" "/two/ping" "/two/deep/ping")
+
+