mirror of
https://github.com/metosin/reitit.git
synced 2026-01-09 08:39:50 +00:00
commit
8c39840eca
19 changed files with 263 additions and 53 deletions
11
README.md
11
README.md
|
|
@ -27,16 +27,17 @@ See the [full documentation](https://metosin.github.io/reitit/) for details.
|
|||
All bundled:
|
||||
|
||||
```clj
|
||||
[metosin/reitit "0.1.0"]
|
||||
[metosin/reitit "0.1.1-SNAPSHOT"]
|
||||
```
|
||||
|
||||
Optionally, the parts can be required separately:
|
||||
|
||||
```clj
|
||||
[metosin/reitit-core "0.1.0"] ; routing core
|
||||
[metosin/reitit-ring "0.1.0"] ; ring-router
|
||||
[metosin/reitit-spec "0.1.0"] ; spec coercion
|
||||
[metosin/reitit-schema "0.1.0"] ; schema coercion
|
||||
[metosin/reitit-core "0.1.1-SNAPSHOT"] ; routing core
|
||||
[metosin/reitit-ring "0.1.1-SNAPSHOT"] ; ring-router
|
||||
[metosin/reitit-spec "0.1.1-SNAPSHOT"] ; spec coercion
|
||||
[metosin/reitit-schema "0.1.1-SNAPSHOT"] ; schema coercion
|
||||
[metosin/reitit-swagger "0.1.1-SNAPSHOT"] ; swagger docs
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
|
|
|||
|
|
@ -19,16 +19,17 @@ The following higher-level routers are also available as separate modules:
|
|||
To use Reitit, add the following dependecy to your project:
|
||||
|
||||
```clj
|
||||
[metosin/reitit "0.1.0"]
|
||||
[metosin/reitit "0.1.1-SNAPSHOT"]
|
||||
```
|
||||
|
||||
Optionally, the parts can be required separately:
|
||||
|
||||
```clj
|
||||
[metosin/reitit-core "0.1.0"] ; routing core
|
||||
[metosin/reitit-ring "0.1.0"] ; ring-router
|
||||
[metosin/reitit-spec "0.1.0"] ; spec coercion
|
||||
[metosin/reitit-schema "0.1.0"] ; schema coercion
|
||||
[metosin/reitit-core "0.1.1-SNAPSHOT"] ; routing core
|
||||
[metosin/reitit-ring "0.1.1-SNAPSHOT"] ; ring-router
|
||||
[metosin/reitit-spec "0.1.1-SNAPSHOT"] ; spec coercion
|
||||
[metosin/reitit-schema "0.1.1-SNAPSHOT"] ; schema coercion
|
||||
[metosin/reitit-swagger "0.1.1-SNAPSHOT"] ; swagger docs
|
||||
```
|
||||
|
||||
For discussions, there is a [#reitit](https://clojurians.slack.com/messages/reitit/) channel in [Clojurians slack](http://clojurians.net/).
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
[Ring](https://github.com/ring-clojure/ring) is a Clojure web applications library inspired by Python's WSGI and Ruby's Rack. By abstracting the details of HTTP into a simple, unified API, Ring allows web applications to be constructed of modular components that can be shared among a variety of applications, web servers, and web frameworks.
|
||||
|
||||
```clj
|
||||
[metosin/reitit-ring "0.1.0-SNAPSHOT"]
|
||||
[metosin/reitit-ring "0.1.1-SNAPSHOT"]
|
||||
```
|
||||
|
||||
Ring-router adds support for [handlers](https://github.com/ring-clojure/ring/wiki/Concepts#handlers), [middleware](https://github.com/ring-clojure/ring/wiki/Concepts#middleware) and routing based on `:request-method`. Ring-router is created with `reitit.ring/router` function. It uses a custom route compiler, creating a optimized data structure for handling route matches, with compiled middleware chain & handlers for all request methods. It also ensures that all routes have a `:handler` defined. `reitit.ring/ring-handler` is used to create a Ring handler out of ring-router.
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@
|
|||
:dependencies [[org.clojure/clojure "1.9.0"]
|
||||
[ring "1.6.3"]
|
||||
[metosin/muuntaja "0.4.1"]
|
||||
[metosin/reitit "0.1.0"]])
|
||||
[metosin/reitit "0.1.1-SNAPSHOT"]])
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@
|
|||
:dependencies [[org.clojure/clojure "1.9.0"]
|
||||
[ring "1.6.3"]
|
||||
[metosin/muuntaja "0.4.1"]
|
||||
[metosin/reitit "0.1.0"]])
|
||||
[metosin/reitit "0.1.1-SNAPSHOT"]])
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
(defproject metosin/reitit-core "0.1.0"
|
||||
(defproject metosin/reitit-core "0.1.1-SNAPSHOT"
|
||||
:description "Snappy data-driven router for Clojure(Script)"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"Pluggable coercion protocol"
|
||||
(-get-name [this] "Keyword name for the coercion")
|
||||
(-get-options [this] "Coercion options")
|
||||
(-get-apidocs [this model data] "???")
|
||||
(-get-apidocs [this spesification data] "Returns api documentation")
|
||||
(-compile-model [this model name] "Compiles a model")
|
||||
(-open-model [this model] "Returns a new model which allows extra keys in maps")
|
||||
(-encode-error [this error] "Converts error in to a serializable format")
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
(defproject metosin/reitit-ring "0.1.0"
|
||||
(defproject metosin/reitit-ring "0.1.1-SNAPSHOT"
|
||||
:description "Reitit: Ring routing"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
(defproject metosin/reitit-schema "0.1.0"
|
||||
(defproject metosin/reitit-schema "0.1.1-SNAPSHOT"
|
||||
:description "Reitit: Plumatic Schema coercion"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@
|
|||
[schema.coerce :as sc]
|
||||
[schema.utils :as su]
|
||||
[schema-tools.coerce :as stc]
|
||||
[reitit.coercion :as coercion]))
|
||||
[schema-tools.swagger.core :as swagger]
|
||||
[reitit.coercion :as coercion]
|
||||
[clojure.set :as set]))
|
||||
|
||||
(def string-coercion-matcher
|
||||
stc/string-coercion-matcher)
|
||||
|
|
@ -44,10 +46,28 @@
|
|||
(reify coercion/Coercion
|
||||
(-get-name [_] :schema)
|
||||
(-get-options [_] opts)
|
||||
(-get-apidocs [_ _ {:keys [parameters responses] :as info}]
|
||||
(cond-> (dissoc info :parameters :responses)
|
||||
parameters (assoc ::parameters parameters)
|
||||
responses (assoc ::responses responses)))
|
||||
(-get-apidocs [this spesification {:keys [parameters responses]}]
|
||||
;; TODO: this looks identical to spec, refactor when schema is done.
|
||||
(condp = spesification
|
||||
:swagger (swagger/swagger-spec
|
||||
(merge
|
||||
(if parameters
|
||||
{::swagger/parameters
|
||||
(into
|
||||
(empty parameters)
|
||||
(for [[k v] parameters]
|
||||
[k (coercion/-compile-model this v nil)]))})
|
||||
(if responses
|
||||
{::swagger/responses
|
||||
(into
|
||||
(empty responses)
|
||||
(for [[k response] responses
|
||||
:let [response (set/rename-keys response {:body :schema})]]
|
||||
[k (update response :schema #(coercion/-compile-model this % nil))]))})))
|
||||
(throw
|
||||
(ex-info
|
||||
(str "Can't produce Schema apidocs for " spesification)
|
||||
{:type spesification, :coercion :schema}))))
|
||||
(-compile-model [_ model _] model)
|
||||
(-open-model [_ schema] (st/open-schema schema))
|
||||
(-encode-error [_ error]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
(defproject metosin/reitit-spec "0.1.0"
|
||||
(defproject metosin/reitit-spec "0.1.1-SNAPSHOT"
|
||||
:description "Reitit: clojure.spec coercion"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@
|
|||
[spec-tools.data-spec :as ds]
|
||||
[spec-tools.conform :as conform]
|
||||
[spec-tools.swagger.core :as swagger]
|
||||
[reitit.coercion :as coercion])
|
||||
[reitit.coercion :as coercion]
|
||||
[clojure.set :as set])
|
||||
#?(:clj
|
||||
(:import (spec_tools.core Spec))))
|
||||
|
||||
|
|
@ -67,20 +68,27 @@
|
|||
(reify coercion/Coercion
|
||||
(-get-name [_] :spec)
|
||||
(-get-options [_] opts)
|
||||
(-get-apidocs [this _ {:keys [parameters responses] :as info}]
|
||||
(cond-> (dissoc info :parameters :responses)
|
||||
parameters (assoc
|
||||
::swagger/parameters
|
||||
(into
|
||||
(empty parameters)
|
||||
(for [[k v] parameters]
|
||||
[k (coercion/-compile-model this v nil)])))
|
||||
responses (assoc
|
||||
::swagger/responses
|
||||
(into
|
||||
(empty responses)
|
||||
(for [[k response] responses]
|
||||
[k (update response :body #(coercion/-compile-model this % nil))])))))
|
||||
(-get-apidocs [this spesification {:keys [parameters responses]}]
|
||||
(condp = spesification
|
||||
:swagger (swagger/swagger-spec
|
||||
(merge
|
||||
(if parameters
|
||||
{::swagger/parameters
|
||||
(into
|
||||
(empty parameters)
|
||||
(for [[k v] parameters]
|
||||
[k (coercion/-compile-model this v nil)]))})
|
||||
(if responses
|
||||
{::swagger/responses
|
||||
(into
|
||||
(empty responses)
|
||||
(for [[k response] responses
|
||||
:let [response (set/rename-keys response {:body :schema})]]
|
||||
[k (update response :schema #(coercion/-compile-model this % nil))]))})))
|
||||
(throw
|
||||
(ex-info
|
||||
(str "Can't produce Spec apidocs for " spesification)
|
||||
{:type spesification, :coercion :spec}))))
|
||||
(-compile-model [_ model name]
|
||||
(into-spec model name))
|
||||
(-open-model [_ spec] spec)
|
||||
|
|
|
|||
9
modules/reitit-swagger/project.clj
Normal file
9
modules/reitit-swagger/project.clj
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
(defproject metosin/reitit-swagger "0.1.1-SNAPSHOT"
|
||||
:description "Reitit: Swagger-support"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
:url "http://www.eclipse.org/legal/epl-v10.html"}
|
||||
:plugins [[lein-parent "0.3.2"]]
|
||||
:parent-project {:path "../../project.clj"
|
||||
:inherit [:deploy-repositories :managed-dependencies]}
|
||||
:dependencies [[metosin/reitit-core]])
|
||||
88
modules/reitit-swagger/src/reitit/swagger.cljc
Normal file
88
modules/reitit-swagger/src/reitit/swagger.cljc
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
(ns reitit.swagger
|
||||
(:require [reitit.core :as r]
|
||||
[meta-merge.core :refer [meta-merge]]
|
||||
[clojure.spec.alpha :as s]
|
||||
[clojure.set :as set]
|
||||
[reitit.coercion :as coercion]))
|
||||
|
||||
(s/def ::id keyword?)
|
||||
(s/def ::no-doc boolean?)
|
||||
(s/def ::tags (s/coll-of (s/or :keyword keyword? :string string?) :kind #{}))
|
||||
(s/def ::summary string?)
|
||||
(s/def ::description string?)
|
||||
|
||||
(s/def ::swagger (s/keys :req-un [::id]))
|
||||
(s/def ::spec (s/keys :opt-un [::swagger ::no-doc ::tags ::summary ::description]))
|
||||
|
||||
(def swagger-feature
|
||||
"Feature for handling swagger-documentation for routes.
|
||||
Works both with Middleware & Interceptors. Does not participate
|
||||
in actual request processing, just provides specs for the new
|
||||
documentation keys for the route data. Should be accompanied by a
|
||||
[[swagger-spec-handler]] to expose the swagger spec.
|
||||
|
||||
Swagger-specific keys:
|
||||
|
||||
| key | description |
|
||||
| --------------|-------------|
|
||||
| :swagger | map of any swagger-data. Must have `:id` to identify the api
|
||||
|
||||
The following common keys also contribute to swagger spec:
|
||||
|
||||
| key | description |
|
||||
| --------------|-------------|
|
||||
| :no-doc | optional boolean to exclude endpoint from api docs
|
||||
| :tags | optional set of strings of keywords tags for an endpoint api docs
|
||||
| :summary | optional short string summary of an endpoint
|
||||
| :description | optional long description of an endpoint. Supports http://spec.commonmark.org/
|
||||
|
||||
Also the coercion keys contribute to swagger spec:
|
||||
|
||||
| :parameters | optional input parameters for a route, in a format defined by the coercion
|
||||
| :responses | optional descriptions of responess, in a format defined by coercion
|
||||
|
||||
Example:
|
||||
|
||||
[\"/api\"
|
||||
{:swagger {:id :my-api}
|
||||
:middleware [reitit.swagger/swagger-feature]}
|
||||
|
||||
[\"/swagger.json\"
|
||||
{:get {:no-doc true
|
||||
:swagger {:info {:title \"my-api\"}}
|
||||
:handler reitit.swagger/swagger-spec-handler}}]
|
||||
|
||||
[\"/plus\"
|
||||
{:get {:tags #{:math}
|
||||
:summary \"adds numbers together\"
|
||||
:description \"takes `x` and `y` query-params and adds them together\"
|
||||
:parameters {:query {:x int?, :y int?}}
|
||||
:responses {200 {:body {:total pos-int?}}}
|
||||
:handler (fn [{:keys [parameters]}]
|
||||
{:status 200
|
||||
:body (+ (-> parameters :query :x)
|
||||
(-> parameters :query :y)})}}]]"
|
||||
{:name ::swagger
|
||||
:spec ::spec})
|
||||
|
||||
(defn swagger-spec-handler
|
||||
"Ring handler to emit swagger spec."
|
||||
[{:keys [::r/router ::r/match :request-method]}]
|
||||
(let [{:keys [id] :as swagger} (-> match :result request-method :data :swagger)
|
||||
swagger (set/rename-keys swagger {:id :x-id})
|
||||
this-swagger? #(-> % second :swagger :id (= id))
|
||||
transform-endpoint (fn [[method endpoint]]
|
||||
(let [coercion (-> endpoint :data :coercion)]
|
||||
(if (and endpoint (-> endpoint :data :no-doc not))
|
||||
[method (meta-merge
|
||||
(if coercion
|
||||
(coercion/-get-apidocs coercion :swagger (-> endpoint :data)))
|
||||
(-> endpoint :data (select-keys [:tags :summary :description]))
|
||||
(-> endpoint :data :swagger (dissoc :id)))])))
|
||||
transform-path (fn [[p _ c]]
|
||||
(if-let [endpoint (some->> c (keep transform-endpoint) (seq) (into {}))]
|
||||
[p endpoint]))]
|
||||
(if id
|
||||
(let [paths (->> router (r/routes) (filter this-swagger?) (map transform-path) (into {}))]
|
||||
{:status 200
|
||||
:body (meta-merge swagger {:paths paths})}))))
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
(defproject metosin/reitit "0.1.0"
|
||||
(defproject metosin/reitit "0.1.1-SNAPSHOT"
|
||||
:description "Snappy data-driven router for Clojure(Script)"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
@ -9,4 +9,5 @@
|
|||
:dependencies [[metosin/reitit-core]
|
||||
[metosin/reitit-ring]
|
||||
[metosin/reitit-spec]
|
||||
[metosin/reitit-schema]])
|
||||
[metosin/reitit-schema]
|
||||
[metosin/reitit-swagger]])
|
||||
|
|
|
|||
20
project.clj
20
project.clj
|
|
@ -1,4 +1,4 @@
|
|||
(defproject metosin/reitit-parent "0.1.0"
|
||||
(defproject metosin/reitit-parent "0.1.1-SNAPSHOT"
|
||||
:description "Snappy data-driven router for Clojure(Script)"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
@ -9,15 +9,16 @@
|
|||
:source-uri "https://github.com/metosin/reitit/{version}/{filepath}#L{line}"
|
||||
:metadata {:doc/format :markdown}}
|
||||
|
||||
:managed-dependencies [[metosin/reitit "0.1.0"]
|
||||
[metosin/reitit-core "0.1.0"]
|
||||
[metosin/reitit-ring "0.1.0"]
|
||||
[metosin/reitit-spec "0.1.0"]
|
||||
[metosin/reitit-schema "0.1.0"]
|
||||
:managed-dependencies [[metosin/reitit "0.1.1-SNAPSHOT"]
|
||||
[metosin/reitit-core "0.1.1-SNAPSHOT"]
|
||||
[metosin/reitit-ring "0.1.1-SNAPSHOT"]
|
||||
[metosin/reitit-spec "0.1.1-SNAPSHOT"]
|
||||
[metosin/reitit-schema "0.1.1-SNAPSHOT"]
|
||||
[metosin/reitit-swagger "0.1.1-SNAPSHOT"]
|
||||
|
||||
[meta-merge "1.0.0"]
|
||||
[metosin/spec-tools "0.6.1"]
|
||||
[metosin/schema-tools "0.10.0"]]
|
||||
[metosin/schema-tools "0.10.1-SNAPSHOT"]]
|
||||
|
||||
:plugins [[jonase/eastwood "0.2.5"]
|
||||
[lein-doo "0.1.9"]
|
||||
|
|
@ -33,14 +34,15 @@
|
|||
"modules/reitit-core/src"
|
||||
"modules/reitit-ring/src"
|
||||
"modules/reitit-spec/src"
|
||||
"modules/reitit-schema/src"]
|
||||
"modules/reitit-schema/src"
|
||||
"modules/reitit-swagger/src"]
|
||||
|
||||
:dependencies [[org.clojure/clojure "1.9.0"]
|
||||
[org.clojure/clojurescript "1.9.946"]
|
||||
|
||||
;; modules dependencies
|
||||
[metosin/reitit]
|
||||
[metosin/schema-tools "0.10.0"]
|
||||
[metosin/schema-tools]
|
||||
|
||||
[expound "0.5.0"]
|
||||
[orchestra "2017.11.12-1"]
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
set -e
|
||||
|
||||
# Modules
|
||||
for ext in reitit-core reitit-ring reitit-spec reitit-schema reitit; do
|
||||
for ext in reitit-core reitit-ring reitit-spec reitit-schema reitit-swagger reitit; do
|
||||
cd modules/$ext; lein "$@"; cd ../..;
|
||||
done
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
#!/bin/sh
|
||||
#!/bin/zsh
|
||||
|
||||
ext="sedbak$$"
|
||||
|
||||
find . -name project.clj -exec sed -i.$ext "s/\[metosin\/reitit\(.*\) \".*\"\]/[metosin\/reitit\1 \"$1\"\]/g" '{}' \;
|
||||
find . -name project.clj -exec sed -i.$ext "s/defproject metosin\/reitit\(.*\) \".*\"/defproject metosin\/reitit\1 \"$1\"/g" '{}' \;
|
||||
sed -i.$ext "s/\[metosin\/reitit\(.*\) \".*\"\]/[metosin\/reitit\1 \"$1\"\]/g" doc/*.md
|
||||
sed -i.$ext "s/\[metosin\/reitit\(.*\) \".*\"\]/[metosin\/reitit\1 \"$1\"\]/g" *.md
|
||||
sed -i.$ext "s/\[metosin\/reitit\(.*\) \".*\"\]/[metosin\/reitit\1 \"$1\"\]/g" **/*.md
|
||||
find . -name "*.$ext" -exec rm '{}' \;
|
||||
|
|
|
|||
81
test/cljc/reitit/swagger_test.clj
Normal file
81
test/cljc/reitit/swagger_test.clj
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
(ns reitit.swagger-test
|
||||
(:require [clojure.test :refer :all]
|
||||
[reitit.ring :as ring]
|
||||
[reitit.swagger :as swagger]
|
||||
[reitit.ring.coercion :as rrc]
|
||||
[reitit.coercion.spec :as spec]
|
||||
[reitit.coercion.schema :as schema]
|
||||
[schema.core :refer [Int]]))
|
||||
|
||||
(def app
|
||||
(ring/ring-handler
|
||||
(ring/router
|
||||
["/api"
|
||||
{:swagger {:id ::math}}
|
||||
|
||||
["/swagger.json"
|
||||
{:get {:no-doc true
|
||||
:swagger {:info {:title "my-api"}}
|
||||
:handler swagger/swagger-spec-handler}}]
|
||||
|
||||
["/spec" {:coercion spec/coercion}
|
||||
["/plus"
|
||||
{:get {:summary "plus"
|
||||
:parameters {:query {:x int?, :y int?}}
|
||||
:responses {200 {:body {:total int?}}}
|
||||
:handler (fn [{{{:keys [x y]} :query} :parameters}]
|
||||
{:status 200, :body {:total (+ x y)}})}}]]
|
||||
|
||||
["/schema" {:coercion schema/coercion}
|
||||
["/plus"
|
||||
{:get {:summary "plus"
|
||||
:parameters {:query {:x Int, :y Int}}
|
||||
:responses {200 {:body {:total Int}}}
|
||||
:handler (fn [{{{:keys [x y]} :query} :parameters}]
|
||||
{:status 200, :body {:total (+ x y)}})}}]]]
|
||||
|
||||
{:data {:middleware [swagger/swagger-feature
|
||||
rrc/coerce-exceptions-middleware
|
||||
rrc/coerce-request-middleware
|
||||
rrc/coerce-response-middleware]}})))
|
||||
|
||||
(deftest swagger-test
|
||||
(testing "endpoints work"
|
||||
(testing "spec"
|
||||
(is (= {:body {:total 3}, :status 200}
|
||||
(app
|
||||
{:request-method :get
|
||||
:uri "/api/spec/plus"
|
||||
:query-params {:x "2", :y "1"}}))))
|
||||
(testing "schema"
|
||||
(is (= {:body {:total 3}, :status 200}
|
||||
(app
|
||||
{:request-method :get
|
||||
:uri "/api/schema/plus"
|
||||
:query-params {:x "2", :y "1"}})))))
|
||||
(testing "swagger-spec"
|
||||
(let [spec (:body (app
|
||||
{:request-method :get
|
||||
:uri "/api/swagger.json"}))]
|
||||
(is (= {:x-id ::math
|
||||
:info {:title "my-api"}
|
||||
:paths {"/api/schema/plus" {:get {:summary "plus"}} ;; TODO: implement!
|
||||
"/api/spec/plus" {:get {:parameters [{:description ""
|
||||
:format "int64"
|
||||
:in "query"
|
||||
:name "x"
|
||||
:required true
|
||||
:type "integer"}
|
||||
{:description ""
|
||||
:format "int64"
|
||||
:in "query"
|
||||
:name "y"
|
||||
:required true
|
||||
:type "integer"}]
|
||||
:responses {200 {:description ""
|
||||
:schema {:properties {"total" {:format "int64"
|
||||
:type "integer"}}
|
||||
:required ["total"]
|
||||
:type "object"}}}
|
||||
:summary "plus"}}}}
|
||||
spec)))))
|
||||
Loading…
Reference in a new issue