Liking cljdoc? Tell your friends :D

Swagger Support

[metosin/reitit-swagger "0.1.3"]

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:

  1. annotate you routes with swagger-data
  2. mount a swagger-handler to serve the swagger-spec
  3. optionally mount a swagger-ui to visualize the swagger-spec

Swagger data

The following route data keys contribute to the generated swagger specification:

keydescription
:swaggermap of any swagger-data. Must have :id (keyword or sequence of keywords) to identify the api
:no-docoptional boolean to exclude endpoint from api docs
:tagsoptional set of strings of keywords tags for an endpoint api docs
:summaryoptional short string summary of an endpoint
:descriptionoptional long description of an endpoint. Supports http://spec.commonmark.org/

Coercion keys also contribute to the docs:

keydescription
:parametersoptional input parameters for a route, in a format defined by the coercion
:responsesoptional 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 specification 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 specification. To make things easy, there is a pre-integrated version of the swagger-ui as a separate module.

[metosin/reitit-swagger-ui "0.1.3"]

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:

keydescription
:parameteroptional name of the wildcard parameter, defaults to unnamed keyword :
:rootoptional resource root, defaults to "swagger-ui"
:urlpath to swagger endpoint, defaults to /swagger.json
:pathoptional path to mount the handler to. Works only if mounted outside of a router.
:configparameters 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.spec and Schema coercion
  • swagger data (:tags, :produces, :consumes)
  • swagger-spec served from "/api/swagger.json"
  • swagger-ui mounted to "/"
  • Muuntaja for request & response formatting
  • wrap-params to 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:

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 ::one and ::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")

TODO

  • create a data-driven version of Muuntaja that integrates into :produces and :consumes
  • ClojureScript
    • example for Macchiato
    • body formatting
    • resource handling

Can you improve this documentation? These fine people already did:
Tommi Reiman, Kanwei Li & Martin Klepsch
Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close