From afc8945d78135e45480137f00396729b8112a19e Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Mon, 16 Sep 2024 12:47:33 +0300 Subject: [PATCH] doc: improve openapi docs --- doc/ring/openapi.md | 177 +++++++++++++++++++++++++++----------------- 1 file changed, 110 insertions(+), 67 deletions(-) diff --git a/doc/ring/openapi.md b/doc/ring/openapi.md index 620ec1b4..ec3d2264 100644 --- a/doc/ring/openapi.md +++ b/doc/ring/openapi.md @@ -34,12 +34,100 @@ Coercion keys also contribute to the docs: | :request | optional description of body parameters, possibly per content-type | :responses | optional descriptions of responses, in a format defined by coercion -## Annotating schemas + +## Per-content-type coercions + +Use `:request` coercion (instead of `:body`) to unlock +per-content-type coercions. This also lets you specify multiple named +examples. See [Coercion](coercion.md) for more info. See also [the +openapi example](../../examples/openapi). + +```clj +["/pizza" + {:get {:summary "Fetch a pizza | Multiple content-types, multiple examples" + :responses {200 {:description "Fetch a pizza as json or EDN" + :content {"application/json" {:schema [:map + [:color :keyword] + [:pineapple :boolean]] + :examples {:white {:description "White pizza with pineapple" + :value {:color :white + :pineapple true}} + :red {:description "Red pizza" + :value {:color :red + :pineapple false}}}} + "application/edn" {:schema [:map + [:color :keyword] + [:pineapple :boolean]] + :examples {:red {:description "Red pizza with pineapple" + :value (pr-str {:color :red :pineapple true})}}}}}} +``` + +The special `:default` content types map to the content types supported by the Muuntaja +instance. You can override these by using the `:openapi/request-content-types` +and `:openapi/response-content-types` keys, which must contain vector of +supported content types. If there is no Muuntaja instance, and these keys are +not defined, the content types will default to `["application/json"]`. + +## OpenAPI spec + +Serving the OpenAPI specification is handled by +`reitit.openapi/create-openapi-handler`. It takes no arguments and returns a +ring handler which collects at request-time data from all routes and returns an +OpenAPI specification as Clojure data, to be encoded by a response formatter. + +You can use the `:openapi` route data key of the `create-openapi-handler` route +to populate the top level of the OpenAPI spec. + +Example: + +``` +["/openapi.json" + {:get {:handler (openapi/create-openapi-handler) + :openapi {:info {:title "my nice api" :version "0.0.1"}} + :no-doc true}}] +``` + +If you need to post-process the generated spec, just wrap the handler with a custom `Middleware` or an `Interceptor`. + +## Swagger-ui + +[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. See `reitit.swagger-ui/create-swagger-ui-handle` + +## Finetuning the OpenAPI output + +There are a number of ways you can specify extra data that gets +included in the OpenAPI spec. + +### Custom OpenAPI data + +The `:openapi` route data key can be used to add top-level or +route-level information to the generated OpenAPI spec. + +A straightforward use case is adding `"externalDocs"`: + +```clj +["/account" + {:get {:summary "Fetch an account | Recursive schemas using malli registry, link to external docs" + :openapi {:externalDocs {:description "The reitit repository" + :url "https://github.com/metosin/reitit"}} + ...}}] +``` + +In a more complex use case is providing `"securitySchemes"`. See +[the openapi example](../../examples/openapi) for a working example of +`"securitySchemes"`. See also the +[OpenAPI docs](https://spec.openapis.org/oas/v3.1.0.html#security-scheme-object) + +### Annotating schemas You can use malli properties, schema-tools data or spec-tools data to annotate your models with examples, descriptions and defaults that show up in the OpenAPI spec. +This approach lets you add additional keys to the +[OpenAPI Schema Objects](https://spec.openapis.org/oas/v3.1.0.html#schema-object). +The most common ones are default and example values for parameters. + Malli: ```clj @@ -81,73 +169,28 @@ Spec: :y int?}}}}}] ``` -## Per-content-type coercions +### Adding examples -Use `:request` coercion (instead of `:body`) to unlock -per-content-type coercions. This also lets you specify multiple named -examples. See [Coercion](coercion.md) for more info. See also [the -openapi example](../../examples/openapi). +Adding request/response examples have been mentioned above a couple of times +above. Here's a summary of the different ways to do it: + +1. Add an example to the schema object using a `:openapi/example` + (schema, spec) or `:json-schema/example` (malli) key in your + schema/spec/malli model metadata. See the examples above. +2. Use `:example` (a single example) or `:examples` (named examples) + with per-content-type coercion. + +**Caveat!** When adding examples for query parameters (or headers), +you must add the examples to the individual parameters, not the map +schema surrounding them. This is due to limitations in how OpenAPI +represents query parameters. ```clj -["/pizza" - {:get {:summary "Fetch a pizza | Multiple content-types, multiple examples" - :responses {200 {:content {"application/json" {:description "Fetch a pizza as json" - :schema [:map - [:color :keyword] - [:pineapple :boolean]] - :examples {:white {:description "White pizza with pineapple" - :value {:color :white - :pineapple true}} - :red {:description "Red pizza" - :value {:color :red - :pineapple false}}}} - "application/edn" {:description "Fetch a pizza as edn" - :schema [:map - [:color :keyword] - [:pineapple :boolean]] - :examples {:red {:description "Red pizza with pineapple" - :value (pr-str {:color :red :pineapple true})}}}}}} +;; Wrong! +{:parameters {:query [:map + {:json-schema/example {:a 1}} + [:a :int]]}} +;; Right! +{:parameters {:query [:map + [:a {:json-schema/example 1} :int]]}} ``` - -The special `:default` content types map to the content types supported by the Muuntaja -instance. You can override these by using the `:openapi/request-content-types` -and `:openapi/response-content-types` keys, which must contain vector of -supported content types. If there is no Muuntaja instance, and these keys are -not defined, the content types will default to `["application/json"]`. - -## Custom OpenAPI data - -The `:openapi` route data key can be used to add top-level or -route-level information to the generated OpenAPI spec. This is useful -for providing `"securitySchemes"` or other OpenAPI keys that are not -generated automatically by reitit. - -See [the openapi example](../../examples/openapi) for a working -example of `"securitySchemes"`. - -## OpenAPI spec - -Serving the OpenAPI specification is handled by -`reitit.openapi/create-openapi-handler`. It takes no arguments and returns a -ring handler which collects at request-time data from all routes and returns an -OpenAPI specification as Clojure data, to be encoded by a response formatter. - -You can use the `:openapi` route data key of the `create-openapi-handler` route -to populate the top level of the OpenAPI spec. - -Example: - -``` -["/openapi.json" - {:get {:handler (openapi/create-openapi-handler) - :openapi {:info {:title "my nice api" :version "0.0.1"}} - :no-doc true}}] -``` - -If you need to post-process the generated spec, just wrap the handler with a custom `Middleware` or an `Interceptor`. - -## Swagger-ui - -[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. - -Note: you need Swagger-UI 5 for OpenAPI 3.1 support. As of 2023-03-10, a v5.0.0-alpha.0 is out.