2018-05-14 05:22:06 +00:00
# Swagger Support
```
2018-09-03 16:44:59 +00:00
[metosin/reitit-swagger "0.2.0"]
2018-05-14 05:22:06 +00:00
```
Reitit supports [Swagger2 ](https://swagger.io/ ) documentation, thanks to [schema-tools ](https://github.com/metosin/schema-tools ) and [spec-tools ](https://github.com/metosin/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:
| key | description |
| --------------|-------------|
2018-07-21 06:38:44 +00:00
| :swagger | map of any swagger-data. Can have `:id` (keyword or sequence of keywords) to identify the api
2018-05-14 05:22:06 +00:00
| :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
2018-05-20 10:04:48 +00:00
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 ](route_data_validation.md ) is turned on.
2018-05-14 05:22:06 +00:00
## Swagger spec
2018-06-07 22:17:33 +00:00
To serve the actual [Swagger Specification ](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md ), 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.
2018-05-14 05:22:06 +00:00
If you need to post-process the generated spec, just wrap the handler with a custom `Middleware` or an `Interceptor` .
## Swagger-ui
2018-06-07 22:17:33 +00:00
[Swagger-ui ](https://github.com/swagger-api/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.
2018-05-14 05:22:06 +00:00
```
2018-09-03 16:44:59 +00:00
[metosin/reitit-swagger-ui "0.2.0"]
2018-05-14 05:22:06 +00:00
```
`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.
2018-07-16 06:34:52 +00:00
| :config | parameters passed to swaggger-ui as-is. See [the docs ](https://github.com/swagger-api/swagger-ui/tree/2.x#parameters )
2018-05-14 05:22:06 +00:00
We use swagger-ui from [ring-swagger-ui ](https://github.com/metosin/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 ](https://github.com/webjars/swagger-ui ) of the swagger-ui.
**NOTE**: Currently, swagger-ui module is just for Clojure. ClojureScript-support welcome as a PR!
## Examples
### Simple example
2018-07-21 06:38:44 +00:00
* two routes
2018-05-14 05:22:06 +00:00
* swagger-spec served from `"/swagger.json"`
* swagger-ui mounted to `"/"`
```clj
(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"
2018-07-21 07:02:08 +00:00
["/ping" {:get (constantly {:status 200, :body "ping"})}]
["/pong" {:post (constantly {:status 200, :body "pong"})}]]
2018-05-14 05:22:06 +00:00
["/swagger.json"
{:get {:no-doc true
2018-07-21 06:38:44 +00:00
:handler (swagger/create-swagger-handler)}}]])
2018-05-14 05:22:06 +00:00
(swagger-ui/create-swagger-ui-handler {:path "/"})))
```
The generated swagger spec:
```clj
(app {:request-method :get :uri "/swagger.json"})
;{:status 200
; :body {:swagger "2.0"
2018-07-21 06:38:44 +00:00
; :x-id #{:reitit.swagger/default}
2018-05-14 05:22:06 +00:00
; :paths {"/api/ping" {:get {}}
; "/api/pong" {:post {}}}}}
```
Swagger-ui:
```clj
(app {:request-method :get :uri "/"})
; ... the swagger-ui index-page, configured correctly
```
### More complete example
2018-08-22 18:51:02 +00:00
* `clojure.spec` coercion
* swagger data (`:tags`, `:produces` , `:summary` )
* swagger-spec served from `"/swagger.json"`
2018-05-14 05:22:06 +00:00
* swagger-ui mounted to `"/"`
2018-08-22 18:51:02 +00:00
* set of middleware for content negotiation, exceptions, multipart etc.
2018-05-14 05:22:06 +00:00
* missed routes are handled by `create-default-handler`
* served via [ring-jetty ](https://github.com/ring-clojure/ring/tree/master/ring-jetty-adapter )
Whole example project is in [`/examples/ring-swagger` ](https://github.com/metosin/reitit/tree/master/examples/ring-swagger ).
```clj
2018-08-22 18:51:02 +00:00
(ns example.server
(:require [reitit.ring :as ring]
[reitit.swagger :as swagger]
[reitit.swagger-ui :as swagger-ui]
[reitit.ring.coercion :as coercion]
[reitit.coercion.spec]
[reitit.ring.middleware.muuntaja :as muuntaja]
[reitit.ring.middleware.exception :as exception]
[reitit.ring.middleware.multipart :as multipart]
[ring.middleware.params :as params]
[ring.adapter.jetty :as jetty]
[muuntaja.core :as m]
[clojure.java.io :as io]))
2018-05-14 05:22:06 +00:00
(def app
(ring/ring-handler
(ring/router
2018-08-22 18:51:02 +00:00
[["/swagger.json"
2018-05-14 05:22:06 +00:00
{:get {:no-doc true
:swagger {:info {:title "my-api"}}
:handler (swagger/create-swagger-handler)}}]
2018-08-22 18:51:02 +00:00
["/files"
{:swagger {:tags ["files"]}}
["/upload"
{:post {:summary "upload a file"
:parameters {:multipart {:file multipart/temp-file-part}}
:responses {200 {:body {:file multipart/temp-file-part}}}
:handler (fn [{{{:keys [file]} :multipart} :parameters}]
{:status 200
:body {:file file}})}}]
["/download"
{:get {:summary "downloads a file"
:swagger {:produces ["image/png"]}
:handler (fn [_]
2018-05-14 05:22:06 +00:00
{:status 200
2018-08-22 18:51:02 +00:00
:headers {"Content-Type" "image/png"}
:body (io/input-stream (io/resource "reitit.png"))})}}]]
2018-05-14 05:22:06 +00:00
2018-08-22 18:51:02 +00:00
["/math"
{:swagger {:tags ["math"]}}
2018-05-14 05:22:06 +00:00
["/plus"
2018-08-22 18:51:02 +00:00
{:get {:summary "plus with spec query parameters"
:parameters {:query {:x int?, :y int?}}
:responses {200 {:body {:total int?}}}
2018-05-14 05:22:06 +00:00
:handler (fn [{{{:keys [x y]} :query} :parameters}]
{:status 200
2018-08-22 18:51:02 +00:00
:body {:total (+ x y)}})}
:post {:summary "plus with spec body parameters"
:parameters {:body {:x int?, :y int?}}
:responses {200 {:body {:total int?}}}
:handler (fn [{{{:keys [x y]} :body} :parameters}]
{:status 200
:body {:total (+ x y)}})}}]]]
{:data {:coercion reitit.coercion.spec/coercion
:muuntaja m/instance
:middleware [;; query-params & form-params
params/wrap-params
;; content-negotiation
muuntaja/format-negotiate-middleware
;; encoding response body
muuntaja/format-response-middleware
;; exception handling
exception/exception-middleware
;; decoding request body
muuntaja/format-request-middleware
;; coercing response bodys
coercion/coerce-response-middleware
;; coercing request parameters
coercion/coerce-request-middleware
;; multipart
multipart/multipart-middleware]}})
2018-05-14 05:22:06 +00:00
(ring/routes
2018-08-22 18:51:02 +00:00
(swagger-ui/create-swagger-ui-handler {:path "/"})
2018-05-14 05:22:06 +00:00
(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:

2018-07-21 06:38:44 +00:00
## Multiple swagger apis
2018-05-14 05:22:06 +00:00
2018-07-21 06:38:44 +00:00
There can be multiple swagger apis within a router. Each route can be part of 0..n swagger apis. Swagger apis are identified by value in route data under key path `[:swagger :id]` . It can be either a keyword or a sequence of keywords. Normal route data [scoping rules ](../basics/route_data.md#nested-route-data ) rules apply.
2018-05-14 05:22:06 +00:00
Example with:
* 4 routes
* 2 swagger apis `::one` and `::two`
* 3 swagger specs
```clj
(require '[reitit.ring :as ring])
(require '[reitit.swagger :as swagger])
(def ping-route
2018-07-21 07:02:08 +00:00
["/ping" {:get (constantly {:status 200, :body "ping"})}])
2018-05-14 05:22:06 +00:00
(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]])))
```
```clj
(-> {:request-method :get, :uri "/one/swagger.json"} app :body :paths keys)
; ("/common/ping" "/one/ping" "/two/deep/ping")
```
```clj
(-> {:request-method :get, :uri "/two/swagger.json"} app :body :paths keys)
; ("/common/ping" "/two/ping")
```
```clj
(-> {:request-method :get, :uri "/one-two/swagger.json"} app :body :paths keys)
; ("/common/ping" "/one/ping" "/two/ping" "/two/deep/ping")
```
### TODO
* ClojureScript
* example for [Macchiato ](https://github.com/macchiato-framework )
* body formatting
* resource handling