Joel Kaasinen

Reitit 0.7.0-alpha1 is out now with OpenAPI 3.1.0 support. Please test it and report any problems on Github or on the #reitit channel in Clojurians Slack. We'll have a proper 0.7.0 release soon!

OpenAPI 3 Support for Reitit

OpenAPI (docs, official spec) is a standard format for documenting HTTP APIs. It is the successor of Swagger, also known as OpenAPI 2.

With OpenAPI documentation, you can provide nice interactive docs for the users of your API (here's an example using Swagger UI). They can also automatically generate client code for your API using a tool like openapi-generator or the Clojure library Martian

Cool Features in OpenAPI 3Link to Cool Features in OpenAPI 3

Why should you care about OpenAPI 3 when Swagger works just fine? Some of the additions that OpenAPI 3 brings might be crucial for documenting your API properly.

Most significantly, OpenAPI 3 brings full JSON Schema support. This means that you can express richer schemas, for example via the operators oneOf, anyOf and allOf. These operators were present as partially-supported extensions in Swagger, but in JSON Schema and OpenAPI 3, they have full support.

Relatedly, you can use discriminators to pick which schema applies to a value based on a field. This is crucial for big union types like events. Here's a nice blog post on these new features. Note that as of this release, Reitit does not know how to generate discriminators automatically for you.

OpenAPI 3 also lets you specify different schemas for different request/response content types. Also, you can specify multiple named examples for parameters. You can find examples of both of these in the Reitit example discussed below.

On the authentication side, OpenAPI 3 has added support for cookie parameters & authentication, and better OAuth / OpenID Connect support. See the spec for more info.

ExampleLink to Example

Generating OpenAPI 3 documentation from Reitit is straightforward. Just use create-openapi-handler to define a route. The OpenAPI docs will be generated based on the route data like :summary, :parameters, etc.

[["/openapi.json"
  {:get {:no-doc true
         :openapi {:info {:title "my-api"
                          :description "reitit + openapi3"
                          :version "0.0.1"}
                   :tags [{:name "api"
                           :description "the api"}]}
         :handler (openapi/create-openapi-handler)}}]

 ["/api"
  {:tags ["api"]}

  ["/hello"
   {:get {:summary "hello"
          :handler (fn [_]
                     {:status 200
                      :body "hello world"})}}]

  ["/plus"
   {:post {:summary "plus with malli body parameters"
           :description "description here"
           :parameters {:body {:x :int, :y :int}}
           :responses {200 {:body {:total :int}}}
           :handler (fn [request]
                      (let [{:keys [x y]} (-> request :parameters :body)]
                        {:status 200
                         :body {:total (+ x y)}}))}}]]]

Here's what Swagger UI looks like for this API:

The ring-malli-swagger and ring-spec-swagger examples in the Reitit repository has been extended to serve OpenAPI 3 & Swagger specs side-by-side. Check them out!

Migrating A Reitit App to OpenAPI 3Link to Migrating A Reitit App to OpenAPI 3

Just like in the previous example, you can easily serve Swagger and OpenAPI 3 specs side-by-side for a transition period.

To migrate your Reitit app to OpenAPI 3, do the following:

  1. Add a reitit.openapi/create-openapi-handler route. Here's a minimal example:
["/openapi.json"
 {:get {:handler (openapi/create-openapi-handler)
        :openapi {:info {:title "my nice api" :version "0.0.1"}}
        :no-doc true}}]
  1. See how your OpenAPI 3 spec looks like in Swagger UI, and validate it using a tool like openapi-schema-validator.
  2. If you have additional fields under your swagger handler (e.g. :securityDefinitions), port them to OpenAPI 3 format, and add them under the :openapi key in the route you just added.
  3. If you have custom :swagger data on your other routes, convert them to :openapi
  4. Use :json-schema/default and :json-schema/example in addition to :swagger/default and :swagger/example in your schema metadata.
    • For Plumatic Schema, the new keys are :openapi/default and :openapi/example instead.

ThanksLink to Thanks

It takes a village to raise a library, as they say. I'd like to thank everybody who made this Reitit update happen.

First of all, thanks to Solita for sponsoring the delivery of the OpenAPI 3 feature and thus my part of the work. Without their nudging and financial support, this release would've happened a lot later.

Secondly, thanks to current and former Metosin programmers @ikitommi, @Deraen, @kalekale, and @miikka for their work on the Metosin libraries Reitit, Malli, Spec-tools, and Schema-tools.

The foundation of the work was transforming Malli, Spec and Plumatic Schema schemas into JSON Schema, which OpenAPI 3 builds on.

The initial JSON Schema support for Spec (via spec-tools) and Malli was implemented by Metosin.

@rrudakov contributed OpenAPI support for spec-tools in PR #236 and schema-tools in PR #63.

Bringing all of these tools together in Reitit was a long process, with lots of discussion among the community in Reitit issue #84 and some valiant first implementation efforts by @czan and @Koura.

Finally, things started to come together last fall when @souenzzo (from CyCognito) submitted an initial version of OpenAPI 3 support in Reitit PR #563

I picked their work up this spring, added some missing pieces like docs, tests, and coercion and multipart uploads.

Finally, I wanted to make sure we support mutual recursion of schemas properly. Luckily, @cap10morgan was working on some fixes related to that on the Swagger side, so we could help each other. Malli issue #868 tracks all the fixes I made.

A big thank you to everybody!

Joel Kaasinen

Contact