Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions doc/ring/openapi.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,11 +207,13 @@ useful when you have multiple endpoints that use the same schema. It
can also make OpenAPI-based code nicer for consumers of your API.
These schemas are also rendered in their own section in Swagger UI.

Reusable schema objects are generated for Malli `:ref`s and vars. The
[openapi example](../../examples/openapi) showcases this.
Reusable schema objects are generated for
- Malli `:ref`s and vars and
- Plumatic Schema named schemas (`defschema` and `named`).
The [openapi example](../../examples/openapi) showcases this.

Currently (as of 0.7.2), reusable schema objects are **not** generated
for Plumatic Schema or Spec.
for Spec.

## Other caveats

Expand Down
35 changes: 32 additions & 3 deletions examples/openapi/src/example/server.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
(:require [reitit.ring :as ring]
[reitit.ring.spec]
[reitit.coercion.malli]
[reitit.coercion.schema]
[reitit.openapi :as openapi]
[reitit.ring.malli]
[reitit.swagger-ui :as swagger-ui]
Expand All @@ -12,7 +13,8 @@
[reitit.ring.middleware.multipart :as multipart]
[reitit.ring.middleware.parameters :as parameters]
[ring.adapter.jetty :as jetty]
[muuntaja.core :as m]))
[muuntaja.core :as m]
[schema.core :as s]))

(def Transaction
[:map
Expand All @@ -31,7 +33,19 @@
[:balance :double]
[:transactions [:vector #'Transaction]]])

(s/defschema TransactionSchema
{:amount s/Num
:from s/Str})

(s/defschema AccountIdSchema
{:bank s/Str
:id s/Str})

(s/defschema AccountSchema
{:bank s/Str
:id s/Str
:balance s/Num
:transactions [TransactionSchema]})

(def app
(ring/ring-handler
Expand Down Expand Up @@ -132,7 +146,7 @@
:email "heidi@alps.ch"}]})}}]

["/account"
{:get {:summary "Fetch an account | Recursive schemas using malli registry, link to external docs"
{:get {:summary "Fetch an account | Named schemas using malli registry, link to external docs"
:parameters {:query #'AccountId}
:responses {200 {:content {:default {:schema #'Account}}}}
:openapi {:externalDocs {:description "The reitit repository"
Expand Down Expand Up @@ -186,7 +200,22 @@
{:status 200
:body {:secret "I am a marmot"}}
{:status 401
:body {:error "unauthorized"}}))}}]]]
:body {:error "unauthorized"}}))}}]]

["/plumatic-schema/account"
{:get {:summary "Fetch an account | Named schemas using Plumatic Schema"
:coercion reitit.coercion.schema/coercion
:parameters {:query AccountIdSchema}
:responses {200 {:content {:default {:schema AccountSchema}}}}
:handler (fn [_request]
{:status 200
:body {:bank "MiniBank"
:id "0001"
:balance 13.5
:transactions [{:from "0002"
:amount 20.0}
{:from "0003"
:amount -6.5}]}})}}]]

{;;:reitit.middleware/transform dev/print-request-diffs ;; pretty diffs
:validate reitit.ring.spec/validate
Expand Down
6 changes: 5 additions & 1 deletion modules/reitit-schema/src/reitit/coercion/schema.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@
(-get-options [_] opts)
(-get-model-apidocs [_ specification model options]
(case specification
:openapi (openapi/transform model (merge opts options))
:openapi (if (= :parameter (:type options))
;; For :parameters we need to output an object schema with actual :properties, not a $ref
;; The caller will iterate through the properties and add them individually to the openapi doc.
(openapi/transform-inline model (merge opts options))
(openapi/transform model (merge opts options)))
(throw
(ex-info
(str "Can't produce Schema apidocs for " specification)
Expand Down
51 changes: 24 additions & 27 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "reitit",
"private": true,
"devDependencies": {
"@seriousme/openapi-schema-validator": "^2.7.0",
"@seriousme/openapi-schema-validator": "^2.9.1",
"karma": "^6.4.4",
"karma-chrome-launcher": "^3.2.0",
"karma-cli": "^2.0.0",
Expand Down
4 changes: 2 additions & 2 deletions project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
[metosin/reitit-pedestal "0.10.1"]
[metosin/ring-swagger-ui "5.31.0"]
[metosin/spec-tools "0.10.8"]
[metosin/schema-tools "0.13.1"]
[metosin/schema-tools "0.14.0"]
[metosin/muuntaja "0.6.11"]
[metosin/jsonista "0.3.14"]
[metosin/sieppari "0.0.0-alpha13"]
Expand Down Expand Up @@ -95,7 +95,7 @@
[org.clojure/clojurescript "1.12.134"]

;; modules dependencies
[metosin/schema-tools "0.13.1"]
[metosin/schema-tools "0.14.0"]
[metosin/spec-tools "0.10.8"]
[metosin/muuntaja "0.6.11"]
[metosin/sieppari "0.0.0-alpha13"]
Expand Down
81 changes: 81 additions & 0 deletions test/cljc/reitit/openapi_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -1143,3 +1143,84 @@
:anyOf [{:required ["address" "zip"]}
{:required ["city" "street"]}]}
(get-in spec [:paths "/spec" :post :requestBody :content "application/json" :schema]))))))

(s/defschema Y2 s/Int)
(s/defschema Plus2 {:x s/Int
:y Y2})

(deftest openapi-schema-tests
(testing "named schemas"
(let [app (ring/ring-handler
(ring/router
[["/openapi.json"
{:get {:no-doc true
:openapi {:info {:title "" :version "0.0.1"}}
:handler (openapi/create-openapi-handler)}}]
["/post"
{:post {:parameters {:body Plus2}
:handler identity}}]
["/get"
{:get {:parameters {:query Plus2}
:handler identity}}]]
{:data {:coercion schema/coercion}}))
spec (:body (app {:request-method :get :uri "/openapi.json"}))]
(is (= {:openapi "3.1.0"
:x-id #{:reitit.openapi/default}
:info {:title "" :version "0.0.1"}
:paths
{"/post"
{:post
{:requestBody
{:content
{"application/json"
{:schema
{:$ref "#/components/schemas/reitit.openapi-test.Plus2"}}}}}}
"/get"
{:get
{:parameters
[{:in "query" :name "x"
:required true
:schema {:type "integer" :format "int32"}}
{:in "query"
:name "y"
:required true
:schema {:$ref "#/components/schemas/reitit.openapi-test.Y2"}}]}}}
:components
{:schemas
{"reitit.openapi-test.Plus2"
{:type "object"
:title "reitit.openapi-test/Plus2"
:additionalProperties false
:properties
{"x" {:type "integer" :format "int32"}
"y" {:$ref "#/components/schemas/reitit.openapi-test.Y2"}}
:required ["x" "y"]}
"reitit.openapi-test.Y2" {:type "integer" :format "int32"}}}}
spec))
(is (nil? (validate spec))))
(testing "under additionalParameters"
(let [app (ring/ring-handler
(ring/router
[["/openapi.json"
{:get {:no-doc true
:openapi {:info {:title "" :version "0.0.1"}}
:handler (openapi/create-openapi-handler)}}]
["/post"
{:post {:parameters {:body {s/Keyword Y2}}
:handler identity}}]]
{:data {:coercion schema/coercion}}))
spec (:body (app {:request-method :get :uri "/openapi.json"}))]
(is (= {:openapi "3.1.0"
:x-id #{:reitit.openapi/default}
:info {:title "" :version "0.0.1"}
:paths
{"/post"
{:post
{:requestBody
{:content
{"application/json"
{:schema {:type "object"
:additionalProperties {:$ref "#/components/schemas/reitit.openapi-test.Y2"}}}}}}}}
:components {:schemas {"reitit.openapi-test.Y2" {:type "integer" :format "int32"}}}}
spec))
(is (nil? (validate spec)))))))
Loading