mirror of
https://github.com/metosin/reitit.git
synced 2026-02-14 23:35:16 +00:00
Merge pull request #81 from metosin/swagger-ui
Swagger-ui as a separate module
This commit is contained in:
commit
98f860e921
24 changed files with 484 additions and 184 deletions
54
CHANGELOG.md
54
CHANGELOG.md
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
### `reitit-core`
|
### `reitit-core`
|
||||||
|
|
||||||
|
* `linear-router` now works with unnamed catch-all parameters, e.g. `"/files/*"`
|
||||||
* `match-by-path` encodes parameters into strings using (internal) `reitit.impl/IntoString` protocol. Handles all of: strings, numbers, keywords, booleans, objects. Fixes [#75](https://github.com/metosin/reitit/issues/75).
|
* `match-by-path` encodes parameters into strings using (internal) `reitit.impl/IntoString` protocol. Handles all of: strings, numbers, keywords, booleans, objects. Fixes [#75](https://github.com/metosin/reitit/issues/75).
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
|
|
@ -23,7 +24,7 @@
|
||||||
|
|
||||||
* `reitit.ring/default-handler` now works correctly with async ring
|
* `reitit.ring/default-handler` now works correctly with async ring
|
||||||
* new helper `reitit.ring/router` to compose routes outside of a router.
|
* new helper `reitit.ring/router` to compose routes outside of a router.
|
||||||
* `reitit.ring/create-resource-handler` function to serve static routes. See (docs)[https://metosin.github.io/reitit/ring/static.html].
|
* `reitit.ring/create-resource-handler` function to serve static routes. See [docs](https://metosin.github.io/reitit/ring/static.html).
|
||||||
|
|
||||||
* new dependencies:
|
* new dependencies:
|
||||||
|
|
||||||
|
|
@ -33,47 +34,24 @@
|
||||||
|
|
||||||
### `reitit-swagger`
|
### `reitit-swagger`
|
||||||
|
|
||||||
* New module to produce swagger-docs from routing tree, including `Coercion` definitions. Works with both middleware & interceptors and Schema & Spec. See [docs](https://metosin.github.io/reitit/swagger.html).
|
* New module to produce swagger-docs from routing tree, including `Coercion` definitions. Works with both middleware & interceptors and Schema & Spec. See [docs](https://metosin.github.io/reitit/ring/swagger.html).
|
||||||
|
|
||||||
|
### `reitit-swagger-ui`
|
||||||
|
|
||||||
|
New module to server pre-integrated [Swagger-ui](https://github.com/swagger-api/swagger-ui). See [docs](https://metosin.github.io/reitit/ring/swagger.html#swagger-ui).
|
||||||
|
|
||||||
|
* new dependencies:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
(require '[reitit.ring :as ring])
|
[metosin/jsonista "0.2.0"]
|
||||||
(require '[reitit.swagger :as swagger])
|
[metosin/ring-swagger-ui "2.2.10"]
|
||||||
(require '[reitit.ring.coercion :as rrc])
|
```
|
||||||
(require '[reitit.coercion.spec :as spec])
|
|
||||||
(require '[reitit.coercion.schema :as schema])
|
|
||||||
|
|
||||||
(require '[schema.core :refer [Int]])
|
### dependencies
|
||||||
|
|
||||||
(ring/ring-handler
|
```clj
|
||||||
(ring/router
|
[metosin/spec-tools "0.7.0"] is available but we use "0.6.1"
|
||||||
["/api"
|
[metosin/schema-tools "0.10.2"] is available but we use "0.10.1"
|
||||||
{:swagger {:id ::math}}
|
|
||||||
|
|
||||||
["/swagger.json"
|
|
||||||
{:get {:no-doc true
|
|
||||||
:swagger {:info {:title "my-api"}}
|
|
||||||
:handler (swagger/create-swagger-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 [rrc/coerce-exceptions-middleware
|
|
||||||
rrc/coerce-request-middleware
|
|
||||||
rrc/coerce-response-middleware
|
|
||||||
swagger/swagger-feature]}}))
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 0.1.0 (2018-2-19)
|
## 0.1.0 (2018-2-19)
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,8 @@ Optionally, the parts can be required separately:
|
||||||
[metosin/reitit-ring "0.1.1-SNAPSHOT"] ; ring-router
|
[metosin/reitit-ring "0.1.1-SNAPSHOT"] ; ring-router
|
||||||
[metosin/reitit-spec "0.1.1-SNAPSHOT"] ; spec coercion
|
[metosin/reitit-spec "0.1.1-SNAPSHOT"] ; spec coercion
|
||||||
[metosin/reitit-schema "0.1.1-SNAPSHOT"] ; schema coercion
|
[metosin/reitit-schema "0.1.1-SNAPSHOT"] ; schema coercion
|
||||||
[metosin/reitit-swagger "0.1.1-SNAPSHOT"] ; swagger docs
|
[metosin/reitit-swagger "0.1.1-SNAPSHOT"] ; swagger
|
||||||
|
[metosin/reitit-swagger-ui "0.1.1-SNAPSHOT"] ; swagger-ui
|
||||||
```
|
```
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,8 @@ Optionally, the parts can be required separately:
|
||||||
[metosin/reitit-ring "0.1.1-SNAPSHOT"] ; ring-router
|
[metosin/reitit-ring "0.1.1-SNAPSHOT"] ; ring-router
|
||||||
[metosin/reitit-spec "0.1.1-SNAPSHOT"] ; spec coercion
|
[metosin/reitit-spec "0.1.1-SNAPSHOT"] ; spec coercion
|
||||||
[metosin/reitit-schema "0.1.1-SNAPSHOT"] ; schema coercion
|
[metosin/reitit-schema "0.1.1-SNAPSHOT"] ; schema coercion
|
||||||
[metosin/reitit-swagger "0.1.1-SNAPSHOT"] ; swagger docs
|
[metosin/reitit-swagger "0.1.1-SNAPSHOT"] ; swagger
|
||||||
|
[metosin/reitit-swagger-ui "0.1.1-SNAPSHOT"] ; swagger-ui
|
||||||
```
|
```
|
||||||
|
|
||||||
For discussions, there is a [#reitit](https://clojurians.slack.com/messages/reitit/) channel in [Clojurians slack](http://clojurians.net/).
|
For discussions, there is a [#reitit](https://clojurians.slack.com/messages/reitit/) channel in [Clojurians slack](http://clojurians.net/).
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
* [Pluggable Coercion](ring/coercion.md)
|
* [Pluggable Coercion](ring/coercion.md)
|
||||||
* [Route Data Validation](ring/route_data_validation.md)
|
* [Route Data Validation](ring/route_data_validation.md)
|
||||||
* [Compiling Middleware](ring/compiling_middleware.md)
|
* [Compiling Middleware](ring/compiling_middleware.md)
|
||||||
|
* [Swagger Support](ring/swagger.md)
|
||||||
* [Performance](performance.md)
|
* [Performance](performance.md)
|
||||||
* [Interceptors (WIP)](interceptors.md)
|
* [Interceptors (WIP)](interceptors.md)
|
||||||
* [Swagger-support](swagger.md)
|
|
||||||
* [FAQ](faq.md)
|
* [FAQ](faq.md)
|
||||||
|
|
|
||||||
BIN
doc/images/swagger.png
Normal file
BIN
doc/images/swagger.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 203 KiB |
|
|
@ -8,3 +8,4 @@
|
||||||
* [Pluggable Coercion](coercion.md)
|
* [Pluggable Coercion](coercion.md)
|
||||||
* [Route Data Validation](route_data_validation.md)
|
* [Route Data Validation](route_data_validation.md)
|
||||||
* [Compiling Middleware](compiling_middleware.md)
|
* [Compiling Middleware](compiling_middleware.md)
|
||||||
|
* [Swagger Support](swagger.md)
|
||||||
|
|
|
||||||
251
doc/ring/swagger.md
Normal file
251
doc/ring/swagger.md
Normal file
|
|
@ -0,0 +1,251 @@
|
||||||
|
# Swagger Support
|
||||||
|
|
||||||
|
```
|
||||||
|
[metosin/reitit-swagger "0.1.1-SNAPSHOT"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Reitit supports [Swagger2](https://swagger.io/) documentation, thanks to [schema-tools](https://github.com/metosin/schema-tools) and [spec-tools](https://github.com/metosin/spec-tools). Documentation is extracted from route definitions, coercion `:parameters` and `:responses` and from a set of new documentation keys.
|
||||||
|
|
||||||
|
To enable swagger-documentation for a ring-router:
|
||||||
|
|
||||||
|
1. annotate you routes with swagger-data
|
||||||
|
2. mount a swagger-handler to serve the swagger-spec
|
||||||
|
3. optionally mount a swagger-ui to visualize the swagger-spec
|
||||||
|
|
||||||
|
## Swagger data
|
||||||
|
|
||||||
|
The following route data keys contribute to the generated swagger specification:
|
||||||
|
|
||||||
|
| key | description |
|
||||||
|
| --------------|-------------|
|
||||||
|
| :swagger | map of any swagger-data. Must have `:id` (keyword or sequence of keywords) to identify the api
|
||||||
|
| :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/
|
||||||
|
|
||||||
|
Coercion keys also contribute to the docs:
|
||||||
|
|
||||||
|
| key | description |
|
||||||
|
| --------------|-------------|
|
||||||
|
| :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
|
||||||
|
|
||||||
|
There is a `reitit.swagger.swagger-feature`, which acts as both a `Middleware` and an `Interceptor` that is not participating in any request processing - it just defines the route data specs for the routes it's mounted to. It is only needed if the [route data validation](route_data_validation.html) is turned on.
|
||||||
|
|
||||||
|
## Swagger spec
|
||||||
|
|
||||||
|
To serve the actual [Swagger Specification](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md), there is `reitit.swagger/create-swagger-handler`. It takes no arguments and returns a ring-handler which collects at request-time data from all routes for the same swagger api and returns a formatted Swagger spesification as Clojure data, to be encoded by a response formatter.
|
||||||
|
|
||||||
|
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 spesification. To make things easy, there is a pre-integrated version of the swagger-ui as a separate module.
|
||||||
|
|
||||||
|
```
|
||||||
|
[metosin/reitit-swagger-ui "0.1.1-SNAPSHOT"]
|
||||||
|
```
|
||||||
|
|
||||||
|
`reitit.swagger-ui/create-swagger-ui-hander` can be used to create a ring-handler to serve the swagger-ui. It accepts the following options:
|
||||||
|
|
||||||
|
| key | description |
|
||||||
|
| -----------------|-------------|
|
||||||
|
| :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:`
|
||||||
|
| :root | optional resource root, defaults to `"swagger-ui"`
|
||||||
|
| :url | path to swagger endpoint, defaults to `/swagger.json`
|
||||||
|
| :path | optional path to mount the handler to. Works only if mounted outside of a router.
|
||||||
|
| :config | parameters passed to swaggger-ui, keys transformed into camelCase. See [the docs](https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md)
|
||||||
|
|
||||||
|
We use swagger-ui from [ring-swagger-ui](https://github.com/metosin/ring-swagger-ui), which can be easily configured from routing application. It stores files `swagger-ui` in the resource classpath.
|
||||||
|
|
||||||
|
Webjars also hosts a [version](https://github.com/webjars/swagger-ui) of the swagger-ui.
|
||||||
|
|
||||||
|
**NOTE**: Currently, swagger-ui module is just for Clojure. ClojureScript-support welcome as a PR!
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Simple example
|
||||||
|
|
||||||
|
* two routes in a single swagger-api `::api`
|
||||||
|
* swagger-spec served from `"/swagger.json"`
|
||||||
|
* swagger-ui mounted to `"/"`
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(require '[reitit.ring :as ring])
|
||||||
|
(require '[reitit.swagger :as swagger])
|
||||||
|
(require '[reitit.swagger-ui :as swagger-ui])
|
||||||
|
|
||||||
|
(def app
|
||||||
|
(ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
[["/api"
|
||||||
|
["/ping" {:get (constantly "ping")}]
|
||||||
|
["/pong" {:post (constantly "pong")}]]
|
||||||
|
["/swagger.json"
|
||||||
|
{:get {:no-doc true
|
||||||
|
:handler (swagger/create-swagger-handler)}}]]
|
||||||
|
{:data {:swagger {:id ::api}}}) ;; for all routes
|
||||||
|
(swagger-ui/create-swagger-ui-handler {:path "/"})))
|
||||||
|
```
|
||||||
|
|
||||||
|
The generated swagger spec:
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(app {:request-method :get :uri "/swagger.json"})
|
||||||
|
;{:status 200
|
||||||
|
; :body {:swagger "2.0"
|
||||||
|
; :x-id #{:user/api}
|
||||||
|
; :paths {"/api/ping" {:get {}}
|
||||||
|
; "/api/pong" {:post {}}}}}
|
||||||
|
```
|
||||||
|
|
||||||
|
Swagger-ui:
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(app {:request-method :get :uri "/"})
|
||||||
|
; ... the swagger-ui index-page, configured correctly
|
||||||
|
```
|
||||||
|
|
||||||
|
### More complete example
|
||||||
|
|
||||||
|
* `clojure.spec` and `Schema` coercion
|
||||||
|
* swagger data (`:tags`, `:produces`, `:consumes`)
|
||||||
|
* swagger-spec served from `"/api/swagger.json"`
|
||||||
|
* swagger-ui mounted to `"/"`
|
||||||
|
* [Muuntaja](https://github.com/metosin/muuntaja) for request & response formatting
|
||||||
|
* `wrap-params` to capture query & path parameters
|
||||||
|
* missed routes are handled by `create-default-handler`
|
||||||
|
* served via [ring-jetty](https://github.com/ring-clojure/ring/tree/master/ring-jetty-adapter)
|
||||||
|
|
||||||
|
Whole example project is in [`/examples/ring-swagger`](https://github.com/metosin/reitit/tree/master/examples/ring-swagger).
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(require '[reitit.ring :as ring]
|
||||||
|
(require '[reitit.swagger :as swagger]
|
||||||
|
(require '[reitit.swagger-ui :as swagger-ui]
|
||||||
|
;; coercion
|
||||||
|
(require '[reitit.ring.coercion :as rrc]
|
||||||
|
(require '[reitit.coercion.spec :as spec]
|
||||||
|
(require '[reitit.coercion.schema :as schema]
|
||||||
|
(require '[schema.core :refer [Int]]
|
||||||
|
;; web server
|
||||||
|
(require '[ring.adapter.jetty :as jetty]
|
||||||
|
(require '[ring.middleware.params]
|
||||||
|
(require '[muuntaja.middleware]))
|
||||||
|
|
||||||
|
(def app
|
||||||
|
(ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
["/api"
|
||||||
|
{:swagger {:id ::math}}
|
||||||
|
|
||||||
|
["/swagger.json"
|
||||||
|
{:get {:no-doc true
|
||||||
|
:swagger {:info {:title "my-api"}}
|
||||||
|
:handler (swagger/create-swagger-handler)}}]
|
||||||
|
|
||||||
|
["/spec"
|
||||||
|
{:coercion spec/coercion
|
||||||
|
:swagger {:tags ["spec"]}}
|
||||||
|
|
||||||
|
["/plus"
|
||||||
|
{:get {:summary "plus with spec"
|
||||||
|
: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
|
||||||
|
:swagger {:tags ["schema"]}}
|
||||||
|
|
||||||
|
["/plus"
|
||||||
|
{:get {:summary "plus with schema"
|
||||||
|
: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 [ring.middleware.params/wrap-params
|
||||||
|
muuntaja.middleware/wrap-format
|
||||||
|
swagger/swagger-feature
|
||||||
|
rrc/coerce-exceptions-middleware
|
||||||
|
rrc/coerce-request-middleware
|
||||||
|
rrc/coerce-response-middleware]
|
||||||
|
:swagger {:produces #{"application/json"
|
||||||
|
"application/edn"
|
||||||
|
"application/transit+json"}
|
||||||
|
:consumes #{"application/json"
|
||||||
|
"application/edn"
|
||||||
|
"application/transit+json"}}}})
|
||||||
|
(ring/routes
|
||||||
|
(swagger-ui/create-swagger-ui-handler
|
||||||
|
{:path "", :url "/api/swagger.json"})
|
||||||
|
(ring/create-default-handler))))
|
||||||
|
|
||||||
|
(defn start []
|
||||||
|
(jetty/run-jetty #'app {:port 3000, :join? false})
|
||||||
|
(println "server running in port 3000"))
|
||||||
|
```
|
||||||
|
|
||||||
|
http://localhost:3000 should render now the swagger-ui:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Advanced
|
||||||
|
|
||||||
|
Route data in path `[:swagger :id]` can be either a keyword or a sequence of keywords. This enables one route to be part of multiple swagger apis. Normal route data [scoping rules](../basics/route_data.html#nested-route-data) rules apply.
|
||||||
|
|
||||||
|
Example with:
|
||||||
|
|
||||||
|
* 4 routes
|
||||||
|
* 2 swagger apis `::one` and `::two`
|
||||||
|
* 3 swagger specs
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(require '[reitit.ring :as ring])
|
||||||
|
(require '[reitit.swagger :as swagger])
|
||||||
|
|
||||||
|
(def ping-route
|
||||||
|
["/ping" {:get (constantly "ping")}])
|
||||||
|
|
||||||
|
(def spec-route
|
||||||
|
["/swagger.json"
|
||||||
|
{:get {:no-doc true
|
||||||
|
:handler (swagger/create-swagger-handler)}}])
|
||||||
|
|
||||||
|
(def app
|
||||||
|
(ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
[["/common" {:swagger {:id #{::one ::two}}} ping-route]
|
||||||
|
["/one" {:swagger {:id ::one}} ping-route spec-route]
|
||||||
|
["/two" {:swagger {:id ::two}} ping-route spec-route
|
||||||
|
["/deep" {:swagger {:id ::one}} ping-route]]
|
||||||
|
["/one-two" {:swagger {:id #{::one ::two}}} spec-route]])))
|
||||||
|
```
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(-> {:request-method :get, :uri "/one/swagger.json"} app :body :paths keys)
|
||||||
|
; ("/common/ping" "/one/ping" "/two/deep/ping")
|
||||||
|
```
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(-> {:request-method :get, :uri "/two/swagger.json"} app :body :paths keys)
|
||||||
|
; ("/common/ping" "/two/ping")
|
||||||
|
```
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(-> {:request-method :get, :uri "/one-two/swagger.json"} app :body :paths keys)
|
||||||
|
; ("/common/ping" "/one/ping" "/two/ping" "/two/deep/ping")
|
||||||
|
```
|
||||||
|
|
||||||
|
### TODO
|
||||||
|
|
||||||
|
* create a data-driven version of [Muuntaja](https://github.com/metosin/muuntaja) that integrates into `:produces` and `:consumes`
|
||||||
|
* ClojureScript
|
||||||
|
* example for [Macchiato](https://github.com/macchiato-framework)
|
||||||
|
* body formatting
|
||||||
|
* resource handling
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
# Swagger
|
|
||||||
|
|
||||||
Reitit supports [Swagger](https://swagger.io/) to generate route documentation. Documentation is extracted from existing coercion definitions `:parameters`, `:responses` and from a set of new doumentation keys.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
Current `reitit-swagger` draft (with `reitit-ring` & data-specs):
|
|
||||||
|
|
||||||
|
|
||||||
```clj
|
|
||||||
(require '[reitit.ring :as ring])
|
|
||||||
(require '[reitit.ring.swagger :as swagger])
|
|
||||||
(require '[reitit.ring.coercion :as rrc])
|
|
||||||
(require '[reitit.coercion.spec :as spec])
|
|
||||||
|
|
||||||
(def app
|
|
||||||
(ring/ring-handler
|
|
||||||
(ring/router
|
|
||||||
["/api"
|
|
||||||
|
|
||||||
;; identify a swagger api
|
|
||||||
;; there can be several in a routing tree
|
|
||||||
{:swagger {:id :math}}
|
|
||||||
|
|
||||||
;; the (undocumented) swagger spec endpoint
|
|
||||||
["/swagger.json"
|
|
||||||
{:get {:no-doc true
|
|
||||||
:swagger {:info {:title "my-api"}}
|
|
||||||
:handler (swagger/create-swagger-handler)}}]
|
|
||||||
|
|
||||||
;; the (undocumented) swagger-ui
|
|
||||||
;; [org.webjars/swagger-ui "3.13.4"]
|
|
||||||
["/docs/*"
|
|
||||||
{:get {:no-doc true
|
|
||||||
:handler (ring/create-resource-handler
|
|
||||||
{:root "META-INF/resources/webjars/swagger-ui"})}}]
|
|
||||||
|
|
||||||
["/minus"
|
|
||||||
{:get {:summary "minus"
|
|
||||||
:parameters {:query {:x int?, :y int?}}
|
|
||||||
:responses {200 {:body {:total int?}}}
|
|
||||||
:handler (fn [{{{:keys [x y]} :query} :parameters}]
|
|
||||||
{:status 200, :body {:total (- x y)}})}}]
|
|
||||||
|
|
||||||
["/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 [rrc/coerce-exceptions-middleware
|
|
||||||
rrc/coerce-request-middleware
|
|
||||||
rrc/coerce-response-middleware
|
|
||||||
;; provides just route data specs
|
|
||||||
swagger/swagger-feature]
|
|
||||||
:coercion spec/coercion}})))
|
|
||||||
```
|
|
||||||
|
|
@ -3,6 +3,5 @@
|
||||||
:dependencies [[org.clojure/clojure "1.9.0"]
|
:dependencies [[org.clojure/clojure "1.9.0"]
|
||||||
[ring "1.6.3"]
|
[ring "1.6.3"]
|
||||||
[metosin/muuntaja "0.5.0"]
|
[metosin/muuntaja "0.5.0"]
|
||||||
[org.webjars/swagger-ui "3.13.6"]
|
|
||||||
[metosin/reitit "0.1.1-SNAPSHOT"]]
|
[metosin/reitit "0.1.1-SNAPSHOT"]]
|
||||||
:repl-options {:init-ns example.server})
|
:repl-options {:init-ns example.server})
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
(ns example.server
|
(ns example.server
|
||||||
(:require [reitit.ring :as ring]
|
(:require [reitit.ring :as ring]
|
||||||
[reitit.swagger :as swagger]
|
[reitit.swagger :as swagger]
|
||||||
|
[reitit.swagger-ui :as swagger-ui]
|
||||||
[reitit.ring.coercion :as rrc]
|
[reitit.ring.coercion :as rrc]
|
||||||
[reitit.coercion.spec :as spec]
|
[reitit.coercion.spec :as spec]
|
||||||
[reitit.coercion.schema :as schema]
|
[reitit.coercion.schema :as schema]
|
||||||
|
|
@ -13,43 +14,53 @@
|
||||||
(def app
|
(def app
|
||||||
(ring/ring-handler
|
(ring/ring-handler
|
||||||
(ring/router
|
(ring/router
|
||||||
[["/api"
|
["/api"
|
||||||
{:swagger {:id ::math}}
|
{:swagger {:id ::math}}
|
||||||
|
|
||||||
["/swagger.json"
|
["/swagger.json"
|
||||||
{:get {:no-doc true
|
{:get {:no-doc true
|
||||||
:swagger {:info {:title "my-api"}}
|
:swagger {:info {:title "my-api"}}
|
||||||
:handler (swagger/create-swagger-handler)}}]
|
:handler (swagger/create-swagger-handler)}}]
|
||||||
|
|
||||||
["/spec" {:coercion spec/coercion}
|
["/spec"
|
||||||
["/plus"
|
{:coercion spec/coercion
|
||||||
{:get {:summary "plus"
|
:swagger {:tags ["spec"]}}
|
||||||
: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"
|
||||||
["/plus"
|
{:get {:summary "plus with spec"
|
||||||
{:get {:summary "plus"
|
:parameters {:query {:x int?, :y int?}}
|
||||||
:parameters {:query {:x Int, :y Int}}
|
:responses {200 {:body {:total int?}}}
|
||||||
:responses {200 {:body {:total Int}}}
|
:handler (fn [{{{:keys [x y]} :query} :parameters}]
|
||||||
:handler (fn [{{{:keys [x y]} :query} :parameters}]
|
{:status 200
|
||||||
{:status 200, :body {:total (+ x y)}})}}]]]
|
:body {:total (+ x y)}})}}]]
|
||||||
|
|
||||||
["/api-docs/*"
|
["/schema"
|
||||||
{:no-doc true
|
{:coercion schema/coercion
|
||||||
:handler (ring/create-resource-handler
|
:swagger {:tags ["schema"]}}
|
||||||
{:root "META-INF/resources/webjars/swagger-ui/3.13.6"})}]]
|
|
||||||
|
["/plus"
|
||||||
|
{:get {:summary "plus with schema"
|
||||||
|
: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 [ring.middleware.params/wrap-params
|
{:data {:middleware [ring.middleware.params/wrap-params
|
||||||
muuntaja.middleware/wrap-format
|
muuntaja.middleware/wrap-format
|
||||||
swagger/swagger-feature
|
swagger/swagger-feature
|
||||||
rrc/coerce-exceptions-middleware
|
rrc/coerce-exceptions-middleware
|
||||||
rrc/coerce-request-middleware
|
rrc/coerce-request-middleware
|
||||||
rrc/coerce-response-middleware]}})
|
rrc/coerce-response-middleware]
|
||||||
|
:swagger {:produces #{"application/json"
|
||||||
|
"application/edn"
|
||||||
|
"application/transit+json"}
|
||||||
|
:consumes #{"application/json"
|
||||||
|
"application/edn"
|
||||||
|
"application/transit+json"}}}})
|
||||||
(ring/routes
|
(ring/routes
|
||||||
(ring/create-resource-handler {:path "/"})
|
(swagger-ui/create-swagger-ui-handler
|
||||||
|
{:path "", :url "/api/swagger.json"})
|
||||||
(ring/create-default-handler))))
|
(ring/create-default-handler))))
|
||||||
|
|
||||||
(defn start []
|
(defn start []
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 203 KiB |
BIN
examples/ring-swagger/swagger.png
Normal file
BIN
examples/ring-swagger/swagger.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 203 KiB |
|
|
@ -56,7 +56,7 @@
|
||||||
(update-in [:path-parts] conj key)
|
(update-in [:path-parts] conj key)
|
||||||
(update-in [:path-params] conj key)
|
(update-in [:path-params] conj key)
|
||||||
(assoc-in [:path-constraints key] "([^/]+)"))))
|
(assoc-in [:path-constraints key] "([^/]+)"))))
|
||||||
#"^\*(.+)$" :>> (fn [[_ token]]
|
#"^\*(.*)$" :>> (fn [[_ token]]
|
||||||
(let [key (keyword token)]
|
(let [key (keyword token)]
|
||||||
(-> out
|
(-> out
|
||||||
(update-in [:path-parts] conj key)
|
(update-in [:path-parts] conj key)
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,8 @@
|
||||||
|
|
||||||
#?(:clj
|
#?(:clj
|
||||||
;; TODO: optimize for perf
|
;; TODO: optimize for perf
|
||||||
|
;; TODO: ring.middleware.not-modified/wrap-not-modified
|
||||||
|
;; TODO: ring.middleware.head/wrap-head
|
||||||
(defn create-resource-handler
|
(defn create-resource-handler
|
||||||
"A ring handler for serving classpath resources, configured via options:
|
"A ring handler for serving classpath resources, configured via options:
|
||||||
|
|
||||||
|
|
@ -82,19 +84,20 @@
|
||||||
| :allow-symlinks? | allow symlinks that lead to paths outside the root classpath directories, defaults to `false`"
|
| :allow-symlinks? | allow symlinks that lead to paths outside the root classpath directories, defaults to `false`"
|
||||||
([]
|
([]
|
||||||
(create-resource-handler nil))
|
(create-resource-handler nil))
|
||||||
([{:keys [parameter root path loader allow-symlinks? index-files]
|
([{:keys [parameter root path loader allow-symlinks? index-files paths]
|
||||||
:or {parameter (keyword "")
|
:or {parameter (keyword "")
|
||||||
root "public"
|
root "public"
|
||||||
index-files ["index.html"]}}]
|
index-files ["index.html"]
|
||||||
|
paths (constantly nil)}}]
|
||||||
(let [options {:root root, :loader loader, :allow-symlinks? allow-symlinks?}
|
(let [options {:root root, :loader loader, :allow-symlinks? allow-symlinks?}
|
||||||
path-size (count path)
|
path-size (inc (count path))
|
||||||
create (fn [handler]
|
create (fn [handler]
|
||||||
(fn
|
(fn
|
||||||
([request] (handler request))
|
([request] (handler request))
|
||||||
([request respond _] (respond (handler request)))))
|
([request respond _] (respond (handler request)))))
|
||||||
resource-response (fn [path accept]
|
resource-response (fn [path accept]
|
||||||
(if-let [path (accept path)]
|
(if-let [path (accept path)]
|
||||||
(if-let [response (response/resource-response path options)]
|
(if-let [response (or (paths path) (response/resource-response path options))]
|
||||||
(response/content-type response (mime-type/ext-mime-type path)))))
|
(response/content-type response (mime-type/ext-mime-type path)))))
|
||||||
path-or-index-response (fn [path accept]
|
path-or-index-response (fn [path accept]
|
||||||
(or (resource-response path accept)
|
(or (resource-response path accept)
|
||||||
|
|
|
||||||
|
|
@ -2,27 +2,33 @@
|
||||||
(:require [clojure.spec.alpha :as s]
|
(:require [clojure.spec.alpha :as s]
|
||||||
[spec-tools.core :as st #?@(:cljs [:refer [Spec]])]
|
[spec-tools.core :as st #?@(:cljs [:refer [Spec]])]
|
||||||
[spec-tools.data-spec :as ds]
|
[spec-tools.data-spec :as ds]
|
||||||
[spec-tools.conform :as conform]
|
[spec-tools.transform :as stt]
|
||||||
[spec-tools.swagger.core :as swagger]
|
[spec-tools.swagger.core :as swagger]
|
||||||
[reitit.coercion :as coercion]
|
[reitit.coercion :as coercion]
|
||||||
[clojure.set :as set])
|
[clojure.set :as set])
|
||||||
#?(:clj
|
#?(:clj
|
||||||
(:import (spec_tools.core Spec))))
|
(:import (spec_tools.core Spec))))
|
||||||
|
|
||||||
(def string-conforming
|
(def string-transformer
|
||||||
(st/type-conforming
|
(st/type-transformer
|
||||||
(merge
|
{:name :string
|
||||||
conform/string-type-conforming
|
:decoders (merge
|
||||||
conform/strip-extra-keys-type-conforming)))
|
stt/string-type-decoders
|
||||||
|
stt/strip-extra-keys-type-decoders)
|
||||||
|
:encoders stt/string-type-encoders
|
||||||
|
:default-encoder stt/any->any}))
|
||||||
|
|
||||||
(def json-conforming
|
(def json-transformer
|
||||||
(st/type-conforming
|
(st/type-transformer
|
||||||
(merge
|
{:name :json
|
||||||
conform/json-type-conforming
|
:decoders (merge
|
||||||
conform/strip-extra-keys-type-conforming)))
|
stt/json-type-decoders
|
||||||
|
stt/strip-extra-keys-type-decoders)
|
||||||
|
:encoders stt/json-type-encoders
|
||||||
|
:default-encoder stt/any->any}))
|
||||||
|
|
||||||
(def default-conforming
|
(def no-op-transformer
|
||||||
::default)
|
st/no-op-transformer)
|
||||||
|
|
||||||
(defprotocol IntoSpec
|
(defprotocol IntoSpec
|
||||||
(into-spec [this name]))
|
(into-spec [this name]))
|
||||||
|
|
@ -58,12 +64,12 @@
|
||||||
|
|
||||||
(def default-options
|
(def default-options
|
||||||
{:coerce-response? coerce-response?
|
{:coerce-response? coerce-response?
|
||||||
:conforming {:body {:default default-conforming
|
:transformers {:body {:default no-op-transformer
|
||||||
:formats {"application/json" json-conforming}}
|
:formats {"application/json" json-transformer}}
|
||||||
:string {:default string-conforming}
|
:string {:default string-transformer}
|
||||||
:response {:default default-conforming}}})
|
:response {:default no-op-transformer}}})
|
||||||
|
|
||||||
(defn create [{:keys [conforming coerce-response?] :as opts}]
|
(defn create [{:keys [transformers coerce-response?] :as opts}]
|
||||||
^{:type ::coercion/coercion}
|
^{:type ::coercion/coercion}
|
||||||
(reify coercion/Coercion
|
(reify coercion/Coercion
|
||||||
(-get-name [_] :spec)
|
(-get-name [_] :spec)
|
||||||
|
|
@ -98,16 +104,16 @@
|
||||||
(update :problems (partial mapv #(update % :pred stringify-pred)))))
|
(update :problems (partial mapv #(update % :pred stringify-pred)))))
|
||||||
(-request-coercer [this type spec]
|
(-request-coercer [this type spec]
|
||||||
(let [spec (coercion/-compile-model this spec nil)
|
(let [spec (coercion/-compile-model this spec nil)
|
||||||
{:keys [formats default]} (conforming type)]
|
{:keys [formats default]} (transformers type)]
|
||||||
(fn [value format]
|
(fn [value format]
|
||||||
(if-let [conforming (or (get formats format) default)]
|
(if-let [transformer (or (get formats format) default)]
|
||||||
(let [conformed (st/conform spec value conforming)]
|
(let [transformed (st/conform spec value transformer)]
|
||||||
(if (s/invalid? conformed)
|
(if (s/invalid? transformed)
|
||||||
(let [problems (st/explain-data spec value conforming)]
|
(let [problems (st/explain-data spec value transformer)]
|
||||||
(coercion/map->CoercionError
|
(coercion/map->CoercionError
|
||||||
{:spec spec
|
{:spec spec
|
||||||
:problems (::s/problems problems)}))
|
:problems (::s/problems problems)}))
|
||||||
(s/unform spec conformed)))
|
(s/unform spec transformed)))
|
||||||
value))))
|
value))))
|
||||||
(-response-coercer [this spec]
|
(-response-coercer [this spec]
|
||||||
(if (coerce-response? spec)
|
(if (coerce-response? spec)
|
||||||
|
|
|
||||||
11
modules/reitit-swagger-ui/project.clj
Normal file
11
modules/reitit-swagger-ui/project.clj
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
(defproject metosin/reitit-swagger-ui "0.1.1-SNAPSHOT"
|
||||||
|
:description "Reitit: Swagger-ui 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-ring]
|
||||||
|
[metosin/jsonista]
|
||||||
|
[metosin/ring-swagger-ui]])
|
||||||
52
modules/reitit-swagger-ui/src/reitit/swagger_ui.cljc
Normal file
52
modules/reitit-swagger-ui/src/reitit/swagger_ui.cljc
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
(ns reitit.swagger-ui
|
||||||
|
(:require [clojure.string :as str]
|
||||||
|
[reitit.ring :as ring]
|
||||||
|
#?@(:clj [
|
||||||
|
[jsonista.core :as j]])))
|
||||||
|
|
||||||
|
#?(:clj
|
||||||
|
(defn create-swagger-ui-handler
|
||||||
|
"Creates a ring handler which can be used to serve swagger-ui.
|
||||||
|
|
||||||
|
| key | description |
|
||||||
|
| -----------------|-------------|
|
||||||
|
| :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:`
|
||||||
|
| :root | optional resource root, defaults to `\"swagger-ui\"`
|
||||||
|
| :url | path to swagger endpoint, defaults to `/swagger.json`
|
||||||
|
| :path | optional path to mount the handler to. Works only if mounted outside of a router.
|
||||||
|
| :config | parameters passed to swaggger-ui, keys transformed into camelCase.
|
||||||
|
|
||||||
|
See https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md
|
||||||
|
for all available :config options
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
;; with defaults
|
||||||
|
(create-swagger-ui-handler)
|
||||||
|
|
||||||
|
;; with path and url set, swagger validator disabled
|
||||||
|
(swagger-ui/create-swagger-ui-handler
|
||||||
|
{:path \"\"
|
||||||
|
:url \"/api/swagger.json\"
|
||||||
|
:config {:validator-url nil})"
|
||||||
|
([]
|
||||||
|
(create-swagger-ui-handler nil))
|
||||||
|
([options]
|
||||||
|
(let [mixed-case (fn [k]
|
||||||
|
(let [[f & rest] (str/split (name k) #"-")]
|
||||||
|
(apply str (str/lower-case f) (map str/capitalize rest))))
|
||||||
|
mixed-case-key (fn [[k v]] [(mixed-case k) v])
|
||||||
|
config-json (fn [{:keys [url config]}] (j/write-value-as-string (merge config {:url url})))
|
||||||
|
conf-js (fn [opts] (str "window.API_CONF = " (config-json opts) ";"))
|
||||||
|
options (as-> options $
|
||||||
|
(update $ :root (fnil identity "swagger-ui"))
|
||||||
|
(update $ :url (fnil identity "/swagger.json"))
|
||||||
|
(update $ :config #(->> % (map mixed-case-key) (into {})))
|
||||||
|
(assoc $ :paths {"conf.js" {:headers {"Content-Type" "application/javascript"}
|
||||||
|
:status 200
|
||||||
|
:body (conf-js $)}
|
||||||
|
"config.json" {:headers {"Content-Type" "application/json"}
|
||||||
|
:status 200
|
||||||
|
:body (config-json $)}}))]
|
||||||
|
(ring/routes
|
||||||
|
(ring/create-resource-handler options))))))
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
[clojure.set :as set]
|
[clojure.set :as set]
|
||||||
[reitit.coercion :as coercion]))
|
[reitit.coercion :as coercion]))
|
||||||
|
|
||||||
(s/def ::id keyword?)
|
(s/def ::id (s/or :keyword keyword? :set (s/coll-of keyword? :into #{})))
|
||||||
(s/def ::no-doc boolean?)
|
(s/def ::no-doc boolean?)
|
||||||
(s/def ::tags (s/coll-of (s/or :keyword keyword? :string string?) :kind #{}))
|
(s/def ::tags (s/coll-of (s/or :keyword keyword? :string string?) :kind #{}))
|
||||||
(s/def ::summary string?)
|
(s/def ::summary string?)
|
||||||
|
|
@ -21,16 +21,11 @@
|
||||||
documentation keys for the route data. Should be accompanied by a
|
documentation keys for the route data. Should be accompanied by a
|
||||||
[[swagger-spec-handler]] to expose the swagger spec.
|
[[swagger-spec-handler]] to expose the swagger spec.
|
||||||
|
|
||||||
Swagger-specific keys:
|
New route data keys contributing to swagger docs:
|
||||||
|
|
||||||
| 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 |
|
| key | description |
|
||||||
| --------------|-------------|
|
| --------------|-------------|
|
||||||
|
| :swagger | map of any swagger-data. Must have `:id` (keyword or sequence of keywords) to identify the api
|
||||||
| :no-doc | optional boolean to exclude endpoint from api docs
|
| :no-doc | optional boolean to exclude endpoint from api docs
|
||||||
| :tags | optional set of strings of keywords tags for an endpoint api docs
|
| :tags | optional set of strings of keywords tags for an endpoint api docs
|
||||||
| :summary | optional short string summary of an endpoint
|
| :summary | optional short string summary of an endpoint
|
||||||
|
|
@ -38,6 +33,8 @@
|
||||||
|
|
||||||
Also the coercion keys contribute to swagger spec:
|
Also the coercion keys contribute to swagger spec:
|
||||||
|
|
||||||
|
| key | description |
|
||||||
|
| --------------|-------------|
|
||||||
| :parameters | optional input parameters for a route, in a format defined by the coercion
|
| :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
|
| :responses | optional descriptions of responess, in a format defined by coercion
|
||||||
|
|
||||||
|
|
@ -66,11 +63,16 @@
|
||||||
:spec ::spec})
|
:spec ::spec})
|
||||||
|
|
||||||
(defn create-swagger-handler []
|
(defn create-swagger-handler []
|
||||||
"Create a ring handler to emit swagger spec."
|
"Create a ring handler to emit swagger spec. Collects all routes from router which have
|
||||||
|
an intersecting `[:swagger :id]` and which are not marked with `:no-doc` route data."
|
||||||
(fn [{:keys [::r/router ::r/match :request-method]}]
|
(fn [{:keys [::r/router ::r/match :request-method]}]
|
||||||
(let [{:keys [id] :as swagger} (-> match :result request-method :data :swagger)
|
(let [{:keys [id] :as swagger} (-> match :result request-method :data :swagger)
|
||||||
swagger (set/rename-keys swagger {:id :x-id})
|
->set (fn [x] (if (or (set? x) (sequential? x)) (set x) (conj #{} x)))
|
||||||
accept-route #(-> % second :swagger :id (= id))
|
ids (->set id)
|
||||||
|
swagger (->> (dissoc swagger :id)
|
||||||
|
(merge {:swagger "2.0"
|
||||||
|
:x-id ids}))
|
||||||
|
accept-route #(-> % second :swagger :id ->set (set/intersection ids) seq)
|
||||||
transform-endpoint (fn [[method {{:keys [coercion no-doc swagger] :as data} :data}]]
|
transform-endpoint (fn [[method {{:keys [coercion no-doc swagger] :as data} :data}]]
|
||||||
(if (and data (not no-doc))
|
(if (and data (not no-doc))
|
||||||
[method
|
[method
|
||||||
|
|
|
||||||
|
|
@ -10,4 +10,5 @@
|
||||||
[metosin/reitit-ring]
|
[metosin/reitit-ring]
|
||||||
[metosin/reitit-spec]
|
[metosin/reitit-spec]
|
||||||
[metosin/reitit-schema]
|
[metosin/reitit-schema]
|
||||||
[metosin/reitit-swagger]])
|
[metosin/reitit-swagger]
|
||||||
|
[metosin/reitit-swagger-ui]])
|
||||||
|
|
|
||||||
17
project.clj
17
project.clj
|
|
@ -15,11 +15,14 @@
|
||||||
[metosin/reitit-spec "0.1.1-SNAPSHOT"]
|
[metosin/reitit-spec "0.1.1-SNAPSHOT"]
|
||||||
[metosin/reitit-schema "0.1.1-SNAPSHOT"]
|
[metosin/reitit-schema "0.1.1-SNAPSHOT"]
|
||||||
[metosin/reitit-swagger "0.1.1-SNAPSHOT"]
|
[metosin/reitit-swagger "0.1.1-SNAPSHOT"]
|
||||||
|
[metosin/reitit-swagger-ui "0.1.1-SNAPSHOT"]
|
||||||
|
|
||||||
[meta-merge "1.0.0"]
|
[meta-merge "1.0.0"]
|
||||||
[ring/ring-core "1.6.3"]
|
[ring/ring-core "1.6.3"]
|
||||||
[metosin/spec-tools "0.6.2-SNAPSHOT"]
|
[metosin/spec-tools "0.7.0"]
|
||||||
[metosin/schema-tools "0.10.2-SNAPSHOT"]]
|
[metosin/schema-tools "0.10.2"]
|
||||||
|
[metosin/ring-swagger-ui "2.2.10"]
|
||||||
|
[metosin/jsonista "0.2.0"]]
|
||||||
|
|
||||||
:plugins [[jonase/eastwood "0.2.5"]
|
:plugins [[jonase/eastwood "0.2.5"]
|
||||||
[lein-doo "0.1.10"]
|
[lein-doo "0.1.10"]
|
||||||
|
|
@ -36,10 +39,11 @@
|
||||||
"modules/reitit-ring/src"
|
"modules/reitit-ring/src"
|
||||||
"modules/reitit-spec/src"
|
"modules/reitit-spec/src"
|
||||||
"modules/reitit-schema/src"
|
"modules/reitit-schema/src"
|
||||||
"modules/reitit-swagger/src"]
|
"modules/reitit-swagger/src"
|
||||||
|
"modules/reitit-swagger-ui/src"]
|
||||||
|
|
||||||
:dependencies [[org.clojure/clojure "1.9.0"]
|
:dependencies [[org.clojure/clojure "1.9.0"]
|
||||||
[org.clojure/clojurescript "1.9.946"]
|
[org.clojure/clojurescript "1.10.238"]
|
||||||
|
|
||||||
;; modules dependencies
|
;; modules dependencies
|
||||||
[metosin/reitit]
|
[metosin/reitit]
|
||||||
|
|
@ -50,12 +54,13 @@
|
||||||
|
|
||||||
[ring "1.6.3"]
|
[ring "1.6.3"]
|
||||||
[metosin/muuntaja "0.5.0"]
|
[metosin/muuntaja "0.5.0"]
|
||||||
[metosin/jsonista "0.1.1"]
|
[metosin/jsonista "0.2.0"]
|
||||||
|
[metosin/ring-swagger-ui "2.2.10"]
|
||||||
|
|
||||||
[criterium "0.4.4"]
|
[criterium "0.4.4"]
|
||||||
[org.clojure/test.check "0.9.0"]
|
[org.clojure/test.check "0.9.0"]
|
||||||
[org.clojure/tools.namespace "0.2.11"]
|
[org.clojure/tools.namespace "0.2.11"]
|
||||||
[com.gfredericks/test.chuck "0.2.8"]]}
|
[com.gfredericks/test.chuck "0.2.9"]]}
|
||||||
:perf {:jvm-opts ^:replace ["-server"
|
:perf {:jvm-opts ^:replace ["-server"
|
||||||
"-Xmx4096m"
|
"-Xmx4096m"
|
||||||
"-Dclojure.compiler.direct-linking=true"]
|
"-Dclojure.compiler.direct-linking=true"]
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,6 @@
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Modules
|
# Modules
|
||||||
for ext in reitit-core reitit-ring reitit-spec reitit-schema reitit-swagger reitit; do
|
for ext in reitit-core reitit-ring reitit-spec reitit-schema reitit-swagger reitit-swagger-ui reitit; do
|
||||||
cd modules/$ext; lein "$@"; cd ../..;
|
cd modules/$ext; lein "$@"; cd ../..;
|
||||||
done
|
done
|
||||||
|
|
|
||||||
|
|
@ -84,11 +84,11 @@
|
||||||
(app valid-request))))
|
(app valid-request))))
|
||||||
|
|
||||||
(testing "invalid request"
|
(testing "invalid request"
|
||||||
(let [{:keys [status body]} (app invalid-request)]
|
(let [{:keys [status]} (app invalid-request)]
|
||||||
(is (= 400 status))))
|
(is (= 400 status))))
|
||||||
|
|
||||||
(testing "invalid response"
|
(testing "invalid response"
|
||||||
(let [{:keys [status body]} (app invalid-request2)]
|
(let [{:keys [status]} (app invalid-request2)]
|
||||||
(is (= 500 status))))))))
|
(is (= 500 status))))))))
|
||||||
|
|
||||||
(deftest schema-coercion-test
|
(deftest schema-coercion-test
|
||||||
|
|
|
||||||
|
|
@ -271,7 +271,9 @@
|
||||||
(ring/ring-handler
|
(ring/ring-handler
|
||||||
(ring/router
|
(ring/router
|
||||||
[["/ping" (constantly {:status 200, :body "pong"})]
|
[["/ping" (constantly {:status 200, :body "pong"})]
|
||||||
["/files/*" (ring/create-resource-handler)]])
|
["/files/*" (ring/create-resource-handler)]
|
||||||
|
["/*" (ring/create-resource-handler)]]
|
||||||
|
{:conflicts (constantly nil)})
|
||||||
(ring/create-default-handler))]
|
(ring/create-default-handler))]
|
||||||
|
|
||||||
["outside of a router"
|
["outside of a router"
|
||||||
|
|
@ -280,34 +282,37 @@
|
||||||
["/ping" (constantly {:status 200, :body "pong"})])
|
["/ping" (constantly {:status 200, :body "pong"})])
|
||||||
(ring/routes
|
(ring/routes
|
||||||
(ring/create-resource-handler {:path "/files"})
|
(ring/create-resource-handler {:path "/files"})
|
||||||
(ring/create-default-handler)))]]]
|
(ring/create-resource-handler {:path "/"})
|
||||||
|
(ring/create-default-handler)))]]
|
||||||
|
prefix ["/" "/files"]
|
||||||
|
:let [request (fn [uri] {:uri (str prefix uri), :request-method :get})]]
|
||||||
|
|
||||||
(testing test
|
(testing test
|
||||||
(testing "different file-types"
|
(testing "different file-types"
|
||||||
(let [response (app {:uri "/files/hello.json", :request-method :get})]
|
(let [response (app (request "/hello.json"))]
|
||||||
(is (= "application/json" (get-in response [:headers "Content-Type"])))
|
(is (= "application/json" (get-in response [:headers "Content-Type"])))
|
||||||
(is (get-in response [:headers "Last-Modified"]))
|
(is (get-in response [:headers "Last-Modified"]))
|
||||||
(is (= "{\"hello\": \"file\"}" (slurp (:body response)))))
|
(is (= "{\"hello\": \"file\"}" (slurp (:body response)))))
|
||||||
(let [response (app {:uri "/files/hello.xml", :request-method :get})]
|
(let [response (app (request "/hello.xml"))]
|
||||||
(is (= "text/xml" (get-in response [:headers "Content-Type"])))
|
(is (= "text/xml" (get-in response [:headers "Content-Type"])))
|
||||||
(is (get-in response [:headers "Last-Modified"]))
|
(is (get-in response [:headers "Last-Modified"]))
|
||||||
(is (= "<xml><hello>file</hello></xml>\n" (slurp (:body response))))))
|
(is (= "<xml><hello>file</hello></xml>\n" (slurp (:body response))))))
|
||||||
|
|
||||||
(testing "index-files"
|
(testing "index-files"
|
||||||
(let [response (app {:uri "/files/docs", :request-method :get})]
|
(let [response (app (request "/docs"))]
|
||||||
(is (= "text/html" (get-in response [:headers "Content-Type"])))
|
(is (= "text/html" (get-in response [:headers "Content-Type"])))
|
||||||
(is (get-in response [:headers "Last-Modified"]))
|
(is (get-in response [:headers "Last-Modified"]))
|
||||||
(is (= "<h1>hello</h1>\n" (slurp (:body response))))))
|
(is (= "<h1>hello</h1>\n" (slurp (:body response))))))
|
||||||
|
|
||||||
(testing "not found"
|
(testing "not found"
|
||||||
(let [response (app {:uri "/files/not-found", :request-method :get})]
|
(let [response (app (request "/not-found"))]
|
||||||
(is (= 404 (:status response)))))
|
(is (= 404 (:status response)))))
|
||||||
|
|
||||||
(testing "3-arity"
|
(testing "3-arity"
|
||||||
(let [result (atom nil)
|
(let [result (atom nil)
|
||||||
respond (partial reset! result)
|
respond (partial reset! result)
|
||||||
raise ::not-called]
|
raise ::not-called]
|
||||||
(app {:uri "/files/hello.xml", :request-method :get} respond raise)
|
(app (request "/hello.xml") respond raise)
|
||||||
(is (= "text/xml" (get-in @result [:headers "Content-Type"])))
|
(is (= "text/xml" (get-in @result [:headers "Content-Type"])))
|
||||||
(is (get-in @result [:headers "Last-Modified"]))
|
(is (get-in @result [:headers "Last-Modified"]))
|
||||||
(is (= "<xml><hello>file</hello></xml>\n" (slurp (:body @result))))))))))
|
(is (= "<xml><hello>file</hello></xml>\n" (slurp (:body @result))))))))))
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,8 @@
|
||||||
(let [spec (:body (app
|
(let [spec (:body (app
|
||||||
{:request-method :get
|
{:request-method :get
|
||||||
:uri "/api/swagger.json"}))]
|
:uri "/api/swagger.json"}))]
|
||||||
(is (= {:x-id ::math
|
(is (= {:x-id #{::math}
|
||||||
|
:swagger "2.0"
|
||||||
:info {:title "my-api"}
|
:info {:title "my-api"}
|
||||||
:paths {"/api/schema/plus" {:get {:parameters [{:description ""
|
:paths {"/api/schema/plus" {:get {:parameters [{:description ""
|
||||||
:format "int32"
|
:format "int32"
|
||||||
|
|
@ -97,3 +98,33 @@
|
||||||
:type "object"}}}
|
:type "object"}}}
|
||||||
:summary "plus"}}}}
|
:summary "plus"}}}}
|
||||||
spec)))))
|
spec)))))
|
||||||
|
|
||||||
|
(deftest multiple-swagger-apis-test
|
||||||
|
(let [ping-route ["/ping" {:get (constantly "ping")}]
|
||||||
|
spec-route ["/swagger.json"
|
||||||
|
{:get {:no-doc true
|
||||||
|
:handler (swagger/create-swagger-handler)}}]
|
||||||
|
app (ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
[["/common" {:swagger {:id #{::one ::two}}}
|
||||||
|
ping-route]
|
||||||
|
|
||||||
|
["/one" {:swagger {:id ::one}}
|
||||||
|
ping-route
|
||||||
|
spec-route]
|
||||||
|
|
||||||
|
["/two" {:swagger {:id ::two}}
|
||||||
|
ping-route
|
||||||
|
spec-route
|
||||||
|
["/deep" {:swagger {:id ::one}}
|
||||||
|
ping-route]]
|
||||||
|
["/one-two" {:swagger {:id #{::one ::two}}}
|
||||||
|
spec-route]]))
|
||||||
|
spec-paths (fn [uri]
|
||||||
|
(-> {:request-method :get, :uri uri} app :body :paths keys))]
|
||||||
|
(is (= ["/common/ping" "/one/ping" "/two/deep/ping"]
|
||||||
|
(spec-paths "/one/swagger.json")))
|
||||||
|
(is (= ["/common/ping" "/two/ping"]
|
||||||
|
(spec-paths "/two/swagger.json")))
|
||||||
|
(is (= ["/common/ping" "/one/ping" "/two/ping" "/two/deep/ping"]
|
||||||
|
(spec-paths "/one-two/swagger.json")))))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue