From 75ebeaf6cd10f9c8097056786cc5102408e6df76 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Thu, 30 Mar 2023 14:32:19 +0300 Subject: [PATCH 1/4] test: test openapi examples support for malli, schema, spec --- test/cljc/reitit/openapi_test.clj | 63 +++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index 0788432e..4df3c050 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -17,6 +17,7 @@ [reitit.swagger-ui :as swagger-ui] [schema.core :as s] [schema-tools.core] + [spec-tools.core :as st] [spec-tools.data-spec :as ds])) (defn validate @@ -432,6 +433,68 @@ (testing "spec is valid" (is (nil? (validate spec)))))))) +(deftest examples-test + (doseq [[coercion ->schema] + [[#'malli/coercion (fn [nom] [:map + {:json-schema/example {nom "EXAMPLE2"}} + [nom [:string {:json-schema/example "EXAMPLE"}]]])] + [#'schema/coercion (fn [nom] (schema-tools.core/schema + {nom (schema-tools.core/schema s/Str {:openapi/example "EXAMPLE"})} + {:openapi/example {nom "EXAMPLE2"}}))] + [#'spec/coercion (fn [nom] + (assoc + (ds/spec ::foo {nom (st/spec string? {:openapi/example "EXAMPLE"})}) + :openapi/example {nom "EXAMPLE2"}))]]] + (testing coercion + (let [app (ring/ring-handler + (ring/router + [["/examples" + {:post {:decription "examples" + :coercion @coercion + :parameters {:query (->schema :q) + :request {:body (->schema :b)}} + :responses {200 {:description "success" + :body (->schema :ok)}} + :handler identity}}] + ["/openapi.json" + {:get {:handler (openapi/create-openapi-handler) + :openapi {:info {:title "" :version "0.0.1"}} + :no-doc true}}]] + {:data {:middleware [openapi/openapi-feature]}})) + spec (-> {:request-method :get + :uri "/openapi.json"} + app + :body)] + (testing "query parameter" + (is (match? [{:in "query" + :name "q" + :required true + :schema {:type "string" + :example "EXAMPLE"}}] + (-> spec + (get-in [:paths "/examples" :post :parameters]) + normalize)))) + (testing "body parameter" + (is (match? {:schema {:type "object" + :properties {:b {:type "string" + :example "EXAMPLE"}} + :required ["b"] + :example {:b "EXAMPLE2"}}} + (-> spec + (get-in [:paths "/examples" :post :requestBody :content "application/json"]) + normalize)))) + (testing "body response" + (is (match? {:schema {:type "object" + :properties {:ok {:type "string" + :example "EXAMPLE"}} + :required ["ok"] + :example {:ok "EXAMPLE2"}}} + (-> spec + (get-in [:paths "/examples" :post :responses 200 :content "application/json"]) + normalize)))) + (testing "spec is valid" + (is (nil? (validate spec)))))))) + (deftest multipart-test (doseq [[coercion file-schema string-schema] [[#'malli/coercion From ca9852a318aa7df1ae69d1e14d8cd3d76feb8982 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Wed, 19 Apr 2023 10:49:23 +0300 Subject: [PATCH 2/4] test: multiple named openapi examples --- test/cljc/reitit/openapi_test.clj | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/test/cljc/reitit/openapi_test.clj b/test/cljc/reitit/openapi_test.clj index 4df3c050..bef17f7c 100644 --- a/test/cljc/reitit/openapi_test.clj +++ b/test/cljc/reitit/openapi_test.clj @@ -455,6 +455,18 @@ :request {:body (->schema :b)}} :responses {200 {:description "success" :body (->schema :ok)}} + :openapi {:requestBody + {:content + {"application/json" + {:examples + {"named-example" {:description "a named example" + :value {:b "named"}}}}}} + :responses + {200 + {:content + {"application/json" + {:examples + {"response-example" {:value {:ok "response"}}}}}}}} :handler identity}}] ["/openapi.json" {:get {:handler (openapi/create-openapi-handler) @@ -479,7 +491,9 @@ :properties {:b {:type "string" :example "EXAMPLE"}} :required ["b"] - :example {:b "EXAMPLE2"}}} + :example {:b "EXAMPLE2"}} + :examples {:named-example {:description "a named example" + :value {:b "named"}}}} (-> spec (get-in [:paths "/examples" :post :requestBody :content "application/json"]) normalize)))) @@ -488,7 +502,8 @@ :properties {:ok {:type "string" :example "EXAMPLE"}} :required ["ok"] - :example {:ok "EXAMPLE2"}}} + :example {:ok "EXAMPLE2"}} + :examples {:response-example {:value {:ok "response"}}}} (-> spec (get-in [:paths "/examples" :post :responses 200 :content "application/json"]) normalize)))) From 5227e650296590084ec1aca73b76008a38aa6453 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Wed, 19 Apr 2023 10:56:22 +0300 Subject: [PATCH 3/4] doc: OpenAPI3 named examples in examples/http-swagger --- examples/http-swagger/src/example/server.clj | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/examples/http-swagger/src/example/server.clj b/examples/http-swagger/src/example/server.clj index d0a9164e..6b5903d2 100644 --- a/examples/http-swagger/src/example/server.clj +++ b/examples/http-swagger/src/example/server.clj @@ -112,6 +112,22 @@ {:status 200 :body {:total (+ x y)}})} :post {:summary "plus with data-spec body parameters" + ;; OpenAPI3 named examples for request & response + :openapi {:requestBody + {:content + {"application/json" + {:examples {"add-one-one" {:summary "1+1" + :value {:x 1 :y 1}} + "add-one-two" {:summary "1+2" + :value {:x 1 :y 2}}}}}} + :responses + {200 + {:content + {"application/json" + {:examples {"two" {:summary "2" + :value {:total 2}} + "three" {:summary "3" + :value {:total 3}}}}}}}} :parameters {:body {:x int?, :y int?}} :responses {200 {:body {:total int?}}} :handler (fn [{{{:keys [x y]} :body} :parameters}] From be0d066f5dbdb9235304928a1927c1200375c9c1 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Wed, 19 Apr 2023 11:03:15 +0300 Subject: [PATCH 4/4] doc: document OpenAPI3 multiple examples --- doc/ring/openapi.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/doc/ring/openapi.md b/doc/ring/openapi.md index d3e127cf..c5187095 100644 --- a/doc/ring/openapi.md +++ b/doc/ring/openapi.md @@ -31,6 +31,29 @@ Coercion keys also contribute to the docs: Use `:request` parameter coercion (instead of `:body`) to unlock per-content-type coercions. See [Coercion](coercion.md). +## 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"`, `"examples"` or other OpenAPI keys +that are not generated automatically by reitit. + +```clj +["/foo" + {:post {:parameters {:body {:name string? :age int?}} + :openapi {:requestBody + {:content + {"application/json" + {:examples {"Pyry" {:summary "Pyry, 45y" + :value {:name "Pyry" :age 45}} + "Cat" {:summary "Cat, 8y" + :value {:name "Cat" :age 8}}}}}}} + ...}}] +``` + +See [the http-swagger example](../../examples/http-swagger) for a +working examples of `"securitySchemes"` and `"examples"`. + ## 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.