Generate Swagger documentation from pedestal routes
(:require [io.pedestal.interceptor.helpers :as interceptor]
[schema.core :as schema]
[pedestal.swagger.core :as swagger]
[pedestal.swagger.doc :as swagger.doc])
Annotate your interceptors using swagger.doc/annotate
. This function will attach swagger metadata to your interceptor.
(def my-endpoint
(swagger/annotate
{:summary "Endpoint for stuff"
:description "This is an interesting endpoint"
:parameters {:query {:param1 schema/Bool}
:body {:param2 schema/Str
:param3 schema/Inst}}
:responses {:default {:headers {:x-tracking schema/Uuid}}
418 {:description "I'm sorry, I'm a teapot"}}}
(interceptor/handler
::my-endpoint
(fn [request]
...))))
You can use these interceptors just like any other interceptors in your route definition.
(defroutes routes [[["/my-endpoint" {:get my-endpoint}]]])
And of course there are some helping macros in the swagger namespace to help you define them more succintely.
(swagger/defhandler my-handler
{:summary ".."
:parameters {..}
:responses {..}}
[request]
...)
A swagger path specification can be generated from your route table. You can inspect at any time what it looks like calling gen-paths
.
(swagger.doc/gen-paths routes)
;; => {"/my-endpoint" {:get {...}} ...}
But what you normally want is to inject the swagger paths in your route table, so that is available to your interceptors. There's a handy macro for that.
(swagger/defroutes routes
{:info {:title "Swagger API"
:description "Swagger + Pedestal api"}}
[[["/my-endpoint" {:get my-endpoint}]]])
The second argument to defroutes is an optional map that will be merged with the generated paths and some default values to create a complete swagger documentation. The resulting swagger object looks like this:
{:swagger "2.0"
:info {:title "Swagger API"
:description "Swagger + Pedestal api"
:version "0.0.1"}
:paths {"/my-endpoint" {...}}}
A special handler can be added to convert this data structure in a json schema and serve it on a path (usually named swagger.json
):
(swagger/defroutes routes
[[["/my-endpoint" {:get my-endpoint}]
["/swagger.json" {:get [(swagger/swagger-json)]}]]])
And of course you can add a swagger-ui endpoint to provide an easy way to view and play with your API.
(swagger/defroutes routes
[[["/my-endpoint" {:get my-endpoint}]
["/swagger/swagger.json" {:get [(swagger/swagger-json)]}]
["/swagger/*resource" {:get [(swagger/swagger-ui)]}]]])
Note that the swagger-ui endpoint requires a *resource
splat parameter.
If your endpoint is a plain ring handler or a pedestal interceptor it will not be included in your documentation.
(defn a-hidden-endpoint [request]
...)
All this would be a little uninteresting if we weren't able to leverage one of Pedestal's most interesting features: sharing logic between endpoints via interceptors.
A common pattern is to have an interceptor at the root of a path that loads a resource.
(swagger/defon-request load-thing-from-db
{:parameters {:path {:id schema/Int}}
:responses {404 {:description "Couldn't find the thing in the db"}}}
[request]
...)
(swagger/defroutes routes
[[["/thing/:id" ^:interceptors [load-thing-from-db]
{:get do-get-thing}
{:put do-update-thing}
{:delete do-delete-thing}]]])
All the documentation specified in the interceptor (such as parameters, responses, description) will be inherited by the endpoints on the same path. Thus you can reuse both behaviour and the documentation for said behaviour.
And finally we want to be able to use the schemas in the documentation to check the incoming parameters or the outgoing responses. To do so we can include swagger/coerce-request
and swagger/validate-response
at the top of our route spec. The default behaviour of these interceptors could be overridden passing a custom coercion or validation function.
(swagger/defroutes routes
[[["/" ^:interceptors [(swagger/serialise-response)
(swagger/body-params)
(swagger/coerce-request)
(swagger/validate-response)]
["/thing/:id" ^:interceptors [load-thing-from-db]
{:get do-get-thing}
{:put do-update-thing}
{:delete do-delete-thing}]]]])
Note that you usually need to include (swagger/body-params)
as it will make sure the keys in the request map are where the validator expects to find them. Each interceptor can still be configured to tweak its default behaviour.
An interceptor with sensible error handling for most of swagger use cases is available under pedestal.swagger.error
namespace, but you're encouraged to build your own using pedestal pattern matching.
The swagger/serialise-response
interceptor provides content-negotiation for the serialisers built in to Pedestal. You can extend it with your own content-types by passing in a map similar to pedestal.swagger.content-negotiation/default-serialisation-interceptors
.
For a complete example have a look at the sample
project.
- Support multiple swagger.json and swagger-ui endpoints (e.g. for versioned apis)
- Documenting
consumes
- More failures at compile time (tags not in sync, produces/consumes not in sync)
Copyright © 2014 Frankie Sardo
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.