mirror of
https://github.com/metosin/reitit.git
synced 2025-12-16 16:01:11 +00:00
Merge branch 'master' into rework-pr-589
This commit is contained in:
commit
f1d26791fc
97 changed files with 1379 additions and 939 deletions
17
.github/workflows/testsuite.yml
vendored
17
.github/workflows/testsuite.yml
vendored
|
|
@ -70,9 +70,24 @@ jobs:
|
|||
run: ./scripts/test.sh cljs
|
||||
|
||||
lint:
|
||||
name: Lint
|
||||
name: Lint cljdoc.edn
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Verify cljdoc.edn
|
||||
run: curl -fsSL https://raw.githubusercontent.com/cljdoc/cljdoc/master/script/verify-cljdoc-edn | bash -s doc/cljdoc.edn
|
||||
|
||||
check-cljdoc:
|
||||
name: Check cljdoc analysis
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Setup Clojure
|
||||
uses: DeLaGuardo/setup-clojure@11.0
|
||||
with:
|
||||
lein: 2.9.5
|
||||
cli: 1.11.0.1100
|
||||
- name: Install cljdoc analyzer
|
||||
run: clojure -Ttools install io.github.cljdoc/cljdoc-analyzer '{:git/tag "RELEASE"}' :as cljdoc-analyzer
|
||||
- name: CljDoc Check
|
||||
run: ./scripts/cljdoc-check.sh
|
||||
|
|
|
|||
44
CHANGELOG.md
44
CHANGELOG.md
|
|
@ -14,7 +14,44 @@ We use [Break Versioning][breakver]. The version numbers follow a `<major>.<mino
|
|||
|
||||
## UNRELEASED
|
||||
|
||||
## 0.7.0-alpha5 (2023-06-14)
|
||||
|
||||
* **BREAKING**: `compile-request-coercers` returns a map with `:data` and `:coerce` instead of plain `:coerce` function
|
||||
* **BREAKING**: Parameter and Response schemas are merged into the route data vector - so they can be properly merged into the compiled result, fixes [#422](https://github.com/metosin/reitit/issues/422) - merging multiple schemas together works with `Malli` and `Schema`, partially with `data-spec` but not with `spec`.
|
||||
* Fixed some module dependencies so Cljdoc can properly analyze all the modules
|
||||
* Updated dependencies:
|
||||
|
||||
```clojure
|
||||
[metosin/schema-tools "0.13.1"] is available but we use "0.13.0"
|
||||
[com.fasterxml.jackson.core/jackson-core "2.15.1"] is available but we use "2.14.2"
|
||||
[com.fasterxml.jackson.core/jackson-databind "2.15.1"] is available but we use "2.14.2"
|
||||
```
|
||||
|
||||
**[compare](https://github.com/metosin/reitit/compare/0.7.0-alpha4...0.7.0-alpha5)**
|
||||
|
||||
## 0.7.0-alpha4 (2023-05-17)
|
||||
|
||||
* OpenAPI 3 parameter descriptions get populated from malli/spec/schema descriptions. [#612](https://github.com/metosin/reitit/issues/612)
|
||||
|
||||
**[compare](https://github.com/metosin/reitit/compare/0.7.0-alpha3...0.7.0-alpha4)**
|
||||
|
||||
## 0.7.0-alpha3 (2023-05-05)
|
||||
|
||||
* Compile `reitit.Trie` with Java 1.8 target for compatibility
|
||||
|
||||
**[compare](https://github.com/metosin/reitit/compare/0.7.0-alpha2...0.7.0-alpha3)**
|
||||
|
||||
## 0.7.0-alpha2 (2023-05-04)
|
||||
|
||||
* Fix reading fragment string on `Html5History` initialization
|
||||
* Add fragment string parameter to reitit-frontend functions ([#604](https://github.com/metosin/reitit/pull/604))
|
||||
|
||||
**[compare](https://github.com/metosin/reitit/compare/0.7.0-alpha1...0.7.0-alpha2)**
|
||||
|
||||
## 0.7.0-alpha1 (2023-05-03)
|
||||
|
||||
* Initial Openapi3 support. See [docs](./doc/ring/openapi.md). Works for simple cases but might still have some rough edges. [#84](https://github.com/metosin/reitit/issues/84)
|
||||
* Frontend: provide easy way to update current query params. [#600](https://github.com/metosin/reitit/issues/600)
|
||||
* Updated dependencies:
|
||||
|
||||
```clojure
|
||||
|
|
@ -23,6 +60,8 @@ We use [Break Versioning][breakver]. The version numbers follow a `<major>.<mino
|
|||
[ring/ring-core "1.10.0"] is available but we use "1.9.6"
|
||||
```
|
||||
|
||||
**[compare](https://github.com/metosin/reitit/compare/0.6.0...0.7.0-alpha1)**
|
||||
|
||||
## 0.6.0 (2023-02-21)
|
||||
|
||||
* Add reitit-frontend support for fragment string [#581](https://github.com/metosin/reitit/pull/581)
|
||||
|
|
@ -50,6 +89,8 @@ We use [Break Versioning][breakver]. The version numbers follow a `<major>.<mino
|
|||
[com.fasterxml.jackson.core/jackson-databind "2.14.2"] is available but we use "2.14.1"
|
||||
```
|
||||
|
||||
**[compare](https://github.com/metosin/reitit/compare/0.5.18...0.6.0)**
|
||||
|
||||
## 0.5.18 (2022-04-05)
|
||||
|
||||
* FIX [#334](https://github.com/metosin/reitit/pull/334) - Frontend: there is no way to catch the exception if coercion fails (via [#549](https://github.com/metosin/reitit/pull/549))
|
||||
|
|
@ -57,10 +98,13 @@ We use [Break Versioning][breakver]. The version numbers follow a `<major>.<mino
|
|||
* update jackson-databind for CVE-2020-36518 [#544](https://github.com/metosin/reitit/pull/544)
|
||||
* Balance parenthesis in docs [#547](https://github.com/metosin/reitit/pull/547)
|
||||
|
||||
**[compare](https://github.com/metosin/reitit/compare/0.5.17...0.5.18)**
|
||||
|
||||
## 0.5.17 (2022-03-10)
|
||||
|
||||
* FIX match-by-path is broken if there are no non-conflicting wildcard routes [#538](https://github.com/metosin/reitit/issues/538)
|
||||
|
||||
**[compare](https://github.com/metosin/reitit/compare/0.5.16...0.5.17)**
|
||||
|
||||
## 0.5.16 (2022-02-15)
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ There is [#reitit](https://clojurians.slack.com/messages/reitit/) in [Clojurians
|
|||
All main modules bundled:
|
||||
|
||||
```clj
|
||||
[metosin/reitit "0.6.0"]
|
||||
[metosin/reitit "0.7.0-alpha5"]
|
||||
```
|
||||
|
||||
Optionally, the parts can be required separately.
|
||||
|
|
@ -143,7 +143,7 @@ Invalid request:
|
|||
|
||||
## More examples
|
||||
|
||||
* [`reitit-ring` with coercion, swagger and default middleware](https://github.com/metosin/reitit/blob/master/examples/ring-swagger/src/example/server.clj)
|
||||
* [`reitit-ring` with coercion, swagger and default middleware](https://github.com/metosin/reitit/blob/master/examples/ring-malli-swagger/src/example/server.clj)
|
||||
* [`reitit-frontend`, the easy way](https://github.com/metosin/reitit/blob/master/examples/frontend/src/frontend/core.cljs)
|
||||
* [`reitit-frontend` with Keechma-style controllers](https://github.com/metosin/reitit/blob/master/examples/frontend-controllers/src/frontend/core.cljs)
|
||||
* [`reitit-http` with Pedestal](https://github.com/metosin/reitit/blob/master/examples/pedestal/src/example/server.clj)
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ There is [#reitit](https://clojurians.slack.com/messages/reitit/) in [Clojurians
|
|||
* `reitit-spec` [clojure.spec](https://clojure.org/about/spec) coercion
|
||||
* `reitit-schema` [Schema](https://github.com/plumatic/schema) coercion
|
||||
* `reitit-swagger` [Swagger2](https://swagger.io/) apidocs
|
||||
* `reitit-openapi` OpenAPI 3 apidocs
|
||||
* `reitit-swagger-ui` Integrated [Swagger UI](https://github.com/swagger-api/swagger-ui).
|
||||
* `reitit-frontend` Tools for [frontend routing](frontend/basics.md)
|
||||
* `reitit-http` http-routing with Pedestal-style Interceptors
|
||||
|
|
@ -40,7 +41,7 @@ There is [#reitit](https://clojurians.slack.com/messages/reitit/) in [Clojurians
|
|||
All bundled:
|
||||
|
||||
```clj
|
||||
[metosin/reitit "0.6.0"]
|
||||
[metosin/reitit "0.7.0-alpha5"]
|
||||
```
|
||||
|
||||
Optionally, the parts can be required separately.
|
||||
|
|
|
|||
|
|
@ -2,18 +2,21 @@
|
|||
|
||||
Routers can be configured via options. The following options are available for the `reitit.core/router`:
|
||||
|
||||
| key | description
|
||||
|---------------|-------------
|
||||
| `:path` | Base-path for routes
|
||||
| `:routes` | Initial resolved routes (default `[]`)
|
||||
| `:data` | Initial route data (default `{}`)
|
||||
| `:spec` | clojure.spec definition for a route data, see `reitit.spec` on how to use this
|
||||
| `:syntax` | Path-parameter syntax as keyword or set of keywords (default #{:bracket :colon})
|
||||
| `:expand` | Function of `arg opts => data` to expand route arg to route data (default `reitit.core/expand`)
|
||||
| `:coerce` | Function of `route opts => route` to coerce resolved route, can throw or return `nil`
|
||||
| `:meta-merge` | Function which follows the signature of `meta-merge.core/meta-merge`, useful for when you want to have more control over the meta merging
|
||||
| `:compile` | Function of `route opts => result` to compile a route handler
|
||||
| `:validate` | Function of `routes opts => ()` to validate route (data) via side-effects
|
||||
| `:conflicts` | Function of `{route #{route}} => ()` to handle conflicting routes
|
||||
| `:exception` | Function of `Exception => Exception ` to handle creation time exceptions (default `reitit.exception/exception`)
|
||||
| `:router` | Function of `routes opts => router` to override the actual router implementation
|
||||
| key | description
|
||||
|-----------------|-------------
|
||||
| `:path` | Base-path for routes
|
||||
| `:routes` | Initial resolved routes (default `[]`)
|
||||
| `:data` | Initial route data (default `{}`)
|
||||
| `:spec` | clojure.spec definition for a route data, see `reitit.spec` on how to use this
|
||||
| `:syntax` | Path-parameter syntax as keyword or set of keywords (default #{:bracket :colon})
|
||||
| `:expand` | Function of `arg opts => data` to expand route arg to route data (default `reitit.core/expand`)
|
||||
| `:coerce` | Function of `route opts => route` to coerce resolved route, can throw or return `nil`
|
||||
| `:compile` | Function of `route opts => result` to compile a route handler
|
||||
| `:validate` | Function of `routes opts => ()` to validate route (data) via side-effects
|
||||
| `:conflicts` | Function of `{route #{route}} => ()` to handle conflicting routes
|
||||
| `:exception` | Function of `Exception => Exception ` to handle creation time exceptions (default `reitit.exception/exception`)
|
||||
| `:meta-merge` | Function of `left right => merged` to merge route-data (default `meta-merge.core/meta-merge`)
|
||||
| `:update-paths` | Sequence of Vectors with elements `update-path` and `function`, used to preprocess route data
|
||||
| `:router` | Function of `routes opts => router` to override the actual router implementation
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ The default exception formatting uses `reitit.exception/exception`. It produces
|
|||
## Pretty Errors
|
||||
|
||||
```clj
|
||||
[metosin/reitit-dev "0.6.0"]
|
||||
[metosin/reitit-dev "0.7.0-alpha5"]
|
||||
```
|
||||
|
||||
For human-readable and developer-friendly exception messages, there is `reitit.dev.pretty/exception` (in the `reitit-dev` module). It is inspired by the lovely errors messages of [ELM](https://elm-lang.org/blog/compiler-errors-for-humans) and [ETA](https://twitter.com/jyothsnasrin/status/1037703436043603968) and uses [fipp](https://github.com/brandonbloom/fipp), [expound](https://github.com/bhb/expound) and [spell-spec](https://github.com/bhauman/spell-spec) for most of heavy lifting.
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@
|
|||
metosin/reitit-swagger-ui
|
||||
metosin/reitit-frontend
|
||||
metosin/reitit-sieppari
|
||||
metosin/reitit-pedestal]
|
||||
metosin/reitit-pedestal
|
||||
fi.metosin/reitit-openapi]
|
||||
:cljdoc.doc/tree
|
||||
[["Introduction" {:file "doc/README.md"}]
|
||||
["Basics" {}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Default Interceptors
|
||||
|
||||
```clj
|
||||
[metosin/reitit-interceptors "0.6.0"]
|
||||
[metosin/reitit-interceptors "0.7.0-alpha5"]
|
||||
```
|
||||
|
||||
Just like the [ring default middleware](../ring/default_middleware.md), but for interceptors.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ Reitit has also support for [interceptors](http://pedestal.io/reference/intercep
|
|||
## Reitit-http
|
||||
|
||||
```clj
|
||||
[metosin/reitit-http "0.6.0"]
|
||||
[metosin/reitit-http "0.7.0-alpha5"]
|
||||
```
|
||||
|
||||
A module for http-routing using interceptors instead of middleware. Builds on top of the [`reitit-ring`](../ring/ring.md) module having all the same features.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
[Pedestal](http://pedestal.io/) is a backend web framework for Clojure. `reitit-pedestal` provides an alternative routing engine for Pedestal.
|
||||
|
||||
```clj
|
||||
[metosin/reitit-pedestal "0.6.0"]
|
||||
[metosin/reitit-pedestal "0.7.0-alpha5"]
|
||||
```
|
||||
|
||||
Why should one use reitit instead of the Pedestal [default routing](http://pedestal.io/reference/routing-quick-reference)?
|
||||
|
|
@ -26,8 +26,8 @@ A minimalistic example on how to to swap the default-router with a reitit router
|
|||
```clj
|
||||
; [io.pedestal/pedestal.service "0.5.5"]
|
||||
; [io.pedestal/pedestal.jetty "0.5.5"]
|
||||
; [metosin/reitit-pedestal "0.6.0"]
|
||||
; [metosin/reitit "0.6.0"]
|
||||
; [metosin/reitit-pedestal "0.7.0-alpha5"]
|
||||
; [metosin/reitit "0.7.0-alpha5"]
|
||||
|
||||
(require '[io.pedestal.http :as server])
|
||||
(require '[reitit.pedestal :as pedestal])
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Sieppari
|
||||
|
||||
```clj
|
||||
[metosin/reitit-sieppari "0.6.0"]
|
||||
[metosin/reitit-sieppari "0.7.0-alpha5"]
|
||||
```
|
||||
|
||||
[Sieppari](https://github.com/metosin/sieppari) is a new and fast interceptor implementation for Clojure, with pluggable async supporting [core.async](https://github.com/clojure/core.async), [Manifold](https://github.com/ztellman/manifold) and [Promesa](http://funcool.github.io/promesa/latest).
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ There is an extra option in http-router (actually, in the underlying interceptor
|
|||
### Printing Context Diffs
|
||||
|
||||
```clj
|
||||
[metosin/reitit-interceptors "0.6.0"]
|
||||
[metosin/reitit-interceptors "0.7.0-alpha5"]
|
||||
```
|
||||
|
||||
Using `reitit.http.interceptors.dev/print-context-diffs` transformation, the context diffs between each interceptor are printed out to the console. To use it, add the following router option:
|
||||
|
|
|
|||
|
|
@ -157,21 +157,21 @@ You can also specify request and response body schemas per content-type. The syn
|
|||
```clj
|
||||
(def app
|
||||
(ring/ring-handler
|
||||
(ring/router
|
||||
["/api"
|
||||
["/example" {:post {:coercion reitit.coercion.schema/coercion
|
||||
:parameters {:request {:content {"application/json" {:y s/Int}
|
||||
"application/edn" {:z s/Int}}
|
||||
;; default if no content-type matches:
|
||||
:body {:yy s/Int}}}
|
||||
:responses {200 {:content {"application/json" {:w s/Int}
|
||||
"application/edn" {:x s/Int}}
|
||||
;; default if no content-type matches:
|
||||
:body {:ww s/Int}}
|
||||
:handler ...}}]]
|
||||
{:data {:middleware [rrc/coerce-exceptions-middleware
|
||||
rrc/coerce-request-middleware
|
||||
rrc/coerce-response-middleware]}})))
|
||||
(ring/router
|
||||
["/api"
|
||||
["/example" {:post {:coercion reitit.coercion.schema/coercion
|
||||
:request {:content {"application/json" {:schema {:y s/Int}}
|
||||
"application/edn" {:schema {:z s/Int}}}
|
||||
;; default if no content-type matches:
|
||||
:body {:yy s/Int}}
|
||||
:responses {200 {:content {"application/json" {:schema {:w s/Int}}
|
||||
"application/edn" {:schema {:x s/Int}}}
|
||||
;; default if no content-type matches:
|
||||
:body {:ww s/Int}}}
|
||||
:handler ...}}]]
|
||||
{:data {:middleware [rrc/coerce-exceptions-middleware
|
||||
rrc/coerce-request-middleware
|
||||
rrc/coerce-response-middleware]}})))
|
||||
```
|
||||
|
||||
## Pretty printing spec errors
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Default Middleware
|
||||
|
||||
```clj
|
||||
[metosin/reitit-middleware "0.6.0"]
|
||||
[metosin/reitit-middleware "0.7.0-alpha5"]
|
||||
```
|
||||
|
||||
Any Ring middleware can be used with `reitit-ring`, but using data-driven middleware is preferred as they are easier to manage and in many cases yield better performance. `reitit-middleware` contains a set of common ring middleware, lifted into data-driven middleware.
|
||||
|
|
@ -59,4 +59,4 @@ Partial sample output:
|
|||
|
||||
## Example app
|
||||
|
||||
See an example app with the default middleware in action: https://github.com/metosin/reitit/blob/master/examples/ring-swagger/src/example/server.clj.
|
||||
See an example app with the default middleware in action: <https://github.com/metosin/reitit/blob/master/examples/ring-malli-swagger/src/example/server.clj>.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Exception Handling with Ring
|
||||
|
||||
```clj
|
||||
[metosin/reitit-middleware "0.6.0"]
|
||||
[metosin/reitit-middleware "0.7.0-alpha5"]
|
||||
```
|
||||
|
||||
Exceptions thrown in router creation can be [handled with custom exception handler](../basics/error_messages.md). By default, exceptions thrown at runtime from a handler or a middleware are not caught by the `reitit.ring/ring-handler`. A good practice is to have a top-level exception handler to log and format errors for clients.
|
||||
|
|
|
|||
|
|
@ -5,8 +5,11 @@
|
|||
Reitit can generate [OpenAPI 3.1.0](https://spec.openapis.org/oas/v3.1.0)
|
||||
documentation. The feature works similarly to [Swagger documentation](swagger.md).
|
||||
|
||||
The [http-swagger](../../examples/http-swagger) and
|
||||
[ring-malli-swagger](../../examples/ring-malli-swagger) examples also
|
||||
The
|
||||
[ring-malli-swagger](../../examples/ring-malli-swagger)
|
||||
and
|
||||
[ring-spec-swagger](../../examples/ring-spec-swagger)
|
||||
examples also
|
||||
have OpenAPI documentation.
|
||||
|
||||
## OpenAPI data
|
||||
|
|
@ -31,6 +34,76 @@ Coercion keys also contribute to the docs:
|
|||
|
||||
Use `:request` parameter coercion (instead of `:body`) to unlock per-content-type coercions. See [Coercion](coercion.md).
|
||||
|
||||
## 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.
|
||||
|
||||
Malli:
|
||||
|
||||
```clj
|
||||
["/plus"
|
||||
{:post
|
||||
{:parameters
|
||||
{:body [:map
|
||||
[:x
|
||||
{:title "X parameter"
|
||||
:description "Description for X parameter"
|
||||
:json-schema/default 42}
|
||||
int?]
|
||||
[:y int?]]}}}]
|
||||
```
|
||||
|
||||
Schema:
|
||||
|
||||
```clj
|
||||
["/plus"
|
||||
{:post
|
||||
{:parameters
|
||||
{:body {:x (schema-tools.core/schema s/Num {:description "Description for X parameter"
|
||||
:openapi/example 13
|
||||
:openapi/default 42})
|
||||
:y int?}}}}]
|
||||
```
|
||||
|
||||
Spec:
|
||||
|
||||
```clj
|
||||
["/plus"
|
||||
{:post
|
||||
{:parameters
|
||||
{:body (spec-tools.data-spec/spec ::foo
|
||||
{:x (schema-tools.core/spec {:spec int?
|
||||
:description "Description for X parameter"
|
||||
:openapi/example 13
|
||||
:openapi/default 42})
|
||||
:y int?}}}}}]
|
||||
```
|
||||
|
||||
## 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 ring-malli-swagger example](../../examples/ring-malli-swagger) for
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
Read more about the [Ring Concepts](https://github.com/ring-clojure/ring/wiki/Concepts).
|
||||
|
||||
```clj
|
||||
[metosin/reitit-ring "0.6.0"]
|
||||
[metosin/reitit-ring "0.7.0-alpha5"]
|
||||
```
|
||||
|
||||
## `reitit.ring/router`
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Swagger Support
|
||||
|
||||
```
|
||||
[metosin/reitit-swagger "0.6.0"]
|
||||
[metosin/reitit-swagger "0.7.0-alpha5"]
|
||||
```
|
||||
|
||||
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.
|
||||
|
|
@ -47,7 +47,7 @@ If you need to post-process the generated spec, just wrap the handler with a cus
|
|||
[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.
|
||||
|
||||
```
|
||||
[metosin/reitit-swagger-ui "0.6.0"]
|
||||
[metosin/reitit-swagger-ui "0.7.0-alpha5"]
|
||||
```
|
||||
|
||||
`reitit.swagger-ui/create-swagger-ui-handler` can be used to create a ring-handler to serve the swagger-ui. It accepts the following options:
|
||||
|
|
@ -145,7 +145,7 @@ Another way to serve the swagger-ui is using the [default handler](default_handl
|
|||
* 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).
|
||||
Whole example project is in [`/examples/ring-spec-swagger`](https://github.com/metosin/reitit/tree/master/examples/ring-spec-swagger).
|
||||
|
||||
```clj
|
||||
(ns example.server
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ There is an extra option in the Ring router (actually, in the underlying middlew
|
|||
### Printing Request Diffs
|
||||
|
||||
```clj
|
||||
[metosin/reitit-middleware "0.6.0"]
|
||||
[metosin/reitit-middleware "0.7.0-alpha5"]
|
||||
```
|
||||
|
||||
Using `reitit.ring.middleware.dev/print-request-diffs` transformation, the request diffs between each middleware are printed out to the console. To use it, add the following router option:
|
||||
|
|
|
|||
|
|
@ -44,7 +44,3 @@ Coercion with Malli and Swagger generation.
|
|||
## ring-spec-swagger
|
||||
|
||||
Coercion with Spec and Swagger generation.
|
||||
|
||||
## ring-swagger
|
||||
|
||||
Coercion with Spec and Swagger generation. Same as previous!
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@
|
|||
:description "Reitit Buddy Auth App"
|
||||
:dependencies [[org.clojure/clojure "1.10.1"]
|
||||
[ring/ring-jetty-adapter "1.8.1"]
|
||||
[metosin/reitit "0.6.0"]
|
||||
[metosin/reitit "0.7.0-alpha5"]
|
||||
[buddy "2.0.0"]]
|
||||
:repl-options {:init-ns example.server})
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@
|
|||
[ring "1.7.1"]
|
||||
[hiccup "1.0.5"]
|
||||
[org.clojure/clojurescript "1.10.439"]
|
||||
[metosin/reitit "0.6.0"]
|
||||
[metosin/reitit-schema "0.6.0"]
|
||||
[metosin/reitit-frontend "0.6.0"]
|
||||
[metosin/reitit "0.7.0-alpha5"]
|
||||
[metosin/reitit-schema "0.7.0-alpha5"]
|
||||
[metosin/reitit-frontend "0.7.0-alpha5"]
|
||||
;; Just for pretty printting the match
|
||||
[fipp "0.6.14"]]
|
||||
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@
|
|||
[ring "1.7.1"]
|
||||
[hiccup "1.0.5"]
|
||||
[org.clojure/clojurescript "1.10.439"]
|
||||
[metosin/reitit "0.6.0"]
|
||||
[metosin/reitit-schema "0.6.0"]
|
||||
[metosin/reitit-frontend "0.6.0"]
|
||||
[metosin/reitit "0.7.0-alpha5"]
|
||||
[metosin/reitit-schema "0.7.0-alpha5"]
|
||||
[metosin/reitit-frontend "0.7.0-alpha5"]
|
||||
;; Just for pretty printting the match
|
||||
[fipp "0.6.14"]]
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
[:div
|
||||
[:ul
|
||||
[:li [:a {:href (rfe/href ::item {:id 1})} "Item 1"]]
|
||||
[:li [:a {:href (rfe/href ::item {:id 2} {:foo "bar"})} "Item 2"]]]
|
||||
[:li [:a {:href (rfe/href ::item {:id 2} {:foo "bar"} "zzz")} "Item 2"]]]
|
||||
(when id
|
||||
[:h2 "Selected item " id])
|
||||
[:p "Query params: " [:pre (pr-str query)]]
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@
|
|||
[ring "1.7.1"]
|
||||
[hiccup "1.0.5"]
|
||||
[org.clojure/clojurescript "1.10.520"]
|
||||
[metosin/reitit "0.6.0"]
|
||||
[metosin/reitit-spec "0.6.0"]
|
||||
[metosin/reitit-frontend "0.6.0"]
|
||||
[metosin/reitit "0.7.0-alpha5"]
|
||||
[metosin/reitit-spec "0.7.0-alpha5"]
|
||||
[metosin/reitit-frontend "0.7.0-alpha5"]
|
||||
;; Just for pretty printting the match
|
||||
[fipp "0.6.14"]]
|
||||
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@
|
|||
[ring "1.7.1"]
|
||||
[hiccup "1.0.5"]
|
||||
[org.clojure/clojurescript "1.10.520"]
|
||||
[metosin/reitit "0.6.0"]
|
||||
[metosin/reitit-spec "0.6.0"]
|
||||
[metosin/reitit-frontend "0.6.0"]
|
||||
[metosin/reitit "0.7.0-alpha5"]
|
||||
[metosin/reitit-spec "0.7.0-alpha5"]
|
||||
[metosin/reitit-frontend "0.7.0-alpha5"]
|
||||
;; Just for pretty printting the match
|
||||
[fipp "0.6.14"]]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
(defproject frontend-re-frame "0.1.0-SNAPSHOT"
|
||||
:dependencies [[org.clojure/clojure "1.10.0"]
|
||||
[org.clojure/clojurescript "1.10.520"]
|
||||
[metosin/reitit "0.6.0"]
|
||||
[metosin/reitit "0.7.0-alpha5"]
|
||||
[reagent "0.8.1"]
|
||||
[re-frame "0.10.6"]]
|
||||
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@
|
|||
[ring "1.8.1"]
|
||||
[hiccup "1.0.5"]
|
||||
[org.clojure/clojurescript "1.10.773"]
|
||||
[metosin/reitit "0.6.0"]
|
||||
[metosin/reitit-spec "0.6.0"]
|
||||
[metosin/reitit-frontend "0.6.0"]
|
||||
[metosin/reitit "0.7.0-alpha5"]
|
||||
[metosin/reitit-spec "0.7.0-alpha5"]
|
||||
[metosin/reitit-frontend "0.7.0-alpha5"]
|
||||
;; Just for pretty printting the match
|
||||
[fipp "0.6.23"]]
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
:dependencies [[org.clojure/clojure "1.10.0"]
|
||||
[ring/ring-jetty-adapter "1.7.1"]
|
||||
[aleph "0.4.7-alpha5"]
|
||||
[metosin/reitit "0.6.0"]
|
||||
[metosin/reitit "0.7.0-alpha5"]
|
||||
[metosin/ring-swagger-ui "5.0.0-alpha.0"]]
|
||||
:repl-options {:init-ns example.server})
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@
|
|||
{:get {:summary "downloads a file"
|
||||
:swagger {:produces ["image/png"]}
|
||||
:responses {200 {:description "an image"
|
||||
:content {"image/png" any?}}}
|
||||
:content {"image/png" {:schema any?}}}}
|
||||
:handler (fn [_]
|
||||
{:status 200
|
||||
:headers {"Content-Type" "image/png"}
|
||||
|
|
@ -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}]
|
||||
|
|
|
|||
|
|
@ -5,5 +5,5 @@
|
|||
[funcool/promesa "1.9.0"]
|
||||
[manifold "0.1.8"]
|
||||
[ring/ring-jetty-adapter "1.7.1"]
|
||||
[metosin/reitit "0.6.0"]]
|
||||
[metosin/reitit "0.7.0-alpha5"]]
|
||||
:repl-options {:init-ns example.server})
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@
|
|||
:description "Reitit coercion with vanilla ring"
|
||||
:dependencies [[org.clojure/clojure "1.10.0"]
|
||||
[ring/ring-jetty-adapter "1.7.1"]
|
||||
[metosin/reitit "0.6.0"]])
|
||||
[metosin/reitit "0.7.0-alpha5"]])
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
:dependencies [[org.clojure/clojure "1.10.0"]
|
||||
[io.pedestal/pedestal.service "0.5.5"]
|
||||
[io.pedestal/pedestal.jetty "0.5.5"]
|
||||
[metosin/reitit-malli "0.6.0"]
|
||||
[metosin/reitit-pedestal "0.6.0"]
|
||||
[metosin/reitit "0.6.0"]]
|
||||
[metosin/reitit-malli "0.7.0-alpha5"]
|
||||
[metosin/reitit-pedestal "0.7.0-alpha5"]
|
||||
[metosin/reitit "0.7.0-alpha5"]]
|
||||
:repl-options {:init-ns server})
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
:dependencies [[org.clojure/clojure "1.10.0"]
|
||||
[io.pedestal/pedestal.service "0.5.5"]
|
||||
[io.pedestal/pedestal.jetty "0.5.5"]
|
||||
[metosin/reitit-pedestal "0.6.0"]
|
||||
[metosin/reitit "0.6.0"]]
|
||||
[metosin/reitit-pedestal "0.7.0-alpha5"]
|
||||
[metosin/reitit "0.7.0-alpha5"]]
|
||||
:repl-options {:init-ns example.server})
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
:dependencies [[org.clojure/clojure "1.10.0"]
|
||||
[io.pedestal/pedestal.service "0.5.5"]
|
||||
[io.pedestal/pedestal.jetty "0.5.5"]
|
||||
[metosin/reitit-pedestal "0.6.0"]
|
||||
[metosin/reitit "0.6.0"]]
|
||||
[metosin/reitit-pedestal "0.7.0-alpha5"]
|
||||
[metosin/reitit "0.7.0-alpha5"]]
|
||||
:repl-options {:init-ns example.server})
|
||||
|
|
|
|||
|
|
@ -2,5 +2,5 @@
|
|||
:description "Reitit Ring App"
|
||||
:dependencies [[org.clojure/clojure "1.10.0"]
|
||||
[ring/ring-jetty-adapter "1.7.1"]
|
||||
[metosin/reitit "0.6.0"]]
|
||||
[metosin/reitit "0.7.0-alpha5"]]
|
||||
:repl-options {:init-ns example.server})
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
:description "Reitit Ring App with Integrant"
|
||||
:dependencies [[org.clojure/clojure "1.10.1"]
|
||||
[ring/ring-jetty-adapter "1.7.1"]
|
||||
[metosin/reitit "0.6.0"]
|
||||
[metosin/reitit "0.7.0-alpha5"]
|
||||
[integrant "0.7.0"]]
|
||||
:main example.server
|
||||
:repl-options {:init-ns user}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
:dependencies [[org.clojure/clojure "1.10.0"]
|
||||
[metosin/jsonista "0.2.6"]
|
||||
[ring/ring-jetty-adapter "1.7.1"]
|
||||
[metosin/reitit "0.6.0"]]
|
||||
[metosin/reitit "0.7.0-alpha5"]]
|
||||
:repl-options {:init-ns example.server}
|
||||
:profiles {:dev {:dependencies [[ring/ring-mock "0.3.2"]]}})
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# reitit-ring, malli, swagger
|
||||
# reitit-ring, malli, swagger, openapi 3
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
:dependencies [[org.clojure/clojure "1.10.0"]
|
||||
[metosin/jsonista "0.2.6"]
|
||||
[ring/ring-jetty-adapter "1.7.1"]
|
||||
[metosin/reitit "0.6.0"]
|
||||
[metosin/reitit "0.7.0-alpha5"]
|
||||
[metosin/ring-swagger-ui "5.0.0-alpha.0"]]
|
||||
:repl-options {:init-ns example.server}
|
||||
:profiles {:dev {:dependencies [[ring/ring-mock "0.3.2"]]}})
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@
|
|||
:swagger {:info {:title "my-api"
|
||||
:description "swagger docs with [malli](https://github.com/metosin/malli) and reitit-ring"
|
||||
:version "0.0.1"}
|
||||
;; used in /secure APIs below
|
||||
:securityDefinitions {"auth" {:type :apiKey
|
||||
:in :header
|
||||
:name "Example-Api-Key"}}
|
||||
:tags [{:name "files", :description "file api"}
|
||||
{:name "math", :description "math api"}]}
|
||||
:handler (swagger/create-swagger-handler)}}]
|
||||
|
|
@ -34,7 +38,11 @@
|
|||
{:get {:no-doc true
|
||||
:openapi {:info {:title "my-api"
|
||||
:description "openapi3 docs with [malli](https://github.com/metosin/malli) and reitit-ring"
|
||||
:version "0.0.1"}}
|
||||
:version "0.0.1"}
|
||||
;; used in /secure APIs below
|
||||
:components {:securitySchemes {"auth" {:type :apiKey
|
||||
:in :header
|
||||
:name "Example-Api-Key"}}}}
|
||||
:handler (openapi/create-openapi-handler)}}]
|
||||
|
||||
["/files"
|
||||
|
|
@ -53,7 +61,7 @@
|
|||
{:get {:summary "downloads a file"
|
||||
:swagger {:produces ["image/png"]}
|
||||
:responses {200 {:description "an image"
|
||||
:content {"image/png" any?}}}
|
||||
:content {"image/png" {:schema string?}}}}
|
||||
:handler (fn [_]
|
||||
{:status 200
|
||||
:headers {"Content-Type" "image/png"}
|
||||
|
|
@ -85,10 +93,42 @@
|
|||
:json-schema/default 42}
|
||||
int?]
|
||||
[:y int?]]}
|
||||
;; 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}}}}}}}}
|
||||
:responses {200 {:body [:map [:total int?]]}}
|
||||
:handler (fn [{{{:keys [x y]} :body} :parameters}]
|
||||
{:status 200
|
||||
:body {:total (+ x y)}})}}]]]
|
||||
:body {:total (+ x y)}})}}]]
|
||||
|
||||
["/secure"
|
||||
{:tags ["secure"]
|
||||
:openapi {:security [{"auth" []}]}
|
||||
:swagger {:security [{"auth" []}]}}
|
||||
["/get"
|
||||
{:get {:summary "endpoint authenticated with a header"
|
||||
:responses {200 {:body [:map [:secret :string]]}
|
||||
401 {:body [:map [:error :string]]}}
|
||||
:handler (fn [request]
|
||||
;; In a real app authentication would be handled by middleware
|
||||
(if (= "secret" (get-in request [:headers "example-api-key"]))
|
||||
{:status 200
|
||||
:body {:secret "I am a marmot"}}
|
||||
{:status 401
|
||||
:body {:error "unauthorized"}}))}}]]]
|
||||
|
||||
{;;:reitit.middleware/transform dev/print-request-diffs ;; pretty diffs
|
||||
;;:validate spec/validate ;; enable spec validation for route data
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 228 KiB After Width: | Height: | Size: 92 KiB |
|
|
@ -1,4 +1,4 @@
|
|||
# reitit-ring, clojure.spec, swagger
|
||||
# reitit-ring, clojure.spec, swagger, openapi 3
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
@ -7,6 +7,10 @@
|
|||
(start)
|
||||
```
|
||||
|
||||
- Swagger spec served at <http://localhost:3000/swagger.json>
|
||||
- Openapi spec served at <http://localhost:3000/openapi.json>
|
||||
- Swagger UI served at <http://localhost:3000/>
|
||||
|
||||
To test the endpoints using [httpie](https://httpie.org/):
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
:description "Reitit Ring App with Swagger"
|
||||
:dependencies [[org.clojure/clojure "1.10.0"]
|
||||
[ring/ring-jetty-adapter "1.7.1"]
|
||||
[metosin/reitit "0.6.0"]]
|
||||
[metosin/reitit "0.7.0-alpha5"]
|
||||
[metosin/ring-swagger-ui "5.0.0-alpha.0"]]
|
||||
:repl-options {:init-ns example.server}
|
||||
:profiles {:dev {:dependencies [[ring/ring-mock "0.3.2"]]}})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
(ns example.server
|
||||
(:require [reitit.ring :as ring]
|
||||
[reitit.coercion.spec]
|
||||
[reitit.openapi :as openapi]
|
||||
[reitit.swagger :as swagger]
|
||||
[reitit.swagger-ui :as swagger-ui]
|
||||
[reitit.ring.coercion :as coercion]
|
||||
|
|
@ -44,9 +45,15 @@
|
|||
{:get {:no-doc true
|
||||
:swagger {:info {:title "my-api"}}
|
||||
:handler (swagger/create-swagger-handler)}}]
|
||||
["/openapi.json"
|
||||
{:get {:no-doc true
|
||||
:openapi {:info {:title "my-api"
|
||||
:description "openapi3-docs with reitit-http"
|
||||
:version "0.0.1"}}
|
||||
:handler (openapi/create-openapi-handler)}}]
|
||||
|
||||
["/files"
|
||||
{:swagger {:tags ["files"]}}
|
||||
{:tags ["files"]}
|
||||
|
||||
["/upload"
|
||||
{:post {:summary "upload a file"
|
||||
|
|
@ -60,6 +67,8 @@
|
|||
["/download"
|
||||
{:get {:summary "downloads a file"
|
||||
:swagger {:produces ["image/png"]}
|
||||
:responses {200 {:description "an image"
|
||||
:content {"image/png" {:schema string?}}}}
|
||||
:handler (fn [_]
|
||||
{:status 200
|
||||
:headers {"Content-Type" "image/png"}
|
||||
|
|
@ -67,7 +76,7 @@
|
|||
(io/resource "reitit.png"))})}}]]
|
||||
|
||||
["/math"
|
||||
{:swagger {:tags ["math"]}}
|
||||
{:tags ["math"]}
|
||||
|
||||
["/plus"
|
||||
{:get {:summary "plus with spec query parameters"
|
||||
|
|
@ -111,6 +120,9 @@
|
|||
(swagger-ui/create-swagger-ui-handler
|
||||
{:path "/"
|
||||
:config {:validatorUrl nil
|
||||
:urls [{:name "swagger" :url "swagger.json"}
|
||||
{:name "openapi" :url "openapi.json"}]
|
||||
:urls.primaryName "openapi"
|
||||
:operationsSorter "alpha"}})
|
||||
(ring/create-default-handler))))
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 228 KiB After Width: | Height: | Size: 94 KiB |
11
examples/ring-swagger/.gitignore
vendored
11
examples/ring-swagger/.gitignore
vendored
|
|
@ -1,11 +0,0 @@
|
|||
/target
|
||||
/classes
|
||||
/checkouts
|
||||
pom.xml
|
||||
pom.xml.asc
|
||||
*.jar
|
||||
*.class
|
||||
/.lein-*
|
||||
/.nrepl-port
|
||||
.hgignore
|
||||
.hg/
|
||||
1
examples/ring-swagger/.lein-failures
Normal file
1
examples/ring-swagger/.lein-failures
Normal file
|
|
@ -0,0 +1 @@
|
|||
{}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
# Ring with Swagger example
|
||||
|
||||
## Usage
|
||||
|
||||
```clj
|
||||
> lein repl
|
||||
(start)
|
||||
```
|
||||
|
||||
To test the endpoints using [httpie](https://httpie.org/):
|
||||
|
||||
```bash
|
||||
http GET :3000/math/plus x==1 y==20
|
||||
http POST :3000/math/spec/plus x:=1 y:=20
|
||||
|
||||
http GET :3000/swagger.json
|
||||
```
|
||||
|
||||
<img src="https://raw.githubusercontent.com/metosin/reitit/master/examples/ring-swagger/swagger.png" />
|
||||
|
||||
## License
|
||||
|
||||
Copyright © 2017-2018 Metosin Oy
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
(defproject ring-example "0.1.0-SNAPSHOT"
|
||||
:description "Reitit Ring App with Swagger"
|
||||
:dependencies [[org.clojure/clojure "1.10.0"]
|
||||
[ring/ring-jetty-adapter "1.7.1"]
|
||||
[metosin/reitit "0.6.0"]]
|
||||
:repl-options {:init-ns example.server})
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 494 KiB |
|
|
@ -1,105 +0,0 @@
|
|||
(ns example.server
|
||||
(:require [reitit.ring :as ring]
|
||||
[reitit.coercion.spec]
|
||||
[reitit.swagger :as swagger]
|
||||
[reitit.swagger-ui :as swagger-ui]
|
||||
[reitit.ring.coercion :as coercion]
|
||||
[reitit.dev.pretty :as pretty]
|
||||
[reitit.ring.middleware.muuntaja :as muuntaja]
|
||||
[reitit.ring.middleware.exception :as exception]
|
||||
[reitit.ring.middleware.multipart :as multipart]
|
||||
[reitit.ring.middleware.parameters :as parameters]
|
||||
;; Uncomment to use
|
||||
; [reitit.ring.middleware.dev :as dev]
|
||||
[ring.adapter.jetty :as jetty]
|
||||
[muuntaja.core :as m]
|
||||
[clojure.java.io :as io]))
|
||||
|
||||
(def app
|
||||
(ring/ring-handler
|
||||
(ring/router
|
||||
[["/swagger.json"
|
||||
{:get {:no-doc true
|
||||
:swagger {:info {:title "my-api"
|
||||
:description "with reitit-ring"}}
|
||||
:handler (swagger/create-swagger-handler)}}]
|
||||
|
||||
["/files"
|
||||
{:swagger {:tags ["files"]}}
|
||||
|
||||
["/upload"
|
||||
{:post {:summary "upload a file"
|
||||
:parameters {:multipart {:file multipart/temp-file-part}}
|
||||
:responses {200 {:body {:name string?, :size int?}}}
|
||||
:handler (fn [{{{:keys [file]} :multipart} :parameters}]
|
||||
{:status 200
|
||||
:body {:name (:filename file)
|
||||
:size (:size file)}})}}]
|
||||
|
||||
["/download"
|
||||
{:get {:summary "downloads a file"
|
||||
:swagger {:produces ["image/png"]}
|
||||
:handler (fn [_]
|
||||
{:status 200
|
||||
:headers {"Content-Type" "image/png"}
|
||||
:body (-> "reitit.png"
|
||||
(io/resource)
|
||||
(io/input-stream))})}}]]
|
||||
|
||||
["/math"
|
||||
{:swagger {:tags ["math"]}}
|
||||
|
||||
["/plus"
|
||||
{:get {:summary "plus with spec query parameters"
|
||||
:parameters {:query {:x int?
|
||||
:y int?}}
|
||||
:responses {200 {:body {:total int?}}}
|
||||
:handler (fn [{{{:keys [x y]} :query} :parameters}]
|
||||
{:status 200
|
||||
:body {:total (+ x y)}})}
|
||||
:post {:summary "plus with spec body parameters"
|
||||
:parameters {:body {:x int?
|
||||
:y int?}}
|
||||
:responses {200 {:body {:total int?}}}
|
||||
:handler (fn [{{{:keys [x y]} :body} :parameters}]
|
||||
{:status 200
|
||||
:body {:total (+ x y)}})}}]]]
|
||||
|
||||
{;;:reitit.middleware/transform dev/print-request-diffs ;; pretty diffs
|
||||
;;:validate spec/validate ;; enable spec validation for route data
|
||||
;;:reitit.spec/wrap spell/closed ;; strict top-level validation
|
||||
:exception pretty/exception
|
||||
:data {:coercion reitit.coercion.spec/coercion
|
||||
:muuntaja m/instance
|
||||
:middleware [;; swagger feature
|
||||
swagger/swagger-feature
|
||||
;; query-params & form-params
|
||||
parameters/parameters-middleware
|
||||
;; content-negotiation
|
||||
muuntaja/format-negotiate-middleware
|
||||
;; encoding response body
|
||||
muuntaja/format-response-middleware
|
||||
;; exception handling
|
||||
(exception/create-exception-middleware
|
||||
{::exception/default (partial exception/wrap-log-to-console exception/default-handler)})
|
||||
;; decoding request body
|
||||
muuntaja/format-request-middleware
|
||||
;; coercing response bodys
|
||||
coercion/coerce-response-middleware
|
||||
;; coercing request parameters
|
||||
coercion/coerce-request-middleware
|
||||
;; multipart
|
||||
multipart/multipart-middleware]}})
|
||||
(ring/routes
|
||||
(swagger-ui/create-swagger-ui-handler
|
||||
{:path "/"
|
||||
:config {:validatorUrl nil
|
||||
:operationsSorter "alpha"}})
|
||||
(ring/create-default-handler))))
|
||||
|
||||
(defn start []
|
||||
(jetty/run-jetty #'app {:port 3000, :join? false})
|
||||
(println "server running in port 3000"))
|
||||
|
||||
(comment
|
||||
(start))
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 228 KiB |
|
|
@ -1,4 +1,4 @@
|
|||
(defproject metosin/reitit-core "0.6.0"
|
||||
(defproject metosin/reitit-core "0.7.0-alpha5"
|
||||
:description "Snappy data-driven router for Clojure(Script)"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
@ -10,4 +10,5 @@
|
|||
:parent-project {:path "../../project.clj"
|
||||
:inherit [:deploy-repositories :managed-dependencies]}
|
||||
:java-source-paths ["java-src"]
|
||||
:javac-options ["-Xlint:unchecked" "-target" "1.8" "-source" "1.8"]
|
||||
:dependencies [[meta-merge]])
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
(-get-name [this] "Keyword name for the coercion")
|
||||
(-get-options [this] "Coercion options")
|
||||
(-get-apidocs [this specification data] "Returns api documentation")
|
||||
;; TODO doc options:
|
||||
(-get-model-apidocs [this specification model options] "Convert model into a format that can be used in api docs")
|
||||
(-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")
|
||||
|
|
@ -37,7 +39,6 @@
|
|||
(def ^:no-doc default-parameter-coercion
|
||||
{:query (->ParameterCoercion :query-params :string true true)
|
||||
:body (->ParameterCoercion :body-params :body false false)
|
||||
:request (->ParameterCoercion :body-params :request false false)
|
||||
:form (->ParameterCoercion :form-params :string true true)
|
||||
:header (->ParameterCoercion :headers :string true true)
|
||||
:path (->ParameterCoercion :path-params :string true true)
|
||||
|
|
@ -83,33 +84,53 @@
|
|||
value)
|
||||
|
||||
;; TODO: support faster key walking, walk/keywordize-keys is quite slow...
|
||||
(defn request-coercer [coercion type model {::keys [extract-request-format parameter-coercion serialize-failed-result]
|
||||
(defn request-coercer [coercion type model {::keys [extract-request-format parameter-coercion serialize-failed-result skip]
|
||||
:or {extract-request-format extract-request-format-default
|
||||
parameter-coercion default-parameter-coercion}}]
|
||||
parameter-coercion default-parameter-coercion
|
||||
skip #{}}}]
|
||||
(if coercion
|
||||
(if-let [{:keys [keywordize? open? in style]} (parameter-coercion type)]
|
||||
(let [transform (comp (if keywordize? walk/keywordize-keys identity) in)
|
||||
->open (if open? #(-open-model coercion %) identity)
|
||||
format-schema-pairs (if (= :request style)
|
||||
(conj (:content model) [:default (:body model)])
|
||||
[[:default model]])
|
||||
format->coercer (some->> (for [[format schema] format-schema-pairs
|
||||
:when schema]
|
||||
[format (-request-coercer coercion (case style :request :body style) (->open schema))])
|
||||
(filter second)
|
||||
(seq)
|
||||
(into {}))]
|
||||
(when format->coercer
|
||||
(fn [request]
|
||||
(let [value (transform request)
|
||||
format (extract-request-format request)
|
||||
coercer (or (format->coercer format)
|
||||
(format->coercer :default)
|
||||
-identity-coercer)
|
||||
result (coercer value format)]
|
||||
(if (error? result)
|
||||
(request-coercion-failed! result coercion value in request serialize-failed-result)
|
||||
result))))))))
|
||||
(when-let [{:keys [keywordize? open? in style]} (parameter-coercion type)]
|
||||
(when-not (skip style)
|
||||
(let [transform (comp (if keywordize? walk/keywordize-keys identity) in)
|
||||
->open (if open? #(-open-model coercion %) identity)
|
||||
coercer (-request-coercer coercion style (->open model))]
|
||||
(when coercer
|
||||
(fn [request]
|
||||
(let [value (transform request)
|
||||
format (extract-request-format request)
|
||||
result (coercer value format)]
|
||||
(if (error? result)
|
||||
(request-coercion-failed! result coercion value in request serialize-failed-result)
|
||||
result)))))))))
|
||||
|
||||
(defn get-default-schema [request-or-response]
|
||||
(or (-> request-or-response :content :default :schema)
|
||||
(:body request-or-response)))
|
||||
|
||||
(defn get-default [request-or-response]
|
||||
(or (-> request-or-response :content :default)
|
||||
(some->> request-or-response :body (assoc {} :schema))))
|
||||
|
||||
(defn content-request-coercer [coercion {:keys [content body]} {::keys [extract-request-format serialize-failed-result]
|
||||
:or {extract-request-format extract-request-format-default}}]
|
||||
(when coercion
|
||||
(let [in :body-params
|
||||
format->coercer (some->> (concat (when body
|
||||
[[:default (-request-coercer coercion :body body)]])
|
||||
(for [[format {:keys [schema]}] content, :when schema]
|
||||
[format (-request-coercer coercion :body schema)]))
|
||||
(filter second) (seq) (into (array-map)))]
|
||||
(when format->coercer
|
||||
(fn [request]
|
||||
(let [value (in request)
|
||||
format (extract-request-format request)
|
||||
coercer (or (format->coercer format)
|
||||
(format->coercer :default)
|
||||
-identity-coercer)
|
||||
result (coercer value format)]
|
||||
(if (error? result)
|
||||
(request-coercion-failed! result coercion value in request serialize-failed-result)
|
||||
result)))))))
|
||||
|
||||
(defn extract-response-format-default [request _]
|
||||
(-> request :muuntaja/response :format))
|
||||
|
|
@ -117,17 +138,18 @@
|
|||
(defn response-coercer [coercion {:keys [content body]} {:keys [extract-response-format serialize-failed-result]
|
||||
:or {extract-response-format extract-response-format-default}}]
|
||||
(if coercion
|
||||
(let [per-format-coercers (some->> (for [[format schema] content]
|
||||
[format (-response-coercer coercion schema)])
|
||||
(filter second)
|
||||
(seq)
|
||||
(into {}))
|
||||
default (when body (-response-coercer coercion body))]
|
||||
(when (or per-format-coercers default)
|
||||
(let [format->coercer (some->> (concat (when body
|
||||
[[:default (-response-coercer coercion body)]])
|
||||
(for [[format {:keys [schema]}] content, :when schema]
|
||||
[format (-response-coercer coercion schema)]))
|
||||
(filter second) (seq) (into (array-map)))]
|
||||
(when format->coercer
|
||||
(fn [request response]
|
||||
(let [format (extract-response-format request response)
|
||||
value (:body response)
|
||||
coercer (get per-format-coercers format (or default -identity-coercer))
|
||||
coercer (or (format->coercer format)
|
||||
(format->coercer :default)
|
||||
-identity-coercer)
|
||||
result (coercer value format)]
|
||||
(if (error? result)
|
||||
(response-coercion-failed! result coercion value request response serialize-failed-result)
|
||||
|
|
@ -151,52 +173,23 @@
|
|||
(impl/fast-assoc response :body (coercer request response))
|
||||
response)))
|
||||
|
||||
(defn request-coercers [coercion parameters opts]
|
||||
(some->> (for [[k v] parameters
|
||||
:when v]
|
||||
[k (request-coercer coercion k v opts)])
|
||||
(filter second)
|
||||
(seq)
|
||||
(into {})))
|
||||
(defn request-coercers
|
||||
([coercion parameters opts]
|
||||
(some->> (for [[k v] parameters, :when v]
|
||||
[k (request-coercer coercion k v opts)])
|
||||
(filter second) (seq) (into {})))
|
||||
([coercion parameters route-request opts]
|
||||
(let [crc (when route-request (some->> (content-request-coercer coercion route-request opts) (array-map :request)))
|
||||
rcs (request-coercers coercion parameters (cond-> opts route-request (assoc ::skip #{:body})))]
|
||||
(if (and crc rcs) (into crc (vec rcs)) (or crc rcs)))))
|
||||
|
||||
(defn response-coercers [coercion responses opts]
|
||||
(some->> (for [[status model] responses]
|
||||
[status (response-coercer coercion model opts)])
|
||||
(filter second)
|
||||
(seq)
|
||||
(into {})))
|
||||
|
||||
;;
|
||||
;; api-docs
|
||||
;;
|
||||
|
||||
(defn -warn-unsupported-coercions [{:keys [parameters responses] :as data}]
|
||||
(when (:request parameters)
|
||||
(println "WARNING [reitit.coercion]: swagger apidocs don't support :request coercion"))
|
||||
(when (some :content (vals responses))
|
||||
(println "WARNING [reitit.coercion]: swagger apidocs don't support :responses :content coercion")))
|
||||
|
||||
(defn get-apidocs [coercion specification data]
|
||||
(let [swagger-parameter {:query :query
|
||||
:body :body
|
||||
:form :formData
|
||||
:header :header
|
||||
:path :path
|
||||
:multipart :formData}]
|
||||
(case specification
|
||||
:openapi (-get-apidocs coercion specification data)
|
||||
:swagger (do
|
||||
(-warn-unsupported-coercions data)
|
||||
(->> (update
|
||||
data
|
||||
:parameters
|
||||
(fn [parameters]
|
||||
(->> parameters
|
||||
(map (fn [[k v]] [(swagger-parameter k) v]))
|
||||
(filter first)
|
||||
(into {}))))
|
||||
(-get-apidocs coercion specification))))))
|
||||
(filter second) (seq) (into {})))
|
||||
|
||||
(defn -compile-parameters [data coercion]
|
||||
(impl/path-update data [[[:parameters any?] #(-compile-model coercion % nil)]]))
|
||||
|
||||
;;
|
||||
;; integration
|
||||
|
|
@ -204,17 +197,29 @@
|
|||
|
||||
(defn compile-request-coercers
|
||||
"A router :compile implementation which reads the `:parameters`
|
||||
and `:coercion` data to create compiled coercers into Match under
|
||||
`:result. A pre-requisite to use [[coerce!]]."
|
||||
[[_ {:keys [parameters coercion]}] opts]
|
||||
and `:coercion` data to both compile the schemas and create compiled coercers
|
||||
into Match under `:result with the following keys:
|
||||
|
||||
| key | description
|
||||
| ----------|-------------
|
||||
| `:data` | data with compiled schemas
|
||||
| `:coerce` | function of `Match -> coerced parameters` to coerce parameters
|
||||
|
||||
A pre-requisite to use [[coerce!]].
|
||||
|
||||
NOTE: this is not needed with ring/http, where the coercion compilation is
|
||||
managed in the request coercion middleware/interceptors."
|
||||
[[_ {:keys [parameters coercion] :as data}] opts]
|
||||
(if (and parameters coercion)
|
||||
(request-coercers coercion parameters opts)))
|
||||
(let [{:keys [parameters] :as data} (-compile-parameters data coercion)]
|
||||
{:data data
|
||||
:coerce (request-coercers coercion parameters opts)})))
|
||||
|
||||
(defn coerce!
|
||||
"Returns a map of coerced input parameters using pre-compiled
|
||||
coercers under `:result` (provided by [[compile-request-coercers]].
|
||||
Throws `ex-info` if parameters can't be coerced
|
||||
If coercion or parameters are not defined, return `nil`"
|
||||
"Returns a map of coerced input parameters using pre-compiled coercers in `Match`
|
||||
under path `[:result :coerce]` (provided by [[compile-request-coercers]].
|
||||
Throws `ex-info` if parameters can't be coerced. If coercion or parameters
|
||||
are not defined, returns `nil`"
|
||||
[match]
|
||||
(if-let [coercers (:result match)]
|
||||
(if-let [coercers (-> match :result :coerce)]
|
||||
(coerce-request coercers match)))
|
||||
|
|
|
|||
|
|
@ -307,6 +307,7 @@
|
|||
:coerce (fn coerce [route _] route)
|
||||
:compile (fn compile [[_ {:keys [handler]}] _] handler)
|
||||
:exception exception/exception
|
||||
:update-paths [[[:parameters any?] impl/accumulate]]
|
||||
:conflicts (fn throw! [conflicts] (exception/fail! :path-conflicts conflicts))})
|
||||
|
||||
(defn router
|
||||
|
|
@ -314,20 +315,22 @@
|
|||
Selects implementation based on route details. The following options
|
||||
are available:
|
||||
|
||||
| key | description
|
||||
| -------------|-------------
|
||||
| `:path` | Base-path for routes
|
||||
| `:routes` | Initial resolved routes (default `[]`)
|
||||
| `:data` | Initial route data (default `{}`)
|
||||
| `:spec` | clojure.spec definition for a route data, see `reitit.spec` on how to use this
|
||||
| `:syntax` | Path-parameter syntax as keyword or set of keywords (default #{:bracket :colon})
|
||||
| `:expand` | Function of `arg opts => data` to expand route arg to route data (default `reitit.core/expand`)
|
||||
| `:coerce` | Function of `route opts => route` to coerce resolved route, can throw or return `nil`
|
||||
| `:compile` | Function of `route opts => result` to compile a route handler
|
||||
| `:validate` | Function of `routes opts => ()` to validate route (data) via side-effects
|
||||
| `:conflicts` | Function of `{route #{route}} => ()` to handle conflicting routes
|
||||
| `:exception` | Function of `Exception => Exception ` to handle creation time exceptions (default `reitit.exception/exception`)
|
||||
| `:router` | Function of `routes opts => router` to override the actual router implementation"
|
||||
| key | description
|
||||
| ----------------|-------------
|
||||
| `:path` | Base-path for routes
|
||||
| `:routes` | Initial resolved routes (default `[]`)
|
||||
| `:data` | Initial route data (default `{}`)
|
||||
| `:spec` | clojure.spec definition for a route data, see `reitit.spec` on how to use this
|
||||
| `:syntax` | Path-parameter syntax as keyword or set of keywords (default #{:bracket :colon})
|
||||
| `:expand` | Function of `arg opts => data` to expand route arg to route data (default `reitit.core/expand`)
|
||||
| `:coerce` | Function of `route opts => route` to coerce resolved route, can throw or return `nil`
|
||||
| `:compile` | Function of `route opts => result` to compile a route handler
|
||||
| `:validate` | Function of `routes opts => ()` to validate route (data) via side-effects
|
||||
| `:conflicts` | Function of `{route #{route}} => ()` to handle conflicting routes
|
||||
| `:exception` | Function of `Exception => Exception ` to handle creation time exceptions (default `reitit.exception/exception`)
|
||||
| `:meta-merge` | Function of `left right => merged` to merge route-data (default `meta-merge.core/meta-merge`)
|
||||
| `:update-paths` | Sequence of Vectors with elements `update-path` and `function`, used to preprocess route data
|
||||
| `:router` | Function of `routes opts => router` to override the actual router implementation"
|
||||
([raw-routes]
|
||||
(router raw-routes {}))
|
||||
([raw-routes opts]
|
||||
|
|
|
|||
|
|
@ -9,6 +9,49 @@
|
|||
(:import (java.net URLEncoder URLDecoder)
|
||||
(java.util HashMap Map))))
|
||||
|
||||
;;
|
||||
;; path-update
|
||||
;;
|
||||
|
||||
(defn -match [path path-map]
|
||||
(letfn [(match [x f] (if (fn? f) (f x) (= x f)))]
|
||||
(reduce
|
||||
(fn [_ [ps f]]
|
||||
(when (and (>= (count path) (count ps)) (every? identity (map match path ps)))
|
||||
(reduced f)))
|
||||
nil path-map)))
|
||||
|
||||
(defn -path-vals [m path-map]
|
||||
(letfn [(-path-vals [l p m]
|
||||
(reduce
|
||||
(fn [l [k v]]
|
||||
(let [p' (conj p k)
|
||||
f (-match p' path-map)]
|
||||
(cond
|
||||
f (conj l [p' (f v)])
|
||||
(and (map? v) (seq v)) (-path-vals l p' v)
|
||||
:else (conj l [p' v]))))
|
||||
l m))]
|
||||
(-path-vals [] [] m)))
|
||||
|
||||
(defn -assoc-in-path-vals [c]
|
||||
(reduce (partial apply assoc-in) {} c))
|
||||
|
||||
(defn path-update [m path-map]
|
||||
(-> (-path-vals m path-map)
|
||||
(-assoc-in-path-vals)))
|
||||
|
||||
(defn accumulator? [x]
|
||||
(-> x meta ::accumulator))
|
||||
|
||||
(defn accumulate
|
||||
([x] (if-not (accumulator? x) (with-meta [x] {::accumulator true}) x))
|
||||
([x y] (into (accumulate x) y)))
|
||||
|
||||
;;
|
||||
;; impl
|
||||
;;
|
||||
|
||||
(defn parse [path opts]
|
||||
(let [path #?(:clj (.intern ^String (trie/normalize path opts)) :cljs (trie/normalize path opts))
|
||||
path-parts (trie/split-path path opts)
|
||||
|
|
@ -60,8 +103,10 @@
|
|||
(defn map-data [f routes]
|
||||
(mapv (fn [[p ds]] [p (f p ds)]) routes))
|
||||
|
||||
(defn meta-merge [left right opts]
|
||||
((or (:meta-merge opts) mm/meta-merge) left right))
|
||||
(defn meta-merge [left right {:keys [meta-merge update-paths]}]
|
||||
(let [update (if update-paths #(path-update % update-paths) identity)
|
||||
merge (or meta-merge mm/meta-merge)]
|
||||
(merge (update left) (update right))))
|
||||
|
||||
(defn merge-data [opts p x]
|
||||
(reduce
|
||||
|
|
|
|||
|
|
@ -82,8 +82,11 @@
|
|||
|
||||
(s/def :reitit.core.coercion/model any?)
|
||||
|
||||
(s/def :reitit.core.coercion/schema any?)
|
||||
(s/def :reitit.core.coercion/map-model (s/keys :opt-un [:reitit.core.coercion/schema]))
|
||||
|
||||
(s/def :reitit.core.coercion/content
|
||||
(s/map-of string? :reitit.core.coercion/model))
|
||||
(s/map-of (s/or :string string?, :default #{:default}) :reitit.core.coercion/map-model))
|
||||
|
||||
(s/def :reitit.core.coercion/query :reitit.core.coercion/model)
|
||||
(s/def :reitit.core.coercion/body :reitit.core.coercion/model)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
(defproject metosin/reitit-dev "0.6.0"
|
||||
(defproject metosin/reitit-dev "0.7.0-alpha5"
|
||||
:description "Snappy data-driven router for Clojure(Script)"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
@ -11,4 +11,6 @@
|
|||
:dependencies [[metosin/reitit-core]
|
||||
[com.bhauman/spell-spec]
|
||||
[expound]
|
||||
[fipp]])
|
||||
[fipp]
|
||||
[org.clojure/core.rrb-vector]
|
||||
[mvxcvi/arrangement]])
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
(defproject metosin/reitit-frontend "0.6.0"
|
||||
(defproject metosin/reitit-frontend "0.7.0-alpha5"
|
||||
:description "Reitit: Clojurescript frontend routing core"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
(:require [clojure.set :as set]
|
||||
[reitit.coercion :as coercion]
|
||||
[reitit.core :as r]
|
||||
[reitit.impl :as impl]
|
||||
goog.Uri
|
||||
goog.Uri.QueryData))
|
||||
|
||||
|
|
@ -36,6 +37,16 @@
|
|||
(.setQueryData uri (goog.Uri.QueryData/createFromMap (clj->js new-query)))
|
||||
(.toString uri)))
|
||||
|
||||
(defn
|
||||
^{:see-also ["reitit.core/match->path"]}
|
||||
match->path
|
||||
"Create routing path from given match and optional query-string map and
|
||||
optional fragment string."
|
||||
[match query-params fragment]
|
||||
(when-let [path (r/match->path match query-params)]
|
||||
(cond-> path
|
||||
(and fragment (seq fragment)) (str "#" (impl/form-encode fragment)))))
|
||||
|
||||
(defn match-by-path
|
||||
"Given routing tree and current path, return match with possibly
|
||||
coerced parameters. Return nil if no match found.
|
||||
|
|
@ -56,8 +67,8 @@
|
|||
fragment (when (.hasFragment uri)
|
||||
(.getFragment uri))
|
||||
match (assoc match
|
||||
:query-params q
|
||||
:fragment fragment)
|
||||
:query-params q
|
||||
:fragment fragment)
|
||||
;; Return uncoerced values if coercion is not enabled - so
|
||||
;; that tha parameters are always accessible from same property.
|
||||
parameters (or (coerce! match)
|
||||
|
|
|
|||
|
|
@ -52,11 +52,13 @@
|
|||
pairs separated by &, i.e. \"?a=1&a=2\", if you want to encode them
|
||||
differently, convert the collections to strings first."
|
||||
([name]
|
||||
(rfh/href @history name nil nil))
|
||||
(rfh/href @history name nil nil nil))
|
||||
([name path-params]
|
||||
(rfh/href @history name path-params nil))
|
||||
(rfh/href @history name path-params nil nil))
|
||||
([name path-params query-params]
|
||||
(rfh/href @history name path-params query-params)))
|
||||
(rfh/href @history name path-params query-params nil))
|
||||
([name path-params query-params fragment]
|
||||
(rfh/href @history name path-params query-params fragment)))
|
||||
|
||||
(defn
|
||||
^{:see-also ["reitit.frontend.history/push-state"]}
|
||||
|
|
@ -74,11 +76,13 @@
|
|||
See also:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/History/pushState"
|
||||
([name]
|
||||
(rfh/push-state @history name nil nil))
|
||||
(rfh/push-state @history name nil nil nil))
|
||||
([name path-params]
|
||||
(rfh/push-state @history name path-params nil))
|
||||
(rfh/push-state @history name path-params nil nil))
|
||||
([name path-params query-params]
|
||||
(rfh/push-state @history name path-params query-params)))
|
||||
(rfh/push-state @history name path-params query-params nil))
|
||||
([name path-params query-params fragment]
|
||||
(rfh/push-state @history name path-params query-params fragment)))
|
||||
|
||||
(defn
|
||||
^{:see-also ["reitit.frontend.history/replace-state"]}
|
||||
|
|
@ -96,11 +100,13 @@
|
|||
See also:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState"
|
||||
([name]
|
||||
(rfh/replace-state @history name nil nil))
|
||||
(rfh/replace-state @history name nil nil nil))
|
||||
([name path-params]
|
||||
(rfh/replace-state @history name path-params nil))
|
||||
(rfh/replace-state @history name path-params nil nil))
|
||||
([name path-params query-params]
|
||||
(rfh/replace-state @history name path-params query-params)))
|
||||
(rfh/replace-state @history name path-params query-params nil))
|
||||
([name path-params query-params fragment]
|
||||
(rfh/replace-state @history name path-params query-params fragment)))
|
||||
|
||||
;; This duplicates previous two, but the map parameter will be easier way to
|
||||
;; extend the functions, e.g. to work with fragment string. Toggling push vs
|
||||
|
|
@ -125,7 +131,7 @@
|
|||
https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState"
|
||||
([name]
|
||||
(rfh/navigate @history name))
|
||||
([name {:keys [path-params query-params replace] :as opts}]
|
||||
([name {:keys [path-params query-params replace fragment] :as opts}]
|
||||
(rfh/navigate @history name opts)))
|
||||
|
||||
(defn
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
(-init [this] "Create event listeners")
|
||||
(-stop [this] "Remove event listeners")
|
||||
(-on-navigate [this path] "Find a match for current routing path and call on-navigate callback")
|
||||
(-get-path [this] "Get the current routing path")
|
||||
(-get-path [this] "Get the current routing path, including query string and fragment")
|
||||
(-href [this path] "Converts given routing path to browser location"))
|
||||
|
||||
;; This version listens for both pop-state and hash-change for
|
||||
|
|
@ -92,6 +92,7 @@
|
|||
;; isContentEditable property is inherited from parents,
|
||||
;; so if the anchor is inside contenteditable div, the property will be true.
|
||||
(not (.-isContentEditable el))
|
||||
;; NOTE: Why doesn't this use frontend variant instead of core?
|
||||
(reitit/match-by-path router (.getPath uri)))))
|
||||
|
||||
(defrecord Html5History [on-navigate router listen-key click-listen-key]
|
||||
|
|
@ -132,7 +133,8 @@
|
|||
nil)
|
||||
(-get-path [this]
|
||||
(str (.. js/window -location -pathname)
|
||||
(.. js/window -location -search)))
|
||||
(.. js/window -location -search)
|
||||
(.. js/window -location -hash)))
|
||||
(-href [this path]
|
||||
path))
|
||||
|
||||
|
|
@ -193,8 +195,10 @@
|
|||
([history name path-params]
|
||||
(href history name path-params nil))
|
||||
([history name path-params query-params]
|
||||
(href history name path-params query-params nil))
|
||||
([history name path-params query-params fragment]
|
||||
(let [match (rf/match-by-name! (:router history) name path-params)]
|
||||
(-href history (reitit/match->path match query-params)))))
|
||||
(-href history (rf/match->path match query-params fragment)))))
|
||||
|
||||
(defn
|
||||
^{:see-also ["reitit.core/match->path"]}
|
||||
|
|
@ -211,12 +215,14 @@
|
|||
See also:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/History/pushState"
|
||||
([history name]
|
||||
(push-state history name nil nil))
|
||||
(push-state history name nil nil nil))
|
||||
([history name path-params]
|
||||
(push-state history name path-params nil))
|
||||
(push-state history name path-params nil nil))
|
||||
([history name path-params query-params]
|
||||
(push-state history name path-params query-params nil))
|
||||
([history name path-params query-params fragment]
|
||||
(let [match (rf/match-by-name! (:router history) name path-params)
|
||||
path (reitit/match->path match query-params)]
|
||||
path (rf/match->path match query-params fragment)]
|
||||
;; pushState and replaceState don't trigger popstate event so call on-navigate manually
|
||||
(.pushState js/window.history nil "" (-href history path))
|
||||
(-on-navigate history path))))
|
||||
|
|
@ -237,12 +243,14 @@
|
|||
See also:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState"
|
||||
([history name]
|
||||
(replace-state history name nil nil))
|
||||
(replace-state history name nil nil nil))
|
||||
([history name path-params]
|
||||
(replace-state history name path-params nil))
|
||||
(replace-state history name path-params nil nil))
|
||||
([history name path-params query-params]
|
||||
(replace-state history name path-params query-params nil))
|
||||
([history name path-params query-params fragment]
|
||||
(let [match (rf/match-by-name! (:router history) name path-params)
|
||||
path (reitit/match->path match query-params)]
|
||||
path (rf/match->path match query-params fragment)]
|
||||
(.replaceState js/window.history nil "" (-href history path))
|
||||
(-on-navigate history path))))
|
||||
|
||||
|
|
@ -265,9 +273,9 @@
|
|||
https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState"
|
||||
([history name]
|
||||
(navigate history name nil))
|
||||
([history name {:keys [path-params query-params replace] :as opts}]
|
||||
([history name {:keys [path-params query-params fragment replace] :as opts}]
|
||||
(let [match (rf/match-by-name! (:router history) name path-params)
|
||||
path (reitit/match->path match query-params)]
|
||||
path (rf/match->path match query-params fragment)]
|
||||
(if replace
|
||||
(.replaceState js/window.history nil "" (-href history path))
|
||||
(.pushState js/window.history nil "" (-href history path)))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
(defproject metosin/reitit-http "0.6.0"
|
||||
(defproject metosin/reitit-http "0.7.0-alpha5"
|
||||
:description "Reitit: HTTP routing with interceptors"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
|
|||
|
|
@ -22,11 +22,12 @@
|
|||
compile (fn [[path data] opts scope]
|
||||
(interceptor/compile-result [path data] opts scope))
|
||||
->endpoint (fn [p d m s]
|
||||
(let [compiled (compile [p d] opts s)]
|
||||
(-> compiled
|
||||
(map->Endpoint)
|
||||
(assoc :path p)
|
||||
(assoc :method m))))
|
||||
(let [d (ring/-compile-coercion d)]
|
||||
(let [compiled (compile [p d] opts s)]
|
||||
(-> compiled
|
||||
(map->Endpoint)
|
||||
(assoc :path p)
|
||||
(assoc :method m)))))
|
||||
->methods (fn [any? data]
|
||||
(reduce
|
||||
(fn [acc method]
|
||||
|
|
@ -67,6 +68,7 @@
|
|||
([data opts]
|
||||
(let [opts (merge {:coerce coerce-handler
|
||||
:compile compile-result
|
||||
:update-paths (ring/-update-paths impl/accumulate)
|
||||
::default-options-endpoint ring/default-options-endpoint} opts)]
|
||||
(when (contains? opts ::default-options-handler)
|
||||
(ex/fail! (str "Option :reitit.http/default-options-handler is deprecated."
|
||||
|
|
|
|||
|
|
@ -10,15 +10,15 @@
|
|||
[]
|
||||
{:name ::coerce-request
|
||||
:spec ::rs/parameters
|
||||
:compile (fn [{:keys [coercion parameters]} opts]
|
||||
:compile (fn [{:keys [coercion parameters request]} opts]
|
||||
(cond
|
||||
;; no coercion, skip
|
||||
(not coercion) nil
|
||||
;; just coercion, don't mount
|
||||
(not parameters) {}
|
||||
(not (or parameters request)) {}
|
||||
;; mount
|
||||
:else
|
||||
(if-let [coercers (coercion/request-coercers coercion parameters opts)]
|
||||
(if-let [coercers (coercion/request-coercers coercion parameters request opts)]
|
||||
{:enter (fn [ctx]
|
||||
(let [request (:request ctx)
|
||||
coerced (coercion/coerce-request coercers request)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
(defproject metosin/reitit-interceptors "0.6.0"
|
||||
(defproject metosin/reitit-interceptors "0.7.0-alpha5"
|
||||
:description "Reitit, common interceptors bundled"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
@ -11,4 +11,5 @@
|
|||
:inherit [:deploy-repositories :managed-dependencies]}
|
||||
:dependencies [[metosin/reitit-ring]
|
||||
[lambdaisland/deep-diff]
|
||||
[metosin/muuntaja]])
|
||||
[metosin/muuntaja]
|
||||
[metosin/spec-tools]])
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
(defproject metosin/reitit-malli "0.6.0"
|
||||
(defproject metosin/reitit-malli "0.7.0-alpha5"
|
||||
:description "Reitit: Malli coercion"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
|
|||
|
|
@ -106,92 +106,6 @@
|
|||
;; malli options
|
||||
:options nil})
|
||||
|
||||
(defn -get-apidocs-openapi
|
||||
[coercion {:keys [parameters responses content-types] :or {content-types ["application/json"]}} options]
|
||||
(let [{:keys [body request multipart]} parameters
|
||||
parameters (dissoc parameters :request :body :multipart)
|
||||
->schema-object (fn [schema opts]
|
||||
(let [current-opts (merge options opts)]
|
||||
(json-schema/transform (coercion/-compile-model coercion schema current-opts)
|
||||
current-opts)))]
|
||||
(merge
|
||||
(when (seq parameters)
|
||||
{:parameters
|
||||
(->> (for [[in schema] parameters
|
||||
:let [{:keys [properties required] :as root} (->schema-object schema {:in in :type :parameter})
|
||||
required? (partial contains? (set required))]
|
||||
[k schema] properties]
|
||||
(merge {:in (name in)
|
||||
:name k
|
||||
:required (required? k)
|
||||
:schema schema}
|
||||
(select-keys root [:description])))
|
||||
(into []))})
|
||||
(when body
|
||||
;; body uses a single schema to describe every :requestBody
|
||||
;; the schema-object transformer should be able to transform into distinct content-types
|
||||
{:requestBody {:content (into {}
|
||||
(map (fn [content-type]
|
||||
(let [schema (->schema-object body {:in :requestBody
|
||||
:type :schema
|
||||
:content-type content-type})]
|
||||
[content-type {:schema schema}])))
|
||||
content-types)}})
|
||||
|
||||
(when request
|
||||
;; request allow to different :requestBody per content-type
|
||||
{:requestBody
|
||||
{:content (merge
|
||||
(when (:body request)
|
||||
(into {}
|
||||
(map (fn [content-type]
|
||||
(let [schema (->schema-object (:body request) {:in :requestBody
|
||||
:type :schema
|
||||
:content-type content-type})]
|
||||
[content-type {:schema schema}])))
|
||||
content-types))
|
||||
(into {}
|
||||
(map (fn [[content-type requestBody]]
|
||||
(let [schema (->schema-object requestBody {:in :requestBody
|
||||
:type :schema
|
||||
:content-type content-type})]
|
||||
[content-type {:schema schema}])))
|
||||
(:content request)))}})
|
||||
(when multipart
|
||||
{:requestBody
|
||||
{:content
|
||||
{"multipart/form-data"
|
||||
{:schema
|
||||
(->schema-object multipart {:in :requestBody
|
||||
:type :schema
|
||||
:content-type "multipart/form-data"})}}}})
|
||||
(when responses
|
||||
{:responses
|
||||
(into {}
|
||||
(map (fn [[status {:keys [body content]
|
||||
:as response}]]
|
||||
(let [content (merge
|
||||
(when body
|
||||
(into {}
|
||||
(map (fn [content-type]
|
||||
(let [schema (->schema-object body {:in :responses
|
||||
:type :schema
|
||||
:content-type content-type})]
|
||||
[content-type {:schema schema}])))
|
||||
content-types))
|
||||
(when content
|
||||
(into {}
|
||||
(map (fn [[content-type schema]]
|
||||
(let [schema (->schema-object schema {:in :responses
|
||||
:type :schema
|
||||
:content-type content-type})]
|
||||
[content-type {:schema schema}])))
|
||||
content)))]
|
||||
[status (merge (select-keys response [:description])
|
||||
(when content
|
||||
{:content content}))])))
|
||||
responses)}))))
|
||||
|
||||
(defn create
|
||||
([]
|
||||
(create nil))
|
||||
|
|
@ -199,12 +113,20 @@
|
|||
(let [{:keys [transformers lite compile options error-keys encode-error] :as opts} (merge default-options opts)
|
||||
show? (fn [key] (contains? error-keys key))
|
||||
transformers (walk/prewalk #(if (satisfies? TransformationProvider %) (-transformer % opts) %) transformers)
|
||||
compile (if lite (fn [schema options] (compile (binding [l/*options* options] (l/schema schema)) options))
|
||||
compile (if lite (fn [schema options]
|
||||
(compile (binding [l/*options* options] (l/schema schema)) options))
|
||||
compile)]
|
||||
^{:type ::coercion/coercion}
|
||||
(reify coercion/Coercion
|
||||
(-get-name [_] :malli)
|
||||
(-get-options [_] opts)
|
||||
(-get-model-apidocs [this specification model options]
|
||||
(case specification
|
||||
:openapi (json-schema/transform model (merge opts options))
|
||||
(throw
|
||||
(ex-info
|
||||
(str "Can't produce Malli apidocs for " specification)
|
||||
{:type specification, :coercion :malli}))))
|
||||
(-get-apidocs [this specification {:keys [parameters responses] :as data}]
|
||||
(case specification
|
||||
:swagger (swagger/swagger-spec
|
||||
|
|
@ -225,12 +147,15 @@
|
|||
(if (:schema $)
|
||||
(update $ :schema #(coercion/-compile-model this % nil))
|
||||
$))]))})))
|
||||
:openapi (-get-apidocs-openapi this data options)
|
||||
;; :openapi handled in reitit.openapi/-get-apidocs-openapi
|
||||
(throw
|
||||
(ex-info
|
||||
(str "Can't produce Schema apidocs for " specification)
|
||||
{:type specification, :coercion :schema}))))
|
||||
(-compile-model [_ model _] (compile model options))
|
||||
(str "Can't produce Malli apidocs for " specification)
|
||||
{:type specification, :coercion :malli}))))
|
||||
(-compile-model [_ model _]
|
||||
(if (= 1 (count model))
|
||||
(compile (first model) options)
|
||||
(reduce (fn [x y] (mu/merge x y options)) (map #(compile % options) model))))
|
||||
(-open-model [_ schema] schema)
|
||||
(-encode-error [_ error]
|
||||
(cond-> error
|
||||
|
|
@ -241,8 +166,8 @@
|
|||
(seq error-keys) (select-keys error-keys)
|
||||
encode-error (encode-error)))
|
||||
(-request-coercer [_ type schema]
|
||||
(-coercer (compile schema options) type transformers :decode opts))
|
||||
(-coercer schema type transformers :decode opts))
|
||||
(-response-coercer [_ schema]
|
||||
(-coercer (compile schema options) :response transformers :encode opts))))))
|
||||
(-coercer schema :response transformers :encode opts))))))
|
||||
|
||||
(def coercion (create default-options))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
(defproject metosin/reitit-middleware "0.6.0"
|
||||
(defproject metosin/reitit-middleware "0.7.0-alpha5"
|
||||
:description "Reitit, common middleware bundled"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
(defproject metosin/reitit-openapi "0.6.0"
|
||||
(defproject fi.metosin/reitit-openapi "0.7.0-alpha5"
|
||||
:description "Reitit: OpenAPI-support"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
|
|||
|
|
@ -73,6 +73,96 @@
|
|||
(defn- openapi-path [path opts]
|
||||
(-> path (trie/normalize opts) (str/replace #"\{\*" "{")))
|
||||
|
||||
(defn -get-apidocs-openapi
|
||||
[coercion {:keys [request parameters responses content-types] :or {content-types ["application/json"]}}]
|
||||
(let [{:keys [body multipart]} parameters
|
||||
parameters (dissoc parameters :request :body :multipart)
|
||||
->content (fn [data schema]
|
||||
(merge
|
||||
{:schema schema}
|
||||
(select-keys data [:description :examples])
|
||||
(:openapi data)))
|
||||
->schema-object #(coercion/-get-model-apidocs coercion :openapi %1 %2)]
|
||||
(merge
|
||||
(when (seq parameters)
|
||||
{:parameters
|
||||
(->> (for [[in schema] parameters
|
||||
:let [{:keys [properties required]} (->schema-object schema {:in in :type :parameter})
|
||||
required? (partial contains? (set required))]
|
||||
[k schema] properties]
|
||||
(merge {:in (name in)
|
||||
:name k
|
||||
:required (required? k)
|
||||
:schema schema}
|
||||
(select-keys schema [:description])))
|
||||
(into []))})
|
||||
(when body
|
||||
;; body uses a single schema to describe every :requestBody
|
||||
;; the schema-object transformer should be able to transform into distinct content-types
|
||||
{:requestBody {:content (into {}
|
||||
(map (fn [content-type]
|
||||
(let [schema (->schema-object body {:in :requestBody
|
||||
:type :schema
|
||||
:content-type content-type})]
|
||||
[content-type {:schema schema}])))
|
||||
content-types)}})
|
||||
|
||||
(when request
|
||||
;; request allow to different :requestBody per content-type
|
||||
{:requestBody
|
||||
{:content (merge
|
||||
(select-keys request [:description])
|
||||
(when-let [{:keys [schema] :as data} (coercion/get-default request)]
|
||||
(into {}
|
||||
(map (fn [content-type]
|
||||
(let [schema (->schema-object schema {:in :requestBody
|
||||
:type :schema
|
||||
:content-type content-type})]
|
||||
[content-type (->content data schema)])))
|
||||
content-types))
|
||||
(into {}
|
||||
(map (fn [[content-type {:keys [schema] :as data}]]
|
||||
(let [schema (->schema-object schema {:in :requestBody
|
||||
:type :schema
|
||||
:content-type content-type})]
|
||||
[content-type (->content data schema)])))
|
||||
(:content request)))}})
|
||||
(when multipart
|
||||
{:requestBody
|
||||
{:content
|
||||
{"multipart/form-data"
|
||||
{:schema
|
||||
(->schema-object multipart {:in :requestBody
|
||||
:type :schema
|
||||
:content-type "multipart/form-data"})}}}})
|
||||
(when responses
|
||||
{:responses
|
||||
(into {}
|
||||
(map (fn [[status {:keys [content], :as response}]]
|
||||
(let [default (coercion/get-default-schema response)
|
||||
content (-> (merge
|
||||
(when default
|
||||
(into {}
|
||||
(map (fn [content-type]
|
||||
(let [schema (->schema-object default {:in :responses
|
||||
:type :schema
|
||||
:content-type content-type})]
|
||||
[content-type (->content nil schema)])))
|
||||
content-types))
|
||||
(when content
|
||||
(into {}
|
||||
(map (fn [[content-type {:keys [schema] :as data}]]
|
||||
(let [schema (->schema-object schema {:in :responses
|
||||
:type :schema
|
||||
:content-type content-type})]
|
||||
[content-type (->content data schema)])))
|
||||
content)))
|
||||
(dissoc :default))]
|
||||
[status (merge (select-keys response [:description])
|
||||
(when content
|
||||
{:content content}))]))
|
||||
responses))}))))
|
||||
|
||||
(defn create-openapi-handler
|
||||
"Stability: alpha
|
||||
|
||||
|
|
@ -99,7 +189,7 @@
|
|||
(apply meta-merge (keep (comp :openapi :data) middleware))
|
||||
(apply meta-merge (keep (comp :openapi :data) interceptors))
|
||||
(if coercion
|
||||
(coercion/get-apidocs coercion :openapi data))
|
||||
(-get-apidocs-openapi coercion data))
|
||||
(select-keys data [:tags :summary :description])
|
||||
(strip-top-level-keys openapi))]))
|
||||
transform-path (fn [[p _ c]]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
(defproject metosin/reitit-pedestal "0.6.0"
|
||||
(defproject metosin/reitit-pedestal "0.7.0-alpha5"
|
||||
:description "Reitit + Pedestal Integration"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
(defproject metosin/reitit-ring "0.6.0"
|
||||
(defproject metosin/reitit-ring "0.7.0-alpha5"
|
||||
:description "Reitit: Ring routing"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#?@(:clj [[ring.util.mime-type :as mime-type]
|
||||
[ring.util.response :as response]])
|
||||
[reitit.core :as r]
|
||||
[reitit.coercion :as coercion]
|
||||
[reitit.exception :as ex]
|
||||
[reitit.impl :as impl]
|
||||
[reitit.middleware :as middleware]))
|
||||
|
|
@ -28,16 +29,43 @@
|
|||
(update acc method expand opts)
|
||||
acc)) data http-methods)])
|
||||
|
||||
(defn -update-paths [f]
|
||||
(let [not-request? #(not= :request %)
|
||||
http-method? #(contains? http-methods %)]
|
||||
[;; default parameters
|
||||
[[:parameters not-request?] f]
|
||||
[[http-method? :parameters not-request?] f]
|
||||
|
||||
;; default responses
|
||||
[[:responses any? :body] f]
|
||||
[[http-method? :responses any? :body] f]
|
||||
|
||||
;; openapi3 request
|
||||
[[:request :content any? :schema] f]
|
||||
[[http-method? :request :content any? :schema] f]
|
||||
|
||||
;; openapi3 LEGACY body
|
||||
[[:request :body] f]
|
||||
[[http-method? :request :body] f]
|
||||
|
||||
;; openapi3 responses
|
||||
[[:responses any? :content any? :schema] f]
|
||||
[[http-method? :responses any? :content any? :schema] f]]))
|
||||
|
||||
(defn -compile-coercion [{:keys [coercion] :as data}]
|
||||
(cond-> data coercion (impl/path-update (-update-paths #(coercion/-compile-model coercion % nil)))))
|
||||
|
||||
(defn compile-result [[path data] {:keys [::default-options-endpoint expand] :as opts}]
|
||||
(let [[top childs] (group-keys data)
|
||||
childs (cond-> childs
|
||||
(and (not (:options childs)) (not (:handler top)) default-options-endpoint)
|
||||
(assoc :options (expand default-options-endpoint opts)))
|
||||
->endpoint (fn [p d m s]
|
||||
(-> (middleware/compile-result [p d] opts s)
|
||||
(map->Endpoint)
|
||||
(assoc :path p)
|
||||
(assoc :method m)))
|
||||
(let [d (-compile-coercion d)]
|
||||
(-> (middleware/compile-result [p d] opts s)
|
||||
(map->Endpoint)
|
||||
(assoc :path p)
|
||||
(assoc :method m))))
|
||||
->methods (fn [any? data]
|
||||
(reduce
|
||||
(fn [acc method]
|
||||
|
|
@ -97,6 +125,7 @@
|
|||
([data opts]
|
||||
(let [opts (merge {:coerce coerce-handler
|
||||
:compile compile-result
|
||||
:update-paths (-update-paths impl/accumulate)
|
||||
::default-options-endpoint default-options-endpoint}
|
||||
opts)]
|
||||
(when (contains? opts ::default-options-handler)
|
||||
|
|
|
|||
|
|
@ -24,15 +24,15 @@
|
|||
and :parameters from route data, otherwise does not mount."
|
||||
{:name ::coerce-request
|
||||
:spec ::rs/parameters
|
||||
:compile (fn [{:keys [coercion parameters]} opts]
|
||||
:compile (fn [{:keys [coercion parameters request]} opts]
|
||||
(cond
|
||||
;; no coercion, skip
|
||||
(not coercion) nil
|
||||
;; just coercion, don't mount
|
||||
(not parameters) {}
|
||||
(not (or parameters request)) {}
|
||||
;; mount
|
||||
:else
|
||||
(if-let [coercers (coercion/request-coercers coercion parameters opts)]
|
||||
(if-let [coercers (coercion/request-coercers coercion parameters request opts)]
|
||||
(fn [handler]
|
||||
(fn
|
||||
([request]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
(defproject metosin/reitit-schema "0.6.0"
|
||||
(defproject metosin/reitit-schema "0.7.0-alpha5"
|
||||
:description "Reitit: Plumatic Schema coercion"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
|
|||
|
|
@ -47,68 +47,38 @@
|
|||
(reify coercion/Coercion
|
||||
(-get-name [_] :schema)
|
||||
(-get-options [_] opts)
|
||||
(-get-apidocs [this specification {:keys [parameters responses content-types]
|
||||
:or {content-types ["application/json"]}}]
|
||||
;; TODO: this looks identical to spec, refactor when schema is done.
|
||||
(-get-model-apidocs [_ specification model options]
|
||||
(case specification
|
||||
:openapi (openapi/transform model (merge opts options))
|
||||
(throw
|
||||
(ex-info
|
||||
(str "Can't produce Schema apidocs for " specification)
|
||||
{:type specification, :coercion :schema}))))
|
||||
(-get-apidocs [_ specification {:keys [request parameters responses content-types]
|
||||
:or {content-types ["application/json"]}}]
|
||||
;; TODO: this looks identical to spec, refactor when schema is done.
|
||||
(case specification
|
||||
:swagger (swagger/swagger-spec
|
||||
(merge
|
||||
(if parameters
|
||||
{::swagger/parameters
|
||||
(into
|
||||
(empty parameters)
|
||||
(for [[k v] parameters]
|
||||
[k (coercion/-compile-model this v nil)]))})
|
||||
{::swagger/parameters parameters})
|
||||
(if responses
|
||||
{::swagger/responses
|
||||
(into
|
||||
(empty responses)
|
||||
(for [[k response] responses]
|
||||
[k (as-> response $
|
||||
(set/rename-keys $ {:body :schema})
|
||||
(if (:schema $)
|
||||
(update $ :schema #(coercion/-compile-model this % nil))
|
||||
$))]))})))
|
||||
:openapi (merge
|
||||
(when (seq (dissoc parameters :body :request :multipart))
|
||||
(openapi/openapi-spec {::openapi/parameters
|
||||
(into
|
||||
(empty parameters)
|
||||
(for [[k v] (dissoc parameters :body :request)]
|
||||
[k (coercion/-compile-model this v nil)]))}))
|
||||
(when (:body parameters)
|
||||
{:requestBody (openapi/openapi-spec
|
||||
{::openapi/content (zipmap content-types (repeat (:body parameters)))})})
|
||||
(when (:request parameters)
|
||||
{:requestBody (openapi/openapi-spec
|
||||
{::openapi/content (merge
|
||||
(when-let [default (get-in parameters [:request :body])]
|
||||
(zipmap content-types (repeat default)))
|
||||
(:content (:request parameters)))})})
|
||||
(when (:multipart parameters)
|
||||
{:requestBody
|
||||
(openapi/openapi-spec
|
||||
{::openapi/content {"multipart/form-data" (:multipart parameters)}})})
|
||||
(when responses
|
||||
{:responses
|
||||
(into
|
||||
(empty responses)
|
||||
(for [[k {:keys [body content] :as response}] responses]
|
||||
[k (merge
|
||||
(select-keys response [:description])
|
||||
(when (or body content)
|
||||
(openapi/openapi-spec
|
||||
{::openapi/content (merge
|
||||
(when body
|
||||
(zipmap content-types (repeat (coercion/-compile-model this body nil))))
|
||||
(when response
|
||||
(:content response)))})))]))}))
|
||||
|
||||
[k (-> response
|
||||
(dissoc :content)
|
||||
(set/rename-keys {:body :schema}))]))})))
|
||||
;; :openapi handled in reitit.openapi/-get-apidocs-openapi
|
||||
(throw
|
||||
(ex-info
|
||||
(str "Can't produce Schema apidocs for " specification)
|
||||
{:type specification, :coercion :schema}))))
|
||||
(-compile-model [_ model _] model)
|
||||
(-compile-model [_ model _]
|
||||
(if (= 1 (count model))
|
||||
(first model)
|
||||
(apply st/merge model)))
|
||||
(-open-model [_ schema] (st/open-schema schema))
|
||||
(-encode-error [_ error]
|
||||
(-> error
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
(defproject metosin/reitit-sieppari "0.6.0"
|
||||
(defproject metosin/reitit-sieppari "0.7.0-alpha5"
|
||||
:description "Reitit: Sieppari Interceptors"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
(defproject metosin/reitit-spec "0.6.0"
|
||||
(defproject metosin/reitit-spec "0.7.0-alpha5"
|
||||
:description "Reitit: clojure.spec coercion"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
(ns reitit.coercion.spec
|
||||
(:require [clojure.set :as set]
|
||||
[clojure.spec.alpha :as s]
|
||||
[meta-merge.core :as mm]
|
||||
[reitit.coercion :as coercion]
|
||||
[reitit.exception :as ex]
|
||||
[spec-tools.core :as st #?@(:cljs [:refer [Spec]])]
|
||||
[spec-tools.data-spec :as ds #?@(:cljs [:refer [Maybe]])]
|
||||
[spec-tools.openapi.core :as openapi]
|
||||
|
|
@ -66,7 +68,7 @@
|
|||
(st/create-spec {:spec this}))
|
||||
|
||||
nil
|
||||
(into-spec [this _]))
|
||||
(into-spec [_ _]))
|
||||
|
||||
(defn stringify-pred [pred]
|
||||
(str (if (seq? pred) (seq pred) pred)))
|
||||
|
|
@ -86,81 +88,51 @@
|
|||
(reify coercion/Coercion
|
||||
(-get-name [_] :spec)
|
||||
(-get-options [_] opts)
|
||||
(-get-apidocs [this specification {:keys [parameters responses content-types]
|
||||
:or {content-types ["application/json"]}}]
|
||||
(-get-model-apidocs [_ specification model options]
|
||||
(case specification
|
||||
:openapi (openapi/transform model (merge opts options))
|
||||
(throw
|
||||
(ex-info
|
||||
(str "Can't produce Spec apidocs for " specification)
|
||||
{:type specification, :coercion :spec}))))
|
||||
(-get-apidocs [_ specification {:keys [request parameters responses content-types]
|
||||
:or {content-types ["application/json"]}}]
|
||||
(case specification
|
||||
:swagger (swagger/swagger-spec
|
||||
(merge
|
||||
(if parameters
|
||||
{::swagger/parameters
|
||||
(into
|
||||
(empty parameters)
|
||||
(for [[k v] parameters]
|
||||
[k (coercion/-compile-model this v nil)]))})
|
||||
{::swagger/parameters parameters})
|
||||
(if responses
|
||||
{::swagger/responses
|
||||
(into
|
||||
(empty responses)
|
||||
(for [[k response] responses]
|
||||
[k (as-> response $
|
||||
(set/rename-keys $ {:body :schema})
|
||||
(if (:schema $)
|
||||
(update $ :schema #(coercion/-compile-model this % nil))
|
||||
$))]))})))
|
||||
:openapi (merge
|
||||
(when (seq (dissoc parameters :body :request :multipart))
|
||||
(openapi/openapi-spec {::openapi/parameters
|
||||
(into (empty parameters)
|
||||
(for [[k v] (dissoc parameters :body :request)]
|
||||
[k (coercion/-compile-model this v nil)]))}))
|
||||
(when (:body parameters)
|
||||
{:requestBody (openapi/openapi-spec
|
||||
{::openapi/content (zipmap content-types (repeat (coercion/-compile-model this (:body parameters) nil)))})})
|
||||
(when (:request parameters)
|
||||
{:requestBody (openapi/openapi-spec
|
||||
{::openapi/content (merge
|
||||
(when-let [default (get-in parameters [:request :body])]
|
||||
(zipmap content-types (repeat (coercion/-compile-model this default nil))))
|
||||
(into {}
|
||||
(for [[format model] (:content (:request parameters))]
|
||||
[format (coercion/-compile-model this model nil)])))})})
|
||||
(when (:multipart parameters)
|
||||
{:requestBody
|
||||
(openapi/openapi-spec
|
||||
{::openapi/content
|
||||
{"multipart/form-data"
|
||||
(coercion/-compile-model this (:multipart parameters) nil)}})})
|
||||
(when responses
|
||||
{:responses
|
||||
(into
|
||||
(empty responses)
|
||||
(for [[k {:keys [body content] :as response}] responses]
|
||||
[k (merge
|
||||
(select-keys response [:description])
|
||||
(when (or body content)
|
||||
(openapi/openapi-spec
|
||||
{::openapi/content (merge
|
||||
(when body
|
||||
(zipmap content-types (repeat (coercion/-compile-model this (:body response) nil))))
|
||||
(when response
|
||||
(into {}
|
||||
(for [[format model] (:content response)]
|
||||
[format (coercion/-compile-model this model nil)]))))})))]))}))
|
||||
(dissoc $ :content)
|
||||
(set/rename-keys $ {:body :schema}))]))})))
|
||||
;; :openapi handled in reitit.openapi/-get-apidocs-openapi
|
||||
(throw
|
||||
(ex-info
|
||||
(str "Can't produce Spec apidocs for " specification)
|
||||
{:specification specification, :coercion :spec}))))
|
||||
(-compile-model [_ model name]
|
||||
(into-spec model name))
|
||||
(into-spec
|
||||
(cond
|
||||
;; we are safe!
|
||||
(= (count model) 1) (first model)
|
||||
;; here be dragons, best effort
|
||||
(every? map? model) (apply mm/meta-merge model)
|
||||
;; fail fast
|
||||
:else (ex/fail! ::model-error {:message "Can't merge nested clojure specs", :spec model}))
|
||||
name))
|
||||
(-open-model [_ spec] spec)
|
||||
(-encode-error [_ error]
|
||||
(let [problems (-> error :problems ::s/problems)]
|
||||
(-> error
|
||||
(update :spec (comp str s/form))
|
||||
(assoc :problems (mapv #(update % :pred stringify-pred) problems)))))
|
||||
(-request-coercer [this type spec]
|
||||
(let [spec (coercion/-compile-model this spec nil)
|
||||
{:keys [formats default]} (transformers type)]
|
||||
(-request-coercer [_ type spec]
|
||||
(let [{:keys [formats default]} (transformers type)]
|
||||
(fn [value format]
|
||||
(if-let [transformer (or (get formats format) default)]
|
||||
(let [coerced (st/coerce spec value transformer)]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
(defproject metosin/reitit-swagger-ui "0.6.0"
|
||||
(defproject metosin/reitit-swagger-ui "0.7.0-alpha5"
|
||||
:description "Reitit: Swagger-ui support"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
(defproject metosin/reitit-swagger "0.6.0"
|
||||
(defproject metosin/reitit-swagger "0.7.0-alpha5"
|
||||
:description "Reitit: Swagger-support"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
|
|||
|
|
@ -69,6 +69,30 @@
|
|||
(defn- swagger-path [path opts]
|
||||
(-> path (trie/normalize opts) (str/replace #"\{\*" "{")))
|
||||
|
||||
(defn -warn-unsupported-coercions [{:keys [request responses] :as _data}]
|
||||
(when request
|
||||
(println "WARNING [reitit.coercion]: swagger apidocs don't support :request coercion"))
|
||||
(when (some :content (vals responses))
|
||||
(println "WARNING [reitit.coercion]: swagger apidocs don't support :responses :content coercion")))
|
||||
|
||||
(defn -get-swagger-apidocs [coercion data]
|
||||
(let [swagger-parameter {:query :query
|
||||
:body :body
|
||||
:form :formData
|
||||
:header :header
|
||||
:path :path
|
||||
:multipart :formData}]
|
||||
(-warn-unsupported-coercions data)
|
||||
(->> (update
|
||||
data
|
||||
:parameters
|
||||
(fn [parameters]
|
||||
(->> parameters
|
||||
(map (fn [[k v]] [(swagger-parameter k) v]))
|
||||
(filter first)
|
||||
(into {}))))
|
||||
(coercion/-get-apidocs coercion :swagger))))
|
||||
|
||||
(defn create-swagger-handler
|
||||
"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."
|
||||
|
|
@ -95,7 +119,7 @@
|
|||
(apply meta-merge (keep (comp :swagger :data) middleware))
|
||||
(apply meta-merge (keep (comp :swagger :data) interceptors))
|
||||
(if coercion
|
||||
(coercion/get-apidocs coercion :swagger data))
|
||||
(-get-swagger-apidocs coercion data))
|
||||
(select-keys data [:tags :summary :description :operationId])
|
||||
(strip-top-level-keys swagger))]))
|
||||
transform-path (fn [[p _ c]]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
(defproject metosin/reitit "0.6.0"
|
||||
(defproject metosin/reitit "0.7.0-alpha5"
|
||||
:description "Snappy data-driven router for Clojure(Script)"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
[metosin/reitit-http]
|
||||
[metosin/reitit-interceptors]
|
||||
[metosin/reitit-swagger]
|
||||
[metosin/reitit-openapi]
|
||||
[fi.metosin/reitit-openapi]
|
||||
[metosin/reitit-swagger-ui]
|
||||
[metosin/reitit-frontend]
|
||||
[metosin/reitit-sieppari]
|
||||
|
|
|
|||
56
project.clj
56
project.clj
|
|
@ -1,4 +1,4 @@
|
|||
(defproject metosin/reitit-parent "0.6.0"
|
||||
(defproject metosin/reitit-parent "0.7.0-alpha5"
|
||||
:description "Snappy data-driven router for Clojure(Script)"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
@ -15,44 +15,47 @@
|
|||
:url "https://github.com/metosin/reitit"}
|
||||
;; TODO: need to verify that the code actually worked with Java1.8, see #242
|
||||
:javac-options ["-Xlint:unchecked" "-target" "1.8" "-source" "1.8"]
|
||||
:managed-dependencies [[metosin/reitit "0.6.0"]
|
||||
[metosin/reitit-core "0.6.0"]
|
||||
[metosin/reitit-dev "0.6.0"]
|
||||
[metosin/reitit-spec "0.6.0"]
|
||||
[metosin/reitit-malli "0.6.0"]
|
||||
[metosin/reitit-schema "0.6.0"]
|
||||
[metosin/reitit-ring "0.6.0"]
|
||||
[metosin/reitit-middleware "0.6.0"]
|
||||
[metosin/reitit-http "0.6.0"]
|
||||
[metosin/reitit-interceptors "0.6.0"]
|
||||
[metosin/reitit-swagger "0.6.0"]
|
||||
[metosin/reitit-openapi "0.6.0"]
|
||||
[metosin/reitit-swagger-ui "0.6.0"]
|
||||
[metosin/reitit-frontend "0.6.0"]
|
||||
[metosin/reitit-sieppari "0.6.0"]
|
||||
[metosin/reitit-pedestal "0.6.0"]
|
||||
:managed-dependencies [[metosin/reitit "0.7.0-alpha5"]
|
||||
[metosin/reitit-core "0.7.0-alpha5"]
|
||||
[metosin/reitit-dev "0.7.0-alpha5"]
|
||||
[metosin/reitit-spec "0.7.0-alpha5"]
|
||||
[metosin/reitit-malli "0.7.0-alpha5"]
|
||||
[metosin/reitit-schema "0.7.0-alpha5"]
|
||||
[metosin/reitit-ring "0.7.0-alpha5"]
|
||||
[metosin/reitit-middleware "0.7.0-alpha5"]
|
||||
[metosin/reitit-http "0.7.0-alpha5"]
|
||||
[metosin/reitit-interceptors "0.7.0-alpha5"]
|
||||
[metosin/reitit-swagger "0.7.0-alpha5"]
|
||||
[fi.metosin/reitit-openapi "0.7.0-alpha5"]
|
||||
[metosin/reitit-swagger-ui "0.7.0-alpha5"]
|
||||
[metosin/reitit-frontend "0.7.0-alpha5"]
|
||||
[metosin/reitit-sieppari "0.7.0-alpha5"]
|
||||
[metosin/reitit-pedestal "0.7.0-alpha5"]
|
||||
[metosin/ring-swagger-ui "4.18.1"]
|
||||
[metosin/spec-tools "0.10.5"]
|
||||
[metosin/schema-tools "0.13.0"]
|
||||
[metosin/schema-tools "0.13.1"]
|
||||
[metosin/muuntaja "0.6.8"]
|
||||
[metosin/jsonista "0.3.7"]
|
||||
[metosin/sieppari "0.0.0-alpha13"]
|
||||
[metosin/malli "0.11.0"]
|
||||
|
||||
;; https://clojureverse.org/t/depending-on-the-right-versions-of-jackson-libraries/5111
|
||||
[com.fasterxml.jackson.core/jackson-core "2.14.2"]
|
||||
[com.fasterxml.jackson.core/jackson-databind "2.14.2"]
|
||||
[com.fasterxml.jackson.core/jackson-core "2.15.1"]
|
||||
[com.fasterxml.jackson.core/jackson-databind "2.15.1"]
|
||||
|
||||
[meta-merge "1.0.0"]
|
||||
[fipp "0.6.26" :exclusions [org.clojure/core.rrb-vector]]
|
||||
;; Deep-diff uses this version, override olders versiom from fipp.
|
||||
[org.clojure/core.rrb-vector "0.0.14"]
|
||||
[expound "0.9.0"]
|
||||
[lambdaisland/deep-diff "0.0-47"]
|
||||
[com.bhauman/spell-spec "0.1.2"]
|
||||
[mvxcvi/arrangement "2.1.0"]
|
||||
[ring/ring-core "1.10.0"]
|
||||
|
||||
[io.pedestal/pedestal.service "0.5.10"]]
|
||||
|
||||
:plugins [[jonase/eastwood "1.3.0"]
|
||||
:plugins [[jonase/eastwood "1.4.0"]
|
||||
;[lein-virgil "0.1.7"]
|
||||
[lein-ancient "1.0.0-RC3"]
|
||||
[lein-doo "0.1.11"]
|
||||
|
|
@ -87,7 +90,7 @@
|
|||
[org.clojure/clojurescript "1.10.773"]
|
||||
|
||||
;; modules dependencies
|
||||
[metosin/schema-tools "0.13.0"]
|
||||
[metosin/schema-tools "0.13.1"]
|
||||
[metosin/spec-tools "0.10.5"]
|
||||
[metosin/muuntaja "0.6.8"]
|
||||
[metosin/sieppari "0.0.0-alpha13"]
|
||||
|
|
@ -105,6 +108,7 @@
|
|||
[ikitommi/immutant-web "3.0.0-alpha1"]
|
||||
[metosin/ring-http-response "0.9.3"]
|
||||
[metosin/ring-swagger-ui "4.18.1"]
|
||||
[org.clojure/tools.analyzer "1.1.1"]
|
||||
|
||||
[criterium "0.4.6"]
|
||||
[org.clojure/test.check "1.1.1"]
|
||||
|
|
@ -115,8 +119,8 @@
|
|||
[io.pedestal/pedestal.service "0.5.10"]
|
||||
|
||||
[org.clojure/core.async "1.6.673"]
|
||||
[manifold "0.4.0"]
|
||||
[funcool/promesa "10.0.594"]
|
||||
[manifold "0.4.1"]
|
||||
[funcool/promesa "11.0.664"]
|
||||
|
||||
[com.clojure-goes-fast/clj-async-profiler "1.0.3"]
|
||||
[ring-cors "0.1.13"]
|
||||
|
|
@ -134,8 +138,8 @@
|
|||
[io.pedestal/pedestal.jetty "0.5.10"]
|
||||
[calfpath "0.8.1"]
|
||||
[org.clojure/core.async "1.6.673"]
|
||||
[manifold "0.4.0"]
|
||||
[funcool/promesa "10.0.594"]
|
||||
[manifold "0.4.1"]
|
||||
[funcool/promesa "11.0.664"]
|
||||
[metosin/sieppari]
|
||||
[yada "1.2.16"]
|
||||
[aleph "0.6.1"]
|
||||
|
|
|
|||
14
scripts/cljdoc-check.sh
Executable file
14
scripts/cljdoc-check.sh
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Need pom and jar for analyze local.
|
||||
# Need repo version installed to the local m2 for up-to-date dependencies between modules.
|
||||
# Install will run jar and pom tasks already.
|
||||
./scripts/lein-modules install
|
||||
|
||||
for i in modules/*; do
|
||||
cd $i
|
||||
clojure -J-Dclojure.main.report=stderr -Tcljdoc-analyzer analyze-local
|
||||
cd ../..
|
||||
done
|
||||
|
|
@ -2,7 +2,11 @@
|
|||
|
||||
ext="sedbak$$"
|
||||
|
||||
# metosin/reitit-*
|
||||
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" '{}' \;
|
||||
# fi.metosin/reitit-*
|
||||
find . -name project.clj -exec sed -i.$ext "s/\[fi.metosin\/reitit\(.*\) \".*\"\]/[fi.metosin\/reitit\1 \"$1\"\]/g" '{}' \;
|
||||
find . -name project.clj -exec sed -i.$ext "s/defproject fi.metosin\/reitit\(.*\) \".*\"/defproject fi.metosin\/reitit\1 \"$1\"/g" '{}' \;
|
||||
sed -i.$ext "s/\[metosin\/reitit\(.*\) \".*\"\]/[metosin\/reitit\1 \"$1\"\]/g" README.md doc/**/*.md
|
||||
find . -name "*.$ext" -exec rm '{}' \;
|
||||
|
|
|
|||
|
|
@ -7,34 +7,55 @@
|
|||
[reitit.coercion.spec]
|
||||
[reitit.core :as r]
|
||||
[schema.core :as s]
|
||||
[clojure.spec.alpha :as cs]
|
||||
[spec-tools.data-spec :as ds])
|
||||
#?(:clj
|
||||
(:import (clojure.lang ExceptionInfo))))
|
||||
|
||||
(cs/def ::number int?)
|
||||
(cs/def ::keyword keyword?)
|
||||
(cs/def ::int int?)
|
||||
(cs/def ::ints (cs/coll-of int? :kind vector))
|
||||
(cs/def ::map (cs/map-of int? int?))
|
||||
|
||||
(deftest coercion-test
|
||||
(let [r (r/router
|
||||
[["/schema" {:coercion reitit.coercion.schema/coercion}
|
||||
["/:number/:keyword" {:parameters {:path {:number s/Int
|
||||
:keyword s/Keyword}
|
||||
:query (s/maybe {:int s/Int, :ints [s/Int], :map {s/Int s/Int}})}}]]
|
||||
["/:number" {:parameters {:path {:number s/Int}}}
|
||||
["/:keyword" {:parameters {:path {:keyword s/Keyword}
|
||||
:query (s/maybe {:int s/Int, :ints [s/Int], :map {s/Int s/Int}})}}]]]
|
||||
|
||||
["/malli" {:coercion reitit.coercion.malli/coercion}
|
||||
["/:number/:keyword" {:parameters {:path [:map [:number int?] [:keyword keyword?]]
|
||||
:query [:maybe [:map [:int int?]
|
||||
[:ints [:vector int?]]
|
||||
[:map [:map-of int? int?]]]]}}]]
|
||||
["/:number" {:parameters {:path [:map [:number int?]]}}
|
||||
["/:keyword" {:parameters {:path [:map [:keyword keyword?]]
|
||||
:query [:maybe [:map [:int int?]
|
||||
[:ints [:vector int?]]
|
||||
[:map [:map-of int? int?]]]]}}]]]
|
||||
|
||||
["/malli-lite" {:coercion reitit.coercion.malli/coercion}
|
||||
["/:number/:keyword" {:parameters {:path {:number int?
|
||||
:keyword keyword?}
|
||||
:query (l/maybe {:int int?
|
||||
:ints (l/vector int?)
|
||||
:map (l/map-of int? int?)})}}]]
|
||||
["/spec" {:coercion reitit.coercion.spec/coercion}
|
||||
["/:number/:keyword" {:parameters {:path {:number int?
|
||||
:keyword keyword?}
|
||||
:query (ds/maybe {:int int?, :ints [int?], :map {int? int?}})}}]]
|
||||
["/:number" {:parameters {:path {:number int?}}}
|
||||
["/:keyword" {:parameters {:path {:keyword keyword?}
|
||||
:query (l/maybe {:int int?
|
||||
:ints (l/vector int?)
|
||||
:map (l/map-of int? int?)})}}]]]
|
||||
|
||||
#_["/spec" {:coercion reitit.coercion.spec/coercion}
|
||||
["/:number" {:parameters {:path (cs/keys :req-un [::number])}}
|
||||
["/:keyword" {:parameters {:path (cs/keys :req-un [::keyword])
|
||||
:query (cs/nilable (cs/keys :req-un [::int ::ints ::map]))}}]]]
|
||||
|
||||
["/spec-shallow" {:coercion reitit.coercion.spec/coercion}
|
||||
["/:number/:keyword" {:parameters {:path (cs/keys :req-un [::number ::keyword])
|
||||
:query (cs/nilable (cs/keys :req-un [::int ::ints ::map]))}}]]
|
||||
|
||||
["/data-spec" {:coercion reitit.coercion.spec/coercion}
|
||||
["/:number" {:parameters {:path {:number int?}}}
|
||||
["/:keyword" {:parameters {:path {:keyword keyword?}
|
||||
:query (ds/maybe {:int int?, :ints [int?], :map {int? int?}})}}]]]
|
||||
|
||||
["/none"
|
||||
["/:number/:keyword" {:parameters {:path {:number int?
|
||||
:keyword keyword?}}}]]]
|
||||
["/:number" {:parameters {:path {:number int?}}}
|
||||
["/:keyword" {:parameters {:path {:keyword keyword?}}}]]]]
|
||||
{:compile coercion/compile-request-coercers})]
|
||||
|
||||
(testing "schema-coercion"
|
||||
|
|
@ -73,19 +94,42 @@
|
|||
(let [m (r/match-by-path r "/malli-lite/kikka/abba")]
|
||||
(is (thrown? ExceptionInfo (coercion/coerce! m))))))
|
||||
|
||||
;; TODO: :map-of fails with string-keys
|
||||
(testing "spec-coercion"
|
||||
(testing "succeeds"
|
||||
#_(testing "spec-coercion"
|
||||
(testing "fails"
|
||||
(let [m (r/match-by-path r "/spec/1/abba")]
|
||||
(is (= {:path {:keyword :abba, :number 1}, :query nil}
|
||||
(coercion/coerce! m))))
|
||||
(let [m (r/match-by-path r "/schema/1/abba")]
|
||||
(is (= {:path {:keyword :abba, :number 1}, :query {:int 10, :ints [1, 2, 3], :map {1 1, #_#_2 2}}}
|
||||
(coercion/coerce! (assoc m :query-params {"int" "10", "ints" ["1" "2" "3"], "map" {:1 "1"}, #_#_"2" "2"}))))))
|
||||
(is (thrown? ExceptionInfo (coercion/coerce! m))))
|
||||
(let [m (r/match-by-path r "/spec/1/abba")]
|
||||
(is (thrown? ExceptionInfo (coercion/coerce! m)))))
|
||||
(testing "throws with invalid input"
|
||||
(let [m (r/match-by-path r "/spec/kikka/abba")]
|
||||
(is (thrown? ExceptionInfo (coercion/coerce! m))))))
|
||||
|
||||
(testing "spec-coercion (shallow)"
|
||||
(testing "succeeds"
|
||||
(let [m (r/match-by-path r "/spec-shallow/1/abba")]
|
||||
(def MATCH m)
|
||||
(is (= {:path {:keyword :abba, :number 1}, :query nil}
|
||||
(coercion/coerce! m))))
|
||||
(let [m (r/match-by-path r "/spec-shallow/1/abba")]
|
||||
(is (= {:path {:keyword :abba, :number 1}, :query {:int 10, :ints [1, 2, 3], :map {1 1, #_#_2 2}}}
|
||||
(coercion/coerce! (assoc m :query-params {"int" "10", "ints" ["1" "2" "3"], "map" {:1 "1"}, #_#_"2" "2"}))))))
|
||||
(testing "throws with invalid input"
|
||||
(let [m (r/match-by-path r "/spec-shallow/kikka/abba")]
|
||||
(is (thrown? ExceptionInfo (coercion/coerce! m))))))
|
||||
|
||||
;; TODO: :map-of fails with string-keys
|
||||
#_(testing "data-spec-coercion"
|
||||
(testing "succeeds"
|
||||
(let [m (r/match-by-path r "/data-spec/1/abba")]
|
||||
(is (= {:path {:keyword :abba, :number 1}, :query nil}
|
||||
(coercion/coerce! m))))
|
||||
(let [m (r/match-by-path r "/data-spec/1/abba")]
|
||||
(is (= {:path {:keyword :abba, :number 1}, :query {:int 10, :ints [1, 2, 3], :map {1 1, #_#_2 2}}}
|
||||
(coercion/coerce! (assoc m :query-params {"int" "10", "ints" ["1" "2" "3"], "map" {:1 "1"}, #_#_"2" "2"}))))))
|
||||
(testing "throws with invalid input"
|
||||
(let [m (r/match-by-path r "/data-spec/kikka/abba")]
|
||||
(is (thrown? ExceptionInfo (coercion/coerce! m))))))
|
||||
|
||||
(testing "no coercion defined"
|
||||
(testing "doesn't coerce"
|
||||
(let [m (r/match-by-path r "/none/1/abba")]
|
||||
|
|
|
|||
|
|
@ -267,14 +267,14 @@
|
|||
(let [pong (constantly "ok")
|
||||
routes ["/api" {:mw [:api]}
|
||||
["/ping" :kikka]
|
||||
["/user/:id" {:parameters {:id "String"}}
|
||||
["/:sub-id" {:parameters {:sub-id "String"}}]]
|
||||
["/user/:id" {:parameters {:path {:id :string}}}
|
||||
["/:sub-id" {:parameters {:path {:sub-id :string}}}]]
|
||||
["/pong" pong]
|
||||
["/admin" {:mw [:admin] :roles #{:admin}}
|
||||
["/user" {:roles ^:replace #{:user}}]
|
||||
["/db" {:mw [:db]}]]]
|
||||
expected [["/api/ping" {:mw [:api], :name :kikka}]
|
||||
["/api/user/:id/:sub-id" {:mw [:api], :parameters {:id "String", :sub-id "String"}}]
|
||||
["/api/user/:id/:sub-id" {:mw [:api], :parameters {:path [{:id :string} {:sub-id :string}]}}]
|
||||
["/api/pong" {:mw [:api], :handler pong}]
|
||||
["/api/admin/user" {:mw [:api :admin], :roles #{:user}}]
|
||||
["/api/admin/db" {:mw [:api :admin :db], :roles #{:admin}}]]
|
||||
|
|
@ -282,7 +282,7 @@
|
|||
(is (= expected (impl/resolve-routes routes (r/default-router-options))))
|
||||
(is (= (r/map->Match
|
||||
{:template "/api/user/:id/:sub-id"
|
||||
:data {:mw [:api], :parameters {:id "String", :sub-id "String"}}
|
||||
:data {:mw [:api], :parameters {:path [{:id :string} {:sub-id :string}]}}
|
||||
:path "/api/user/1/2"
|
||||
:path-params {:id "1", :sub-id "2"}})
|
||||
(r/match-by-path router "/api/user/1/2"))))))
|
||||
|
|
|
|||
|
|
@ -171,3 +171,39 @@
|
|||
:path-parts ["https://google.com"]
|
||||
:path-params #{}}
|
||||
(impl/parse "https://google.com" nil))))
|
||||
|
||||
(deftest path-update-test
|
||||
(is (= {:get {:responses {200 {:body [[:map [:total :int]]]}}
|
||||
:parameters {:query [[:map [:x :int]]]}},
|
||||
:parameters {:query [[:map [:x :int]]]}
|
||||
:post {}}
|
||||
(impl/path-update
|
||||
{:parameters {:query [:map [:x :int]]}
|
||||
:get {:parameters {:query [:map [:x :int]]}
|
||||
:responses {200 {:body [:map [:total :int]]}}}
|
||||
:post {}}
|
||||
[[[:parameters any?] vector]
|
||||
[[any? :parameters any?] vector]
|
||||
[[:responses any? :body] vector]
|
||||
[[any? :responses any? :body] vector]]))))
|
||||
|
||||
(deftest meta-merge-test
|
||||
(is (= {:get {:responses {200 {:body [[:map [:total :int]]
|
||||
[:map [:total :int]]]}},
|
||||
:parameters {:query [[:map [:x :int]]
|
||||
[:map [:y :int]]]}},
|
||||
:parameters {:query [[:map [:x :int]]
|
||||
[:map [:y :int]]]},
|
||||
:post {:parameters {:query [[:map [:y :int]]]}}}
|
||||
(impl/meta-merge
|
||||
{:parameters {:query [:map [:x :int]]}
|
||||
:get {:parameters {:query [:map [:x :int]]}
|
||||
:responses {200 {:body [:map [:total :int]]}}}}
|
||||
{:parameters {:query [:map [:y :int]]}
|
||||
:get {:parameters {:query [:map [:y :int]]}
|
||||
:responses {200 {:body [:map [:total :int]]}}}
|
||||
:post {:parameters {:query [:map [:y :int]]}}}
|
||||
{:update-paths [[[:parameters any?] vector]
|
||||
[[any? :parameters any?] vector]
|
||||
[[:responses any? :body] vector]
|
||||
[[any? :responses any? :body] vector]]}))))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -31,101 +32,110 @@
|
|||
|
||||
(def app
|
||||
(ring/ring-handler
|
||||
(ring/router
|
||||
["/api"
|
||||
{:openapi {:id ::math}}
|
||||
(ring/router
|
||||
["/api"
|
||||
{:openapi {:id ::math}}
|
||||
|
||||
["/openapi.json"
|
||||
{:get {:no-doc true
|
||||
:openapi {:info {:title "my-api"
|
||||
:version "0.0.1"}}
|
||||
:handler (openapi/create-openapi-handler)}}]
|
||||
["/openapi.json"
|
||||
{:get {:no-doc true
|
||||
:openapi {:info {:title "my-api"
|
||||
:version "0.0.1"}}
|
||||
:handler (openapi/create-openapi-handler)}}]
|
||||
|
||||
["/spec" {:coercion spec/coercion}
|
||||
["/plus/:z"
|
||||
{:get {:summary "plus"
|
||||
:tags [:plus :spec]
|
||||
:parameters {:query {:x int?, :y int?}
|
||||
:path {:z int?}}
|
||||
:openapi {:operationId "spec-plus"
|
||||
:deprecated true
|
||||
:responses {400 {:description "kosh"
|
||||
:content {"application/json" {:schema {:type "string"}}}}}}
|
||||
:responses {200 {:description "success"
|
||||
:body {:total int?}}
|
||||
500 {:description "fail"}}
|
||||
:handler (fn [{{{:keys [x y]} :query
|
||||
{:keys [z]} :path} :parameters}]
|
||||
{:status 200, :body {:total (+ x y z)}})}
|
||||
:post {:summary "plus with body"
|
||||
:parameters {:body (ds/maybe [int?])
|
||||
:path {:z int?}}
|
||||
:openapi {:responses {400 {:content {"application/json" {:schema {:type "string"}}}
|
||||
:description "kosh"}}}
|
||||
:responses {200 {:description "success"
|
||||
:body {:total int?}}
|
||||
500 {:description "fail"}}
|
||||
:handler (fn [{{{:keys [z]} :path
|
||||
xs :body} :parameters}]
|
||||
{:status 200, :body {:total (+ (reduce + xs) z)}})}}]]
|
||||
["/spec" {:coercion spec/coercion}
|
||||
["/plus/:z"
|
||||
{:get {:summary "plus"
|
||||
:tags [:plus :spec]
|
||||
:parameters {:query {:x int?, :y int?}
|
||||
:path {:z int?}}
|
||||
:openapi {:operationId "spec-plus"
|
||||
:deprecated true
|
||||
:responses {400 {:description "kosh"
|
||||
:content {"application/json" {:schema {:type "string"}}}}}}
|
||||
:responses {200 {:description "success"
|
||||
:body {:total int?}}
|
||||
500 {:description "fail"}}
|
||||
:handler (fn [{{{:keys [x y]} :query
|
||||
{:keys [z]} :path} :parameters}]
|
||||
{:status 200, :body {:total (+ x y z)}})}
|
||||
:post {:summary "plus with body"
|
||||
:parameters {:body (ds/maybe [int?])
|
||||
:path {:z int?}}
|
||||
:openapi {:responses {400 {:content {"application/json" {:schema {:type "string"}}}
|
||||
:description "kosh"}}}
|
||||
:responses {200 {:description "success"
|
||||
:body {:total int?}}
|
||||
500 {:description "fail"}
|
||||
504 {:description "default"
|
||||
:content {:default {:schema {:error string?}}}
|
||||
:body {:masked string?}}}
|
||||
:handler (fn [{{{:keys [z]} :path
|
||||
xs :body} :parameters}]
|
||||
{:status 200, :body {:total (+ (reduce + xs) z)}})}}]]
|
||||
|
||||
["/malli" {:coercion malli/coercion}
|
||||
["/plus/*z"
|
||||
{:get {:summary "plus"
|
||||
:tags [:plus :malli]
|
||||
:parameters {:query [:map [:x int?] [:y int?]]
|
||||
:path [:map [:z int?]]}
|
||||
:openapi {:responses {400 {:description "kosh"
|
||||
:content {"application/json" {:schema {:type "string"}}}}}}
|
||||
:responses {200 {:description "success"
|
||||
:body [:map [:total int?]]}
|
||||
500 {:description "fail"}}
|
||||
:handler (fn [{{{:keys [x y]} :query
|
||||
{:keys [z]} :path} :parameters}]
|
||||
{:status 200, :body {:total (+ x y z)}})}
|
||||
:post {:summary "plus with body"
|
||||
:parameters {:body [:maybe [:vector int?]]
|
||||
:path [:map [:z int?]]}
|
||||
:openapi {:responses {400 {:description "kosh"
|
||||
:content {"application/json" {:schema {:type "string"}}}}}}
|
||||
:responses {200 {:description "success"
|
||||
:body [:map [:total int?]]}
|
||||
500 {:description "fail"}}
|
||||
:handler (fn [{{{:keys [z]} :path
|
||||
xs :body} :parameters}]
|
||||
{:status 200, :body {:total (+ (reduce + xs) z)}})}}]]
|
||||
["/malli" {:coercion malli/coercion}
|
||||
["/plus/*z"
|
||||
{:get {:summary "plus"
|
||||
:tags [:plus :malli]
|
||||
:parameters {:query [:map [:x int?] [:y int?]]
|
||||
:path [:map [:z int?]]}
|
||||
:openapi {:responses {400 {:description "kosh"
|
||||
:content {"application/json" {:schema {:type "string"}}}}}}
|
||||
:responses {200 {:description "success"
|
||||
:body [:map [:total int?]]}
|
||||
500 {:description "fail"}}
|
||||
:handler (fn [{{{:keys [x y]} :query
|
||||
{:keys [z]} :path} :parameters}]
|
||||
{:status 200, :body {:total (+ x y z)}})}
|
||||
:post {:summary "plus with body"
|
||||
:parameters {:body [:maybe [:vector int?]]
|
||||
:path [:map [:z int?]]}
|
||||
:openapi {:responses {400 {:description "kosh"
|
||||
:content {"application/json" {:schema {:type "string"}}}}}}
|
||||
:responses {200 {:description "success"
|
||||
:body [:map [:total int?]]}
|
||||
500 {:description "fail"}
|
||||
504 {:description "default"
|
||||
:content {:default {:schema {:error string?}}}
|
||||
:body {:masked string?}}}
|
||||
:handler (fn [{{{:keys [z]} :path
|
||||
xs :body} :parameters}]
|
||||
{:status 200, :body {:total (+ (reduce + xs) z)}})}}]]
|
||||
|
||||
["/schema" {:coercion schema/coercion}
|
||||
["/plus/*z"
|
||||
{:get {:summary "plus"
|
||||
:tags [:plus :schema]
|
||||
:parameters {:query {:x s/Int, :y s/Int}
|
||||
:path {:z s/Int}}
|
||||
:openapi {:responses {400 {:content {"application/json" {:schema {:type "string"}}}
|
||||
:description "kosh"}}}
|
||||
:responses {200 {:description "success"
|
||||
:body {:total s/Int}}
|
||||
500 {:description "fail"}}
|
||||
:handler (fn [{{{:keys [x y]} :query
|
||||
{:keys [z]} :path} :parameters}]
|
||||
{:status 200, :body {:total (+ x y z)}})}
|
||||
:post {:summary "plus with body"
|
||||
:parameters {:body (s/maybe [s/Int])
|
||||
:path {:z s/Int}}
|
||||
:openapi {:responses {400 {:content {"application/json" {:schema {:type "string"}}}
|
||||
:description "kosh"}}}
|
||||
:responses {200 {:description "success"
|
||||
:body {:total s/Int}}
|
||||
500 {:description "fail"}}
|
||||
:handler (fn [{{{:keys [z]} :path
|
||||
xs :body} :parameters}]
|
||||
{:status 200, :body {:total (+ (reduce + xs) z)}})}}]]]
|
||||
["/schema" {:coercion schema/coercion}
|
||||
["/plus/*z"
|
||||
{:get {:summary "plus"
|
||||
:tags [:plus :schema]
|
||||
:parameters {:query {:x s/Int, :y s/Int}
|
||||
:path {:z s/Int}}
|
||||
:openapi {:responses {400 {:content {"application/json" {:schema {:type "string"}}}
|
||||
:description "kosh"}}}
|
||||
:responses {200 {:description "success"
|
||||
:body {:total s/Int}}
|
||||
500 {:description "fail"}}
|
||||
:handler (fn [{{{:keys [x y]} :query
|
||||
{:keys [z]} :path} :parameters}]
|
||||
{:status 200, :body {:total (+ x y z)}})}
|
||||
:post {:summary "plus with body"
|
||||
:parameters {:body (s/maybe [s/Int])
|
||||
:path {:z s/Int}}
|
||||
:openapi {:responses {400 {:content {"application/json" {:schema {:type "string"}}}
|
||||
:description "kosh"}}}
|
||||
:responses {200 {:description "success"
|
||||
:body {:total s/Int}}
|
||||
500 {:description "fail"}
|
||||
504 {:description "default"
|
||||
:content {:default {:schema {:error s/Str}}}
|
||||
:body {:masked s/Str}}}
|
||||
:handler (fn [{{{:keys [z]} :path
|
||||
xs :body} :parameters}]
|
||||
{:status 200, :body {:total (+ (reduce + xs) z)}})}}]]]
|
||||
|
||||
{:validate reitit.ring.spec/validate
|
||||
:data {:middleware [openapi/openapi-feature
|
||||
rrc/coerce-exceptions-middleware
|
||||
rrc/coerce-request-middleware
|
||||
rrc/coerce-response-middleware]}})))
|
||||
{:validate reitit.ring.spec/validate
|
||||
:data {:middleware [openapi/openapi-feature
|
||||
rrc/coerce-exceptions-middleware
|
||||
rrc/coerce-request-middleware
|
||||
rrc/coerce-response-middleware]}})))
|
||||
|
||||
(deftest openapi-test
|
||||
(testing "endpoints work"
|
||||
|
|
@ -147,19 +157,16 @@
|
|||
:version "0.0.1"}
|
||||
:paths {"/api/spec/plus/{z}" {:get {:parameters [{:in "query"
|
||||
:name "x"
|
||||
:description ""
|
||||
:required true
|
||||
:schema {:type "integer"
|
||||
:format "int64"}}
|
||||
{:in "query"
|
||||
:name "y"
|
||||
:description ""
|
||||
:required true
|
||||
:schema {:type "integer"
|
||||
:format "int64"}}
|
||||
{:in "path"
|
||||
:name "z"
|
||||
:description ""
|
||||
:required true
|
||||
:schema {:type "integer"
|
||||
:format "int64"}}]
|
||||
|
|
@ -178,7 +185,6 @@
|
|||
:post {:parameters [{:in "path"
|
||||
:name "z"
|
||||
:required true
|
||||
:description ""
|
||||
:schema {:type "integer"
|
||||
:format "int64"}}]
|
||||
:requestBody {:content {"application/json" {:schema {:oneOf [{:items {:type "integer"
|
||||
|
|
@ -192,7 +198,11 @@
|
|||
:type "object"}}}}
|
||||
400 {:content {"application/json" {:schema {:type "string"}}}
|
||||
:description "kosh"}
|
||||
500 {:description "fail"}}
|
||||
500 {:description "fail"}
|
||||
504 {:description "default"
|
||||
:content {"application/json" {:schema {:properties {"error" {:type "string"}}
|
||||
:required ["error"]
|
||||
:type "object"}}}}}
|
||||
:summary "plus with body"}}
|
||||
"/api/malli/plus/{z}" {:get {:parameters [{:in "query"
|
||||
:name :x
|
||||
|
|
@ -230,23 +240,25 @@
|
|||
:type "object"}}}}
|
||||
400 {:description "kosh"
|
||||
:content {"application/json" {:schema {:type "string"}}}}
|
||||
500 {:description "fail"}}
|
||||
500 {:description "fail"}
|
||||
504 {:description "default"
|
||||
:content {"application/json" {:schema {:additionalProperties false
|
||||
:properties {:error {:type "string"}}
|
||||
:required [:error]
|
||||
:type "object"}}}}}
|
||||
:summary "plus with body"}}
|
||||
"/api/schema/plus/{z}" {:get {:parameters [{:description ""
|
||||
:in "query"
|
||||
"/api/schema/plus/{z}" {:get {:parameters [{:in "query"
|
||||
:name "x"
|
||||
:required true
|
||||
:schema {:format "int32"
|
||||
:type "integer"}}
|
||||
{:description ""
|
||||
:in "query"
|
||||
{:in "query"
|
||||
:name "y"
|
||||
:required true
|
||||
:schema {:type "integer"
|
||||
:format "int32"}}
|
||||
{:in "path"
|
||||
:name "z"
|
||||
:description ""
|
||||
:required true
|
||||
:schema {:type "integer"
|
||||
:format "int32"}}]
|
||||
|
|
@ -263,7 +275,6 @@
|
|||
:summary "plus"}
|
||||
:post {:parameters [{:in "path"
|
||||
:name "z"
|
||||
:description ""
|
||||
:required true
|
||||
:schema {:type "integer"
|
||||
:format "int32"}}]
|
||||
|
|
@ -279,10 +290,15 @@
|
|||
:type "object"}}}}
|
||||
400 {:description "kosh"
|
||||
:content {"application/json" {:schema {:type "string"}}}}
|
||||
500 {:description "fail"}}
|
||||
500 {:description "fail"}
|
||||
504 {:description "default"
|
||||
:content {"application/json" {:schema {:additionalProperties false
|
||||
:properties {"error" {:type "string"}}
|
||||
:required ["error"]
|
||||
:type "object"}}}}}
|
||||
:summary "plus with body"}}}}]
|
||||
(is (= expected spec))
|
||||
(is (nil? (validate spec))))))
|
||||
(is (= nil (validate spec))))))
|
||||
|
||||
(defn spec-paths [app uri]
|
||||
(-> {:request-method :get, :uri uri} app :body :paths keys))
|
||||
|
|
@ -293,21 +309,21 @@
|
|||
{:get {:no-doc true
|
||||
:handler (openapi/create-openapi-handler)}}]
|
||||
app (ring/ring-handler
|
||||
(ring/router
|
||||
[["/common" {:openapi {:id #{::one ::two}}}
|
||||
ping-route]
|
||||
(ring/router
|
||||
[["/common" {:openapi {:id #{::one ::two}}}
|
||||
ping-route]
|
||||
|
||||
["/one" {:openapi {:id ::one}}
|
||||
ping-route
|
||||
spec-route]
|
||||
["/one" {:openapi {:id ::one}}
|
||||
ping-route
|
||||
spec-route]
|
||||
|
||||
["/two" {:openapi {:id ::two}}
|
||||
ping-route
|
||||
spec-route
|
||||
["/deep" {:openapi {:id ::one}}
|
||||
ping-route]]
|
||||
["/one-two" {:openapi {:id #{::one ::two}}}
|
||||
spec-route]]))]
|
||||
["/two" {:openapi {:id ::two}}
|
||||
ping-route
|
||||
spec-route
|
||||
["/deep" {:openapi {:id ::one}}
|
||||
ping-route]]
|
||||
["/one-two" {:openapi {:id #{::one ::two}}}
|
||||
spec-route]]))]
|
||||
(is (= ["/common/ping" "/one/ping" "/two/deep/ping"]
|
||||
(spec-paths app "/one/openapi.json")))
|
||||
(is (= ["/common/ping" "/two/ping"]
|
||||
|
|
@ -317,9 +333,9 @@
|
|||
|
||||
(deftest openapi-ui-config-test
|
||||
(let [app (swagger-ui/create-swagger-ui-handler
|
||||
{:path "/"
|
||||
:url "/openapi.json"
|
||||
:config {:jsonEditor true}})]
|
||||
{:path "/"
|
||||
:url "/openapi.json"
|
||||
:config {:jsonEditor true}})]
|
||||
(is (= 302 (:status (app {:request-method :get, :uri "/"}))))
|
||||
(is (= 200 (:status (app {:request-method :get, :uri "/index.html"}))))
|
||||
(is (= {:jsonEditor true, :url "/openapi.json"}
|
||||
|
|
@ -328,12 +344,12 @@
|
|||
|
||||
(deftest without-openapi-id-test
|
||||
(let [app (ring/ring-handler
|
||||
(ring/router
|
||||
[["/ping"
|
||||
{:get (constantly "ping")}]
|
||||
["/openapi.json"
|
||||
{:get {:no-doc true
|
||||
:handler (openapi/create-openapi-handler)}}]]))]
|
||||
(ring/router
|
||||
[["/ping"
|
||||
{:get (constantly "ping")}]
|
||||
["/openapi.json"
|
||||
{:get {:no-doc true
|
||||
:handler (openapi/create-openapi-handler)}}]]))]
|
||||
(is (= ["/ping"] (spec-paths app "/openapi.json")))
|
||||
(is (= #{::openapi/default}
|
||||
(-> {:request-method :get :uri "/openapi.json"}
|
||||
|
|
@ -341,14 +357,14 @@
|
|||
|
||||
(deftest with-options-endpoint-test
|
||||
(let [app (ring/ring-handler
|
||||
(ring/router
|
||||
[["/ping"
|
||||
{:options (constantly "options")}]
|
||||
["/pong"
|
||||
(constantly "options")]
|
||||
["/openapi.json"
|
||||
{:get {:no-doc true
|
||||
:handler (openapi/create-openapi-handler)}}]]))]
|
||||
(ring/router
|
||||
[["/ping"
|
||||
{:options (constantly "options")}]
|
||||
["/pong"
|
||||
(constantly "options")]
|
||||
["/openapi.json"
|
||||
{:get {:no-doc true
|
||||
:handler (openapi/create-openapi-handler)}}]]))]
|
||||
(is (= ["/ping" "/pong"] (spec-paths app "/openapi.json")))
|
||||
(is (= #{::openapi/default}
|
||||
(-> {:request-method :get :uri "/openapi.json"}
|
||||
|
|
@ -364,10 +380,12 @@
|
|||
|
||||
(deftest all-parameter-types-test
|
||||
(doseq [[coercion ->schema]
|
||||
[[#'malli/coercion (fn [nom] [:map [nom :string]])]
|
||||
[#'schema/coercion (fn [nom] {nom s/Str})]
|
||||
[#'spec/coercion (fn [nom] {nom string?})]]]
|
||||
(testing coercion
|
||||
[[#'malli/coercion (fn [nom] [:map [nom [:string {:description (str "description " nom)}]]])]
|
||||
[#'schema/coercion (fn [nom] {nom (schema-tools.core/schema s/Str
|
||||
{:description (str "description " nom)})})]
|
||||
[#'spec/coercion (fn [nom] {nom (st/spec {:spec string?
|
||||
:description (str "description " nom)})})]]]
|
||||
(testing (str coercion)
|
||||
(let [app (ring/ring-handler
|
||||
(ring/router
|
||||
[["/parameters"
|
||||
|
|
@ -394,22 +412,26 @@
|
|||
(is (match? [{:in "query"
|
||||
:name "q"
|
||||
:required true
|
||||
:description "description :q"
|
||||
:schema {:type "string"}}
|
||||
{:in "header"
|
||||
:name "h"
|
||||
:required true
|
||||
:description "description :h"
|
||||
:schema {:type "string"}}
|
||||
{:in "cookie"
|
||||
:name "c"
|
||||
:required true
|
||||
:description "description :c"
|
||||
:schema {:type "string"}}
|
||||
{:in "path"
|
||||
:name "p"
|
||||
:required true
|
||||
:description "description :p"
|
||||
:schema {:type "string"}}]
|
||||
(-> spec
|
||||
(get-in [:paths "/parameters" :post :parameters])
|
||||
normalize))))
|
||||
(-> spec
|
||||
(get-in [:paths "/parameters" :post :parameters])
|
||||
normalize))))
|
||||
(testing "body parameter"
|
||||
(is (match? (merge {:type "object"
|
||||
:properties {:b {:type "string"}}
|
||||
|
|
@ -432,22 +454,98 @@
|
|||
(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 (str coercion)
|
||||
(let [app (ring/ring-handler
|
||||
(ring/router
|
||||
[["/examples"
|
||||
{:post {:decription "examples"
|
||||
:coercion @coercion
|
||||
:request {:body (->schema :b)}
|
||||
:parameters {:query (->schema :q)}
|
||||
: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)
|
||||
: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"}}
|
||||
:examples {:named-example {:description "a named example"
|
||||
:value {:b "named"}}}}
|
||||
(-> 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"}}
|
||||
:examples {:response-example {:value {:ok "response"}}}}
|
||||
(-> 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
|
||||
reitit.ring.malli/bytes-part
|
||||
:string]
|
||||
[#'schema/coercion
|
||||
(schema-tools.core/schema {:filename s/Str
|
||||
:content-type s/Str
|
||||
:bytes s/Num}
|
||||
{:openapi {:type "string"
|
||||
:format "binary"}})
|
||||
s/Str]
|
||||
[#'spec/coercion
|
||||
reitit.http.interceptors.multipart/bytes-part
|
||||
string?]]]
|
||||
(testing coercion
|
||||
(doseq [[coercion file-schema string-schema] [[#'malli/coercion
|
||||
reitit.ring.malli/bytes-part
|
||||
:string]
|
||||
[#'schema/coercion
|
||||
(schema-tools.core/schema {:filename s/Str
|
||||
:content-type s/Str
|
||||
:bytes s/Num}
|
||||
{:openapi {:type "string"
|
||||
:format "binary"}})
|
||||
s/Str]
|
||||
[#'spec/coercion
|
||||
reitit.http.interceptors.multipart/bytes-part
|
||||
string?]]]
|
||||
(testing (str coercion)
|
||||
(let [app (ring/ring-handler
|
||||
(ring/router
|
||||
[["/upload"
|
||||
|
|
@ -481,21 +579,20 @@
|
|||
(is (nil? (validate spec))))))))
|
||||
|
||||
(deftest per-content-type-test
|
||||
(doseq [[coercion ->schema]
|
||||
[[#'malli/coercion (fn [nom] [:map [nom :string]])]
|
||||
[#'schema/coercion (fn [nom] {nom s/Str})]
|
||||
[#'spec/coercion (fn [nom] {nom string?})]]]
|
||||
(testing coercion
|
||||
(doseq [[coercion ->schema] [[malli/coercion (fn [nom] [:map [nom :string]])]
|
||||
[schema/coercion (fn [nom] {nom s/Str})]
|
||||
[spec/coercion (fn [nom] {nom string?})]]]
|
||||
(testing (str coercion)
|
||||
(let [app (ring/ring-handler
|
||||
(ring/router
|
||||
[["/parameters"
|
||||
{:post {:description "parameters"
|
||||
:coercion @coercion
|
||||
:parameters {:request {:content {"application/json" (->schema :b)
|
||||
"application/edn" (->schema :c)}}}
|
||||
:coercion coercion
|
||||
:request {:content {"application/json" {:schema (->schema :b)}
|
||||
"application/edn" {:schema (->schema :c)}}}
|
||||
:responses {200 {:description "success"
|
||||
:content {"application/json" (->schema :ok)
|
||||
"application/edn" (->schema :edn)}}}
|
||||
:content {"application/json" {:schema (->schema :ok)}
|
||||
"application/edn" {:schema (->schema :edn)}}}}
|
||||
:handler (fn [req]
|
||||
{:status 200
|
||||
:body (-> req :parameters :request)})}}]
|
||||
|
|
@ -510,41 +607,42 @@
|
|||
spec (-> {:request-method :get
|
||||
:uri "/openapi.json"}
|
||||
app
|
||||
:body)]
|
||||
:body)
|
||||
spec-coercion (= coercion spec/coercion)]
|
||||
(testing "body parameter"
|
||||
(is (match? (merge {:type "object"
|
||||
:properties {:b {:type "string"}}
|
||||
:required ["b"]}
|
||||
(when-not (#{#'spec/coercion} coercion)
|
||||
{:additionalProperties false}))
|
||||
(-> spec
|
||||
(get-in [:paths "/parameters" :post :requestBody :content "application/json" :schema])
|
||||
normalize)))
|
||||
(is (match? (merge {:type "object"
|
||||
:properties {:c {:type "string"}}
|
||||
:required ["c"]}
|
||||
(when-not (#{#'spec/coercion} coercion)
|
||||
{:additionalProperties false}))
|
||||
(-> spec
|
||||
(get-in [:paths "/parameters" :post :requestBody :content "application/edn" :schema])
|
||||
normalize))))
|
||||
(is (= (merge {:type "object"
|
||||
:properties {:b {:type "string"}}
|
||||
:required ["b"]}
|
||||
(when-not spec-coercion
|
||||
{:additionalProperties false}))
|
||||
(-> spec
|
||||
(get-in [:paths "/parameters" :post :requestBody :content "application/json" :schema])
|
||||
normalize)))
|
||||
(is (= (merge {:type "object"
|
||||
:properties {:c {:type "string"}}
|
||||
:required ["c"]}
|
||||
(when-not spec-coercion
|
||||
{:additionalProperties false}))
|
||||
(-> spec
|
||||
(get-in [:paths "/parameters" :post :requestBody :content "application/edn" :schema])
|
||||
normalize))))
|
||||
(testing "body response"
|
||||
(is (match? (merge {:type "object"
|
||||
:properties {:ok {:type "string"}}
|
||||
:required ["ok"]}
|
||||
(when-not (#{#'spec/coercion} coercion)
|
||||
{:additionalProperties false}))
|
||||
(-> spec
|
||||
(get-in [:paths "/parameters" :post :responses 200 :content "application/json" :schema])
|
||||
normalize)))
|
||||
(is (match? (merge {:type "object"
|
||||
:properties {:edn {:type "string"}}
|
||||
:required ["edn"]}
|
||||
(when-not (#{#'spec/coercion} coercion)
|
||||
{:additionalProperties false}))
|
||||
(-> spec
|
||||
(get-in [:paths "/parameters" :post :responses 200 :content "application/edn" :schema])
|
||||
normalize))))
|
||||
(is (= (merge {:type "object"
|
||||
:properties {:ok {:type "string"}}
|
||||
:required ["ok"]}
|
||||
(when-not spec-coercion
|
||||
{:additionalProperties false}))
|
||||
(-> spec
|
||||
(get-in [:paths "/parameters" :post :responses 200 :content "application/json" :schema])
|
||||
normalize)))
|
||||
(is (= (merge {:type "object"
|
||||
:properties {:edn {:type "string"}}
|
||||
:required ["edn"]}
|
||||
(when-not spec-coercion
|
||||
{:additionalProperties false}))
|
||||
(-> spec
|
||||
(get-in [:paths "/parameters" :post :responses 200 :content "application/edn" :schema])
|
||||
normalize))))
|
||||
(testing "validation"
|
||||
(let [query {:request-method :post
|
||||
:uri "/parameters"
|
||||
|
|
@ -569,23 +667,22 @@
|
|||
(is (nil? (validate spec))))))))
|
||||
|
||||
(deftest default-content-type-test
|
||||
(doseq [[coercion ->schema]
|
||||
[[#'malli/coercion (fn [nom] [:map [nom :string]])]
|
||||
[#'schema/coercion (fn [nom] {nom s/Str})]
|
||||
[#'spec/coercion (fn [nom] {nom string?})]]]
|
||||
(testing coercion
|
||||
(doseq [[coercion ->schema] [[malli/coercion (fn [nom] [:map [nom :string]])]
|
||||
[schema/coercion (fn [nom] {nom s/Str})]
|
||||
[spec/coercion (fn [nom] {nom string?})]]]
|
||||
(testing (str coercion)
|
||||
(doseq [content-type ["application/json" "application/edn"]]
|
||||
(testing (str "default content type " content-type)
|
||||
(let [app (ring/ring-handler
|
||||
(ring/router
|
||||
[["/parameters"
|
||||
{:post {:description "parameters"
|
||||
:coercion @coercion
|
||||
:coercion coercion
|
||||
:content-types [content-type] ;; TODO should this be under :openapi ?
|
||||
:parameters {:request {:content {"application/transit" (->schema :transit)}
|
||||
:body (->schema :default)}}
|
||||
:request {:content {"application/transit" {:schema (->schema :transit)}}
|
||||
:body (->schema :default)}
|
||||
:responses {200 {:description "success"
|
||||
:content {"application/transit" (->schema :transit)}
|
||||
:content {"application/transit" {:schema (->schema :transit)}}
|
||||
:body (->schema :default)}}
|
||||
:handler (fn [req]
|
||||
{:status 200
|
||||
|
|
@ -623,16 +720,15 @@
|
|||
[["/parameters"
|
||||
{:post {:description "parameters"
|
||||
:coercion malli/coercion
|
||||
:parameters {:request
|
||||
{:body
|
||||
[:schema
|
||||
{:registry {"friend" [:map
|
||||
[:age int?]
|
||||
[:pet [:ref "pet"]]]
|
||||
"pet" [:map
|
||||
[:name :string]
|
||||
[:friends [:vector [:ref "friend"]]]]}}
|
||||
"friend"]}}
|
||||
:request {:body
|
||||
[:schema
|
||||
{:registry {"friend" [:map
|
||||
[:age int?]
|
||||
[:pet [:ref "pet"]]]
|
||||
"pet" [:map
|
||||
[:name :string]
|
||||
[:friends [:vector [:ref "friend"]]]]}}
|
||||
"friend"]}
|
||||
:handler (fn [req]
|
||||
{:status 200
|
||||
:body (-> req :parameters :request)})}}]
|
||||
|
|
@ -670,3 +766,53 @@
|
|||
spec))
|
||||
(testing "spec is valid"
|
||||
(is (nil? (validate spec))))))
|
||||
|
||||
(deftest openapi-malli-tests
|
||||
(let [app (ring/ring-handler
|
||||
(ring/router
|
||||
[["/openapi.json"
|
||||
{:get {:no-doc true
|
||||
:handler (openapi/create-openapi-handler)}}]
|
||||
|
||||
["/malli" {:coercion malli/coercion}
|
||||
["/plus" {:post {:summary "plus with body"
|
||||
:request {:description "body description"
|
||||
:content {"application/json" {:schema {:x int?, :y int?}
|
||||
:examples {"1+1" {:x 1, :y 1}
|
||||
"1+2" {:x 1, :y 2}}
|
||||
:openapi {:example {:x 2, :y 2}}}}}
|
||||
:responses {200 {:description "success"
|
||||
:content {"application/json" {:schema {:total int?}
|
||||
:examples {"2" {:total 2}
|
||||
"3" {:total 3}}
|
||||
:openapi {:example {:total 4}}}}}}
|
||||
:handler (fn [request]
|
||||
(let [{:keys [x y]} (-> request :parameters :body)]
|
||||
{:status 200, :body {:total (+ x y)}}))}}]]]
|
||||
|
||||
{:validate reitit.ring.spec/validate
|
||||
:data {:middleware [openapi/openapi-feature
|
||||
rrc/coerce-exceptions-middleware
|
||||
rrc/coerce-request-middleware
|
||||
rrc/coerce-response-middleware]}}))]
|
||||
(is (= {"/malli/plus" {:post {:requestBody {:content {:description "body description",
|
||||
"application/json" {:schema {:type "object",
|
||||
:properties {:x {:type "integer"},
|
||||
:y {:type "integer"}},
|
||||
:required [:x :y],
|
||||
:additionalProperties false},
|
||||
:examples {"1+1" {:x 1, :y 1}, "1+2" {:x 1, :y 2}},
|
||||
:example {:x 2, :y 2}}}},
|
||||
:responses {200 {:description "success",
|
||||
:content {"application/json" {:schema {:type "object",
|
||||
:properties {:total {:type "integer"}},
|
||||
:required [:total],
|
||||
:additionalProperties false},
|
||||
:examples {"2" {:total 2}, "3" {:total 3}},
|
||||
:example {:total 4}}}}},
|
||||
:summary "plus with body"}}})
|
||||
(-> {:request-method :get
|
||||
:uri "/openapi.json"}
|
||||
(app)
|
||||
:body
|
||||
:paths))))
|
||||
|
|
|
|||
|
|
@ -234,14 +234,12 @@
|
|||
([] {})
|
||||
([left] left)
|
||||
([left right]
|
||||
(if (and (map? left) (map? right)
|
||||
(contains? left :parameters)
|
||||
(contains? right :parameters))
|
||||
(-> (merge-with custom-meta-merge-checking-parameters left right)
|
||||
(assoc :parameters (merge-with mu/merge
|
||||
(:parameters left)
|
||||
(:parameters right))))
|
||||
(meta-merge left right)))
|
||||
(let [pleft (-> left :parameters :path)
|
||||
pright (-> right :parameters :path)]
|
||||
(if (and (map? left) (map? right) pleft pright)
|
||||
(-> (merge-with custom-meta-merge-checking-parameters left right)
|
||||
(assoc-in [:parameters :path] (reduce mu/merge (concat pleft pright))))
|
||||
(meta-merge left right))))
|
||||
([left right & more]
|
||||
(reduce custom-meta-merge-checking-parameters left (cons right more))))
|
||||
|
||||
|
|
@ -586,75 +584,96 @@
|
|||
|
||||
(deftest per-content-type-test
|
||||
(doseq [[coercion json-request edn-request default-request json-response edn-response default-response]
|
||||
[[#'malli/coercion
|
||||
[[malli/coercion
|
||||
[:map [:request [:enum :json]] [:response any?]]
|
||||
[:map [:request [:enum :edn]] [:response any?]]
|
||||
[:map [:request [:enum :default]] [:response any?]]
|
||||
[:map [:request any?] [:response [:enum :json]]]
|
||||
[:map [:request any?] [:response [:enum :edn]]]
|
||||
[:map [:request any?] [:response [:enum :default]]]]
|
||||
[#'schema/coercion
|
||||
[schema/coercion
|
||||
{:request (s/eq :json) :response s/Any}
|
||||
{:request (s/eq :edn) :response s/Any}
|
||||
{:request (s/eq :default) :response s/Any}
|
||||
{:request s/Any :response (s/eq :json)}
|
||||
{:request s/Any :response (s/eq :edn)}
|
||||
{:request s/Any :response (s/eq :default)}]
|
||||
[#'spec/coercion
|
||||
[spec/coercion
|
||||
{:request (clojure.spec.alpha/spec #{:json}) :response any?}
|
||||
{:request (clojure.spec.alpha/spec #{:edn}) :response any?}
|
||||
{:request (clojure.spec.alpha/spec #{:default}) :response any?}
|
||||
{:request any? :response (clojure.spec.alpha/spec #{:json})}
|
||||
{:request any? :response (clojure.spec.alpha/spec #{:end})}
|
||||
{:request any? :response (clojure.spec.alpha/spec #{:default})}]]]
|
||||
(testing coercion
|
||||
(let [app (ring/ring-handler
|
||||
(ring/router
|
||||
[["/foo" {:post {:parameters {:request {:content {"application/json" json-request
|
||||
"application/edn" edn-request}
|
||||
:body default-request}}
|
||||
:responses {200 {:content {"application/json" json-response
|
||||
"application/edn" edn-response}
|
||||
:body default-response}}
|
||||
:handler (fn [req]
|
||||
{:status 200
|
||||
:body (-> req :parameters :request)})}}]]
|
||||
{:validate reitit.ring.spec/validate
|
||||
:data {:middleware [rrc/coerce-request-middleware
|
||||
rrc/coerce-response-middleware]
|
||||
:coercion @coercion}}))
|
||||
call (fn [request]
|
||||
(try
|
||||
(app request)
|
||||
(catch ExceptionInfo e
|
||||
(select-keys (ex-data e) [:type :in]))))
|
||||
request (fn [request-format response-format body]
|
||||
{:request-method :post
|
||||
:uri "/foo"
|
||||
:muuntaja/request {:format request-format}
|
||||
:muuntaja/response {:format response-format}
|
||||
:body-params body})]
|
||||
(testing "succesful call"
|
||||
(is (= {:status 200 :body {:request :json, :response :json}}
|
||||
(call (request "application/json" "application/json" {:request :json :response :json}))))
|
||||
(is (= {:status 200 :body {:request :edn, :response :json}}
|
||||
(call (request "application/edn" "application/json" {:request :edn :response :json}))))
|
||||
(is (= {:status 200 :body {:request :default, :response :default}}
|
||||
(call (request "application/transit" "application/transit" {:request :default :response :default})))))
|
||||
(testing "request validation fails"
|
||||
(is (= {:type :reitit.coercion/request-coercion :in [:request :body-params]}
|
||||
(call (request "application/edn" "application/json" {:request :json :response :json}))))
|
||||
(is (= {:type :reitit.coercion/request-coercion :in [:request :body-params]}
|
||||
(call (request "application/json" "application/json" {:request :edn :response :json}))))
|
||||
(is (= {:type :reitit.coercion/request-coercion :in [:request :body-params]}
|
||||
(call (request "application/transit" "application/json" {:request :edn :response :json})))))
|
||||
(testing "response validation fails"
|
||||
(is (= {:type :reitit.coercion/response-coercion :in [:response :body]}
|
||||
(call (request "application/json" "application/json" {:request :json :response :edn}))))
|
||||
(is (= {:type :reitit.coercion/response-coercion :in [:response :body]}
|
||||
(call (request "application/json" "application/edn" {:request :json :response :json}))))
|
||||
(is (= {:type :reitit.coercion/response-coercion :in [:response :body]}
|
||||
(call (request "application/json" "application/transit" {:request :json :response :json})))))))))
|
||||
(testing (str coercion)
|
||||
(doseq [{:keys [name app]}
|
||||
[{:name "using top-level :body"
|
||||
:app (ring/ring-handler
|
||||
(ring/router
|
||||
["/foo" {:post {:request {:content {"application/json" {:schema json-request}
|
||||
"application/edn" {:schema edn-request}}
|
||||
:body default-request}
|
||||
:responses {200 {:content {"application/json" {:schema json-response}
|
||||
"application/edn" {:schema edn-response}}
|
||||
:body default-response}}
|
||||
:handler (fn [req]
|
||||
{:status 200
|
||||
:body (-> req :parameters :request)})}}]
|
||||
{:validate reitit.ring.spec/validate
|
||||
:data {:middleware [rrc/coerce-request-middleware
|
||||
rrc/coerce-response-middleware]
|
||||
:coercion coercion}}))}
|
||||
{:name "using :default content"
|
||||
:app (ring/ring-handler
|
||||
(ring/router
|
||||
["/foo" {:post {:request {:content {"application/json" {:schema json-request}
|
||||
"application/edn" {:schema edn-request}
|
||||
:default {:schema default-request}}
|
||||
:body json-request} ;; not applied as :default exists
|
||||
:responses {200 {:content {"application/json" {:schema json-response}
|
||||
"application/edn" {:schema edn-response}
|
||||
:default {:schema default-response}}
|
||||
:body json-response}} ;; not applied as :default exists
|
||||
:handler (fn [req]
|
||||
{:status 200
|
||||
:body (-> req :parameters :request)})}}]
|
||||
{:validate reitit.ring.spec/validate
|
||||
:data {:middleware [rrc/coerce-request-middleware
|
||||
rrc/coerce-response-middleware]
|
||||
:coercion coercion}}))}]]
|
||||
(testing name
|
||||
(let [call (fn [request]
|
||||
(try
|
||||
(app request)
|
||||
(catch ExceptionInfo e
|
||||
(select-keys (ex-data e) [:type :in]))))
|
||||
request (fn [request-format response-format body]
|
||||
{:request-method :post
|
||||
:uri "/foo"
|
||||
:muuntaja/request {:format request-format}
|
||||
:muuntaja/response {:format response-format}
|
||||
:body-params body})]
|
||||
(testing "succesful call"
|
||||
(is (= {:status 200 :body {:request :json, :response :json}}
|
||||
(call (request "application/json" "application/json" {:request :json :response :json}))))
|
||||
(is (= {:status 200 :body {:request :edn, :response :json}}
|
||||
(call (request "application/edn" "application/json" {:request :edn :response :json}))))
|
||||
(is (= {:status 200 :body {:request :default, :response :default}}
|
||||
(call (request "application/transit" "application/transit" {:request :default :response :default})))))
|
||||
(testing "request validation fails"
|
||||
(is (= {:type :reitit.coercion/request-coercion :in [:request :body-params]}
|
||||
(call (request "application/edn" "application/json" {:request :json :response :json}))))
|
||||
(is (= {:type :reitit.coercion/request-coercion :in [:request :body-params]}
|
||||
(call (request "application/json" "application/json" {:request :edn :response :json}))))
|
||||
(is (= {:type :reitit.coercion/request-coercion :in [:request :body-params]}
|
||||
(call (request "application/transit" "application/json" {:request :edn :response :json})))))
|
||||
(testing "response validation fails"
|
||||
(is (= {:type :reitit.coercion/response-coercion :in [:response :body]}
|
||||
(call (request "application/json" "application/json" {:request :json :response :edn}))))
|
||||
(is (= {:type :reitit.coercion/response-coercion :in [:response :body]}
|
||||
(call (request "application/json" "application/edn" {:request :json :response :json}))))
|
||||
(is (= {:type :reitit.coercion/response-coercion :in [:response :body]}
|
||||
(call (request "application/json" "application/transit" {:request :json :response :json})))))))))))
|
||||
|
||||
|
||||
#?(:clj
|
||||
|
|
|
|||
|
|
@ -138,7 +138,6 @@
|
|||
rrc/coerce-request-middleware
|
||||
rrc/coerce-response-middleware]}})))
|
||||
|
||||
(require '[fipp.edn])
|
||||
(deftest swagger-test
|
||||
(testing "endpoints work"
|
||||
(testing "spec"
|
||||
|
|
@ -451,7 +450,7 @@
|
|||
(ring/router
|
||||
[["/parameters"
|
||||
{:post {:coercion spec/coercion
|
||||
:parameters {:request {:content {"application/json" {:x string?}}}}
|
||||
:request {:content {"application/json" {:x string?}}}
|
||||
:handler identity}}]
|
||||
["/swagger.json"
|
||||
{:get {:no-doc true
|
||||
|
|
@ -479,7 +478,7 @@
|
|||
[#'spec/coercion
|
||||
reitit.http.interceptors.multipart/bytes-part
|
||||
string?]]]
|
||||
(testing coercion
|
||||
(testing (str coercion)
|
||||
(let [app (ring/ring-handler
|
||||
(ring/router
|
||||
[["/upload"
|
||||
|
|
|
|||
|
|
@ -282,3 +282,16 @@
|
|||
(testing "Need to coerce current values manually"
|
||||
(is (= "foo?foo=2"
|
||||
(rf/set-query-params "foo?foo=1" (fn [q] (update q :foo #(inc (js/parseInt %)))))))))
|
||||
|
||||
(deftest match->path-test
|
||||
(is (= "foo"
|
||||
(rf/match->path {:path "foo"} nil nil)
|
||||
(rf/match->path {:path "foo"} {} "")))
|
||||
(is (= "foo?a=1&b=&c=foo+bar"
|
||||
;; NOTE: This encoding differs from set-query
|
||||
(rf/match->path {:path "foo"} {:a "1" :b "" :c "foo bar"} nil)))
|
||||
(is (= "foo#aaa"
|
||||
(rf/match->path {:path "foo"} nil "aaa")))
|
||||
(testing "Fragment encoding"
|
||||
(is (= "foo#foo+bar+%25"
|
||||
(rf/match->path {:path "foo"} nil "foo bar %")))))
|
||||
|
|
|
|||
|
|
@ -29,10 +29,10 @@
|
|||
1 (do (is (some? (:popstate-listener history)))
|
||||
(is (= "/" url)
|
||||
"start at root")
|
||||
(rfe/push-state ::foo))
|
||||
(rfe/push-state ::foo nil {:a 1} "foo bar"))
|
||||
;; 0. /
|
||||
;; 1. /foo
|
||||
2 (do (is (= "/foo" url)
|
||||
;; 1. /foo?a=1#foo+bar
|
||||
2 (do (is (= "/foo?a=1#foo+bar" url)
|
||||
"push-state")
|
||||
(.back js/window.history))
|
||||
;; 0. /
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@
|
|||
(rfh/href history ::bar {:id 5})))
|
||||
(is (= "#/bar/5?q=x"
|
||||
(rfh/href history ::bar {:id 5} {:q "x"})))
|
||||
(is (= "#/bar/5?q=x#foo"
|
||||
(rfh/href history ::bar {:id 5} {:q "x"} "foo")))
|
||||
(let [{:keys [value messages]} (capture-console
|
||||
(fn []
|
||||
(rfh/href history ::asd)))]
|
||||
|
|
|
|||
Loading…
Reference in a new issue