Skip to content

Swagger integration

Pablo Botelho edited this page Apr 20, 2017 · 20 revisions

Swagger

Compojure-api supports natively Swagger, with help of Ring-Swagger & ring-swagger-ui.

Artifacts

In order to use swagger, a swagger spec needs to be generated. It contains information about the routes, data models and common api capabilities. Optionally a local a swagger ui can be mounted.

There are two way to mount the swagger things:

Swagger-routes

A route function compojure.api.swagger/swagger-routes, bundling the spec-route compojure.api.swagger/swagger-docs and the ui-route compojure.api.swagger/swagger-ui. It takes an optional options map. Without any parameters, it uses defaults to swagger-ui is mounted to / and the spec is generated to /swagger.json.

Note swagger-docs and swagger-ui are not part of the public api since 1.0.0. Use swagger-routes instead.

(api
  (swagger-routes)
  (GET "/ping" []
    (ok {:message "pong"})))

; same as
(api
  (swagger-routes
    {:ui "/", :spec "/swagger.json"})
  (GET "/ping" []
    (ok {:message "pong"})))

Api-options

For larger apps, it's nice to be able to keep all api-level configuration in one place. If an api has a :swagger option, swagger-routes is configured with its value and mounted before other routes and configured.

(api
  {:swagger {:ui "/", :spec "/swagger.json"}}
  (GET "/ping" []
    (ok {:message "pong"})))

Advanced

Swagger data is read from the following sources into the generated spec:

  • api creation-time route & schema information, generated for you by the lib
  • Run-time extra information from the middleware passed in with the request
  • User-set custom information

Api creation-time route & schema information

Passed in automatically via request injection.

Run-time injected information

By default, the application wire-format serialization capabilities (:produces and :consumes) are injected in automatically by the api machinery.

One can contribute extra arbitrary swagger-data (like swagger security definitions) to the docs via the ring.swagger.middleware/wrap-swagger-data middleware.

User-set custom information

The swagger-routes can be used without parameters, but one can also set any valid root-level Swagger Data with it.

(swagger-routes)
Via api with options
(api
  {:ring-swagger {:ignore-missing-mappings? true}})
   :swagger
   {:ui "/api-docs"
    :spec "/swagger.json"
    :options {:ui {:validatorUrl nil}}
    :data {:basePath "/app"
           :info {:version "1.0.0"
                  :title "Thingies API"
                  :description "the description"
                  :termsOfService "http://www.metosin.fi"
                  :contact {:name "My API Team"
                            :email "[email protected]"
                            :url "http://www.metosin.fi"}
                  :license {:name "Eclipse Public License"
                            :url "http://www.eclipse.org/legal/epl-v10.html"}}
           :tags [{:name "math", :description "Math with parameters"}
                  {:name "pizzas", :description "Pizza API"}
                  {:name "failing", :description "Handling uncaught exceptions"}
                  {:name "dates", :description "Dates API"}
                  {:name "responses", :description "Responses demo"}
                  {:name "primitives", :description "Returning primitive values"}
                  {:name "context", :description "context routes"}
                  {:name "echo", :description "Echoes data"}
                  {:name "ordered", :description "Ordered routes"}
                  {:name "file", :description "File upload"}]}}}
  ...)

Authentication & Authorization

Example to enable HTTP Basic Auth for the swagger-ui:

(api
  {:swagger 
   {:data 
    {:securityDefinitions 
     {:login 
      {:type "basic"}}}}}
  ...)

Example to enable header-based (x-apikey) authentication for the swagger-ui:

(api
  {:swagger 
   {:data 
    {:securityDefinitions 
     {:api_key 
      {:type "apiKey"
       :name "x-apikey"
       :in "header"}}}}}
  ...)

Example to enable custom authentication for the swagger-ui:

(defn authorized-for-docs? [handler]
   (fn [request]
     (let [auth-header (get (:headers request) "authorization")]
       (cond
         (nil? auth-header)
         (-> (res/unauthorized)
             (res/header "WWW-Authenticate" "Basic realm=\"whatever\""))

         (= auth-header "Your Basic Auth Secret")
         (handler request)

         :else
         (res/unauthorized {})))))

(def app
  (ring/api

   (ring/context "/docs" req
     :middleware [authorized-for-docs?]
     (docs/swagger-routes
       {:ui "/"
        :options {:ui {:swagger-docs "/docs/swagger.json"}}
        :spec "/swagger.json"
        :data {:info
               {:title "Title ..."
                :description "Foo Bar"}}}))

Customizing Swagger output

One can configure Ring-Swagger by providing options to api-middleware for the key :ring-swagger. See Ring-Swagger docs for possible options and examples.

(api
  {:ring-swagger {:ignore-missing-mappings? true}})
  (swagger-routes)
  ...)

Customizing swagger-ui

If the shipped ring-swagger-ui isn't enough for you, you can exclude it from dependencies and create & package your own UI from the sources.

Another option is to override the shipped files under resources/swagger-ui. Just place a custom index.html there and it's used instead.

Route meta-data

Most in-build compojure-api restucturing also contribute to generated swagger-docs. See Endpoints for details.

Sample documented endpoint

(GET "/plus" []
  :return Long
  :header-params [authorization :- s/String]
  :query-params [x :- (describe Long "description")
                 {y :- Long 1}]
  :summary "x+y with query-parameters. y defaults to 1."
  (ok {:total (+ x y)}))
; #Route{:path "/plus",
;        :method :get,
;        :info {:responses {200 {:schema java.lang.Long, :description ""}},
;               :parameters {:query {Keyword Any, :x java.lang.Long, #schema.core.OptionalKey{:k :y} java.lang.Long}},
;               :summary "x+y with query-parameters. y defaults to 1."}}

Non-documented routes

Adding a :no-doc true meta-data on route (or context) marks it to be removed from swagger-docs

(context "/secret" []
  :no-doc true
  (GET "/abba" []
    (ok {:jabba "dabba"})))

I just want the swagger-docs, without coercion

You can either use the normal restructuring (:query, :path etc.) to get the swagger docs and disable the coercion by setting the :coercion to nil for the whole api, for a context, or for an individual endpoint.

The whole api

(api
  {:coercion nil}
  ...

A context

(context "/no-validation" []
  :coercion nil
  (POST "/echo" []
    :return {:x s/Int}
    :body [data {:x s/Int}]
    (ok data)))

A single endpoint

(POST "/echo-no-validation" []
  :coercion nil
  :return {:x s/Int}
  :body [data {:x s/Int}]
  (ok data))

Just data

You can also use the :swagger metadata at your route, which pushes the swagger docs for the routes, without doing any destructuring of the request.

(GET "/route" [q]
  :swagger {:x-name :boolean
            :operationId "echoBoolean"
            :description "Echoes a boolean"
            :parameters {:query {:q s/Bool}}}
  ;; q might be anything here.
  (ok {:q q}))

Validating the Swagger spec

As one can pass arbitrary data to swagger, it's good to validate the end results. Swagger 2.0 has a JSON Schema to validate the results against. This can be done in several ways:

Online validator

See https://github.com/swagger-api/validator-badge for details.

Via ring-swagger

Build your own validation-badge endpoint.

(require '[ring.swagger.validator :as validator])

(validator/validate (slurp "http://localhost:3000/swagger.json"))
; => nil

Compojure-api validator

Great for integration tests.

(Note: this was moved from compojure.api.swagger to compojure.api.validator in this commit, but the Codox API documentation doesn't reflect the update).

(require '[compojure.api.validator :as validator])
(validator/validate api)
; => returns api or throws descriptive exception