mirror of
https://github.com/metosin/reitit.git
synced 2025-12-17 00:11:11 +00:00
Compare commits
112 commits
0.8.0-alph
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3a152a44e | ||
|
|
c0bc789863 | ||
|
|
78aba57d2d | ||
|
|
451b286f1d | ||
|
|
eb06404f1e | ||
|
|
af7313bd9b | ||
|
|
ea58100fec | ||
|
|
a4576cc622 | ||
|
|
9d88d92241 | ||
|
|
d16aac673e | ||
|
|
dede2db213 | ||
|
|
1dc961f661 | ||
|
|
2ce9850de6 | ||
|
|
0bc30e9361 | ||
|
|
9b26d5c0fd | ||
|
|
e671f78741 | ||
|
|
55f8d98bde | ||
|
|
342bae3ffe | ||
|
|
ae52000b29 | ||
|
|
39c5ae86a4 | ||
|
|
7fb9c27e46 | ||
|
|
f4da07c222 | ||
|
|
d25dca19f6 | ||
|
|
1b02662c78 | ||
|
|
36af88b65e | ||
|
|
152c598858 | ||
|
|
6d9632e85e | ||
|
|
5ff8ba2e3e | ||
|
|
c684c83c99 | ||
|
|
defebb0f1f | ||
|
|
54c0935078 | ||
|
|
cde050f964 | ||
|
|
560c6d7969 | ||
|
|
abe95bfc17 | ||
|
|
d2f44b8015 | ||
|
|
ef9dd495be | ||
|
|
9509e40dae | ||
|
|
67918a3f9c | ||
|
|
45951aa82e | ||
|
|
1cdca2e3d5 | ||
|
|
2f22838820 | ||
|
|
d809291553 | ||
|
|
4e572e86d6 | ||
|
|
10700e0ca2 | ||
|
|
3e0f1d3188 | ||
|
|
f26dc1ab19 | ||
|
|
01766ee211 | ||
|
|
79627aea7b | ||
|
|
4d284385ec | ||
|
|
31fa0376cf | ||
|
|
05bc331397 | ||
|
|
7520d20f12 | ||
|
|
210f20e714 | ||
|
|
aad054ef39 | ||
|
|
653db7efa3 | ||
|
|
5025ca3a75 | ||
|
|
55c30af979 | ||
|
|
9ee8e364f3 | ||
|
|
ad9cd31168 | ||
|
|
99267d2943 | ||
|
|
ecf22d5c4a | ||
|
|
1803643f1f | ||
|
|
f247751409 | ||
|
|
48fdb692ef | ||
|
|
b9cef492f8 | ||
|
|
524d0d4d57 | ||
|
|
20c28ecc7c | ||
|
|
3793a7b23b | ||
|
|
ef64fc50b8 | ||
|
|
15987d9952 | ||
|
|
2bf8aa98a7 | ||
|
|
832d9ebe95 | ||
|
|
4b7a596ece | ||
|
|
0454e8914f | ||
|
|
7a77c9f86b | ||
|
|
a8b4bc0d2d | ||
|
|
9db58f21dc | ||
|
|
9797725ae8 | ||
|
|
3c6139950c | ||
|
|
9534f6df8b | ||
|
|
a390180975 | ||
|
|
f038fe1941 | ||
|
|
8058cecae0 | ||
|
|
dd835e73a8 | ||
|
|
78cf477b88 | ||
|
|
2d1d0cc5d2 | ||
|
|
75a5e816a9 | ||
|
|
e16c95c5b3 | ||
|
|
91fa60324f | ||
|
|
6dd9cb1b7c | ||
|
|
fa28489b63 | ||
|
|
9849ed5ebb | ||
|
|
a103411bf7 | ||
|
|
a57662693c | ||
|
|
44d54cc9f2 | ||
|
|
3342e77538 | ||
|
|
8fb44467a0 | ||
|
|
c2feb5b983 | ||
|
|
9fb0b7233a | ||
|
|
89c05932dc | ||
|
|
2f1defe3cd | ||
|
|
f50feff63c | ||
|
|
34e6db0d3f | ||
|
|
04cb70f1f7 | ||
|
|
58195eed68 | ||
|
|
d5d46d5b0b | ||
|
|
f0fc440425 | ||
|
|
30fd739fa9 | ||
|
|
de0e1f4462 | ||
|
|
34b6bb9349 | ||
|
|
3fcd6cfd73 | ||
|
|
f1e3ed88ea |
87 changed files with 1970 additions and 2049 deletions
26
.github/workflows/release.yml
vendored
Normal file
26
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types:
|
||||||
|
- published # reacts to releases and prereleases, but not their drafts
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
- name: "Setup Java"
|
||||||
|
uses: actions/setup-java@v5
|
||||||
|
with:
|
||||||
|
distribution: zulu
|
||||||
|
java-version: 11
|
||||||
|
- name: "Setup Clojure"
|
||||||
|
uses: DeLaGuardo/setup-clojure@master
|
||||||
|
with:
|
||||||
|
lein: 2.9.5
|
||||||
|
- name: Deploy to Clojars
|
||||||
|
run: ./scripts/lein-modules do clean, deploy clojars
|
||||||
|
env:
|
||||||
|
CLOJARS_USERNAME: metosinci
|
||||||
|
CLOJARS_PASSWORD: "${{ secrets.CLOJARS_DEPLOY_TOKEN }}"
|
||||||
4
.github/workflows/testsuite.yml
vendored
4
.github/workflows/testsuite.yml
vendored
|
|
@ -9,7 +9,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
# Supported Java versions: LTS releases and latest
|
# Supported Java versions: LTS releases and latest
|
||||||
jdk: [8, 11, 17, 21]
|
jdk: [11, 17, 21]
|
||||||
|
|
||||||
name: Clojure (Java ${{ matrix.jdk }})
|
name: Clojure (Java ${{ matrix.jdk }})
|
||||||
|
|
||||||
|
|
@ -52,7 +52,7 @@ jobs:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Cache dependencies
|
- name: Cache dependencies
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.m2/repository
|
~/.m2/repository
|
||||||
|
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -15,3 +15,4 @@ figwheel_server.log
|
||||||
/.idea
|
/.idea
|
||||||
.clj-kondo
|
.clj-kondo
|
||||||
.shadow-cljs
|
.shadow-cljs
|
||||||
|
.cache
|
||||||
|
|
|
||||||
120
CHANGELOG.md
120
CHANGELOG.md
|
|
@ -12,11 +12,83 @@ We use [Break Versioning][breakver]. The version numbers follow a `<major>.<mino
|
||||||
|
|
||||||
[breakver]: https://github.com/ptaoussanis/encore/blob/master/BREAK-VERSIONING.md
|
[breakver]: https://github.com/ptaoussanis/encore/blob/master/BREAK-VERSIONING.md
|
||||||
|
|
||||||
|
## UNRELEASED
|
||||||
|
|
||||||
|
* Improve & document how response schemas get picked in per-content-type coercion. See [docs](./doc/ring/coercion.md#per-content-type-coercion). [#745](https://github.com/metosin/reitit/issues/745).
|
||||||
|
* **BREAKING** Remove unused `reitit.dependency` ns. [#763](https://github.com/metosin/reitit/pull/763)
|
||||||
|
* Support passing options to malli humanize. See [docs](./doc/coercion/malli_coercion.md). [#467](https://github.com/metosin/reitit/issues/467)
|
||||||
|
|
||||||
|
## 0.9.2 (2025-10-28)
|
||||||
|
|
||||||
|
* Allow multimethods as handlers when validating [#755](https://github.com/metosin/reitit/pull/755)
|
||||||
|
* Improve error reporting when generating OpenAPI fails [#754](https://github.com/metosin/reitit/pull/754)
|
||||||
|
* Allow middleware registry to be used when defining middleware in `ring-handler`. See [docs](./doc/ring/middleware_registry.md). [#739](https://github.com/metosin/reitit/pull/739)
|
||||||
|
* Allow passing options (eg. `:malli.transform/add-optional-keys`) to malli's `default-value-transformer`. See [docs](./doc/coercion/malli_coercion.md). [#756](https://github.com/metosin/reitit/pull/756)
|
||||||
|
* **FIX**: `match-by-name!` returning `nil` instead of throwing an exception for some partial matches [#758](https://github.com/metosin/reitit/issues/758)
|
||||||
|
* Updated dependencies:
|
||||||
|
|
||||||
|
```
|
||||||
|
[com.fasterxml.jackson.core/jackson-core "2.20.0"] is available but we use "2.18.2"
|
||||||
|
[com.fasterxml.jackson.core/jackson-databind "2.20.0"] is available but we use "2.18.2"
|
||||||
|
[compojure "1.7.2"] is available but we use "1.7.1"
|
||||||
|
[fipp "0.6.29"] is available but we use "0.6.27"
|
||||||
|
[metosin/malli "0.19.2"] is available but we use "0.18.0"
|
||||||
|
[ring "1.15.3"] is available but we use "1.14.1"
|
||||||
|
[ring/ring-core "1.15.3"] is available but we use "1.14.1"
|
||||||
|
[ring/ring-defaults "0.7.0"] is available but we use "0.6.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 0.9.1 (2025-05-27)
|
||||||
|
|
||||||
|
* **FIX**: response coercion threw an exception for unlisted HTTP status codes if there was no `:default`. Broken in 0.9.0. [#742](https://github.com/metosin/reitit/issues/742)
|
||||||
|
|
||||||
|
## 0.9.0 (2025-05-23)
|
||||||
|
|
||||||
|
* Improvements to mime type handling in `create-file-handler` and `create-resource-handler` [#733](https://github.com/metosin/reitit/pull/733)
|
||||||
|
* New `:mime-types` option to configure a map from file extension to mime type
|
||||||
|
* Don't set Content-Type header at all if mime type is not known
|
||||||
|
* Fix location of OpenAPI deprecated metadata [#714](https://github.com/metosin/reitit/pull/714)
|
||||||
|
* **BREAKING** Fix & clarify `:responses :default` and `:content :default` handling. See [docs](./doc/ring/coercion.md). [#735](https://github.com/metosin/reitit/pull/735)
|
||||||
|
* Summary: If `:responses <status>` is present, `:responses :default` is not used, even if `:responses <status>` defines no schema.
|
||||||
|
* Should not break normal use, but might cause surprises related to defaults applying/not applying
|
||||||
|
* **NOTE** This release depends on malli 0.18.0, which changes the format of OpenAPI & Swagger named schemas from `foo.bar/quux` to `foo.bar.quux`
|
||||||
|
|
||||||
|
## 0.8.0 (2025-03-28)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.7.2..0.8.0)**
|
||||||
|
|
||||||
|
* **BREAKING**: throw error if `:responses` keys are not integers [#667](https://github.com/metosin/reitit/issues/667)
|
||||||
|
* **BREAKING**: Java 8 is no longer supported (Ring-core requires Apache Commons FileUpload which now requires Java 11)
|
||||||
|
* File and resource handlers (`create-file-handler` and `create-resource-handler`)
|
||||||
|
* **BREAKING**: New default is to redirect from `dir` path to `dir/` and serve the index file (if found) on the path ending with `/`
|
||||||
|
* For example the Swagger UI handler now serves the index from `/api-docs/` instead of redirecting to `/api-docs/index.html` (both work)
|
||||||
|
* Mostly this is a visual change, though if you have unit tests checking for response status or redirect, those could break
|
||||||
|
* New option `:index-redirect?` (default false) allows enable redirecting to the index file, e.g. `dir` -> `dir/index.html` (same as the old default)
|
||||||
|
* New option `:canonicalize-uris?` (default true) enables redirect from `dir` to `dir/` if the index file exists for the path
|
||||||
|
* Without this option `dir` would return 404 and `dir/` and `dir/index.html` would return the file
|
||||||
|
* Changes in 0.8.0-alpha1
|
||||||
|
* Updated dependencies:
|
||||||
|
|
||||||
|
```
|
||||||
|
[fipp "0.6.27"] is available but we use "0.6.26"
|
||||||
|
[metosin/jsonista "0.3.13"] is available but we use "0.3.10"
|
||||||
|
[metosin/malli "0.17.0"] is available but we use "0.16.4"
|
||||||
|
[metosin/muuntaja "0.6.11"] is available but we use "0.6.10"
|
||||||
|
[metosin/ring-swagger-ui "5.20.0"] is available but we use "5.9.0"
|
||||||
|
[ring/ring-core "1.14.1"] is available but we use "1.12.2"
|
||||||
|
[ring/ring-defaults "0.6.0"] is available but we use "0.5.0"
|
||||||
|
```
|
||||||
|
|
||||||
## 0.8.0-alpha1 (2025-01-31)
|
## 0.8.0-alpha1 (2025-01-31)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.7.2..0.8.0-alpha1)**
|
||||||
|
|
||||||
* Improve OpenAPI docs, plus don't emit `:description` in the wrong place [#702](https://github.com/metosin/reitit/pull/702)
|
* Improve OpenAPI docs, plus don't emit `:description` in the wrong place [#702](https://github.com/metosin/reitit/pull/702)
|
||||||
|
* Support reitit.walk for all IPersitentMap implementations, fixes coercion with
|
||||||
|
aleph 0.7.2 [#700](https://github.com/metosin/reitit/issues/700), [#701](https://github.com/metosin/reitit/pull/701)
|
||||||
* *POTENTIALLY BREAKING* The frontend functions (href, push/replace-state, navigate, set-query) now
|
* *POTENTIALLY BREAKING* The frontend functions (href, push/replace-state, navigate, set-query) now
|
||||||
encode query-string values using configured coercion when possible (only Malli supports encoding).
|
encode query-string values using configured coercion when possible (only Malli supports encoding).
|
||||||
|
[#716](https://github.com/metosin/reitit/pull/716)
|
||||||
- You can use this to encode query parameter values before they are URL-encoded. This works for DateTimes, collections etc.
|
- You can use this to encode query parameter values before they are URL-encoded. This works for DateTimes, collections etc.
|
||||||
- In most cases this shouldn't break existing uses, but it is possible even without
|
- In most cases this shouldn't break existing uses, but it is possible even without
|
||||||
a custom encoding function, the default Malli string-transformer could encode some values differently
|
a custom encoding function, the default Malli string-transformer could encode some values differently
|
||||||
|
|
@ -24,6 +96,8 @@ We use [Break Versioning][breakver]. The version numbers follow a `<major>.<mino
|
||||||
|
|
||||||
## 0.7.2 (2024-09-02)
|
## 0.7.2 (2024-09-02)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.7.1..0.7.2)**
|
||||||
|
|
||||||
* Speed up routes and inline it in code ring handler [#693](https://github.com/metosin/reitit/pull/693) [#693](https://github.com/metosin/reitit/pull/696)
|
* Speed up routes and inline it in code ring handler [#693](https://github.com/metosin/reitit/pull/693) [#693](https://github.com/metosin/reitit/pull/696)
|
||||||
* Fix: Can't get descendants of classes [#555](https://github.com/metosin/reitit/issues/555)
|
* Fix: Can't get descendants of classes [#555](https://github.com/metosin/reitit/issues/555)
|
||||||
* Faster keywordize [#506](https://github.com/metosin/reitit/pull/506)
|
* Faster keywordize [#506](https://github.com/metosin/reitit/pull/506)
|
||||||
|
|
@ -38,6 +112,8 @@ We use [Break Versioning][breakver]. The version numbers follow a `<major>.<mino
|
||||||
|
|
||||||
## 0.7.1 (2024-06-30)
|
## 0.7.1 (2024-06-30)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.7.0..0.7.1)**
|
||||||
|
|
||||||
* FIX: Route data maps ignore meta-merge options in 0.7.0, breaking compatibility [#679](https://github.com/metosin/reitit/issues/679)
|
* FIX: Route data maps ignore meta-merge options in 0.7.0, breaking compatibility [#679](https://github.com/metosin/reitit/issues/679)
|
||||||
* FIX: Clojure record in route data is converted to a plain map [#686](https://github.com/metosin/reitit/issues/686)
|
* FIX: Clojure record in route data is converted to a plain map [#686](https://github.com/metosin/reitit/issues/686)
|
||||||
* Add arities 1 and 2 to rf/match->path [#685](https://github.com/metosin/reitit/pull/685)
|
* Add arities 1 and 2 to rf/match->path [#685](https://github.com/metosin/reitit/pull/685)
|
||||||
|
|
@ -54,6 +130,8 @@ We use [Break Versioning][breakver]. The version numbers follow a `<major>.<mino
|
||||||
|
|
||||||
## 0.7.0 (2024-04-30)
|
## 0.7.0 (2024-04-30)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.6.0..0.7.0)**
|
||||||
|
|
||||||
The OpenAPI3 release, Year in the making - the changes span over multiple repositories.
|
The OpenAPI3 release, Year in the making - the changes span over multiple repositories.
|
||||||
|
|
||||||
* Openapi3 support, see the [docs](https://github.com/metosin/reitit/blob/master/doc/ring/openapi.md)
|
* Openapi3 support, see the [docs](https://github.com/metosin/reitit/blob/master/doc/ring/openapi.md)
|
||||||
|
|
@ -88,6 +166,8 @@ The OpenAPI3 release, Year in the making - the changes span over multiple reposi
|
||||||
|
|
||||||
## 0.7.0-alpha8 (2024-04-30)
|
## 0.7.0-alpha8 (2024-04-30)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.7.0-alpha7...0.7.0-alpha8)**
|
||||||
|
|
||||||
* Handlers can be vars [#585](https://github.com/metosin/reitit/pull/585)
|
* Handlers can be vars [#585](https://github.com/metosin/reitit/pull/585)
|
||||||
* Fetch OpenAPI content types from Muuntaja [#636](https://github.com/metosin/reitit/issues/636)
|
* Fetch OpenAPI content types from Muuntaja [#636](https://github.com/metosin/reitit/issues/636)
|
||||||
* **BREAKING** OpenAPI support is now clj only
|
* **BREAKING** OpenAPI support is now clj only
|
||||||
|
|
@ -108,6 +188,8 @@ The OpenAPI3 release, Year in the making - the changes span over multiple reposi
|
||||||
|
|
||||||
## 0.7.0-alpha7 (2023-10-03)
|
## 0.7.0-alpha7 (2023-10-03)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.7.0-alpha6...0.7.0-alpha7)**
|
||||||
|
|
||||||
* Revert the group id change from alpha6
|
* Revert the group id change from alpha6
|
||||||
* New release to bring alpha6 changes to the old group id
|
* New release to bring alpha6 changes to the old group id
|
||||||
* Updated dependencies:
|
* Updated dependencies:
|
||||||
|
|
@ -116,20 +198,20 @@ The OpenAPI3 release, Year in the making - the changes span over multiple reposi
|
||||||
[metosin/ring-swagger-ui "4.19.1"] is available but we use "4.18.1"
|
[metosin/ring-swagger-ui "4.19.1"] is available but we use "4.18.1"
|
||||||
```
|
```
|
||||||
|
|
||||||
**[compare](https://github.com/metosin/reitit/compare/0.7.0-alpha6...0.7.0-alpha7)**
|
|
||||||
|
|
||||||
## 0.7.0-alpha6 (2023-09-11)
|
## 0.7.0-alpha6 (2023-09-11)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.7.0-alpha5...0.7.0-alpha6)**
|
||||||
|
|
||||||
* **BREAKING**: require Clojure 1.11, drop support for Clojure 1.10
|
* **BREAKING**: require Clojure 1.11, drop support for Clojure 1.10
|
||||||
* **BREAKING**: new syntax for `:request` and `:response` per-content-type coercions. See [coercion.md](doc/ring/coercion.md). [#627](https://github.com/metosin/reitit/issues/627)
|
* **BREAKING**: new syntax for `:request` and `:response` per-content-type coercions. See [coercion.md](doc/ring/coercion.md). [#627](https://github.com/metosin/reitit/issues/627)
|
||||||
* **BREAKING**: replace the openapi `:content-types` keyword with separate `:openapi/request-content-types` and `:openapi/response-content-types`. See [openapi.md](doc/ring/openapi.md)
|
* **BREAKING**: replace the openapi `:content-types` keyword with separate `:openapi/request-content-types` and `:openapi/response-content-types`. See [openapi.md](doc/ring/openapi.md)
|
||||||
* **NOTE!**: all reitit libraries are now under the `fi.metosin` group on clojars instead of `metosin`. Use `fi.metosin/reitit` in your dependencies instead of `metosin/reitit` to get new versions.
|
* **NOTE!**: all reitit libraries are now under the `fi.metosin` group on clojars instead of `metosin`. Use `fi.metosin/reitit` in your dependencies instead of `metosin/reitit` to get new versions.
|
||||||
- **Reverted in alpha7 due to problems with renaming artifacts**
|
- **Reverted in alpha7 due to problems with renaming artifacts**
|
||||||
|
|
||||||
**[compare](https://github.com/metosin/reitit/compare/0.7.0-alpha5...0.7.0-alpha6)**
|
|
||||||
|
|
||||||
## 0.7.0-alpha5 (2023-06-14)
|
## 0.7.0-alpha5 (2023-06-14)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.7.0-alpha4...0.7.0-alpha5)**
|
||||||
|
|
||||||
* **BREAKING**: `compile-request-coercers` returns a map with `:data` and `:coerce` instead of plain `:coerce` function
|
* **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`.
|
* **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
|
* Fixed some module dependencies so Cljdoc can properly analyze all the modules
|
||||||
|
|
@ -141,29 +223,29 @@ The OpenAPI3 release, Year in the making - the changes span over multiple reposi
|
||||||
[com.fasterxml.jackson.core/jackson-databind "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)
|
## 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)**
|
**[compare](https://github.com/metosin/reitit/compare/0.7.0-alpha3...0.7.0-alpha4)**
|
||||||
|
|
||||||
## 0.7.0-alpha3 (2023-05-05)
|
* OpenAPI 3 parameter descriptions get populated from malli/spec/schema descriptions. [#612](https://github.com/metosin/reitit/issues/612)
|
||||||
|
|
||||||
* Compile `reitit.Trie` with Java 1.8 target for compatibility
|
## 0.7.0-alpha3 (2023-05-05)
|
||||||
|
|
||||||
**[compare](https://github.com/metosin/reitit/compare/0.7.0-alpha2...0.7.0-alpha3)**
|
**[compare](https://github.com/metosin/reitit/compare/0.7.0-alpha2...0.7.0-alpha3)**
|
||||||
|
|
||||||
|
* Compile `reitit.Trie` with Java 1.8 target for compatibility
|
||||||
|
|
||||||
## 0.7.0-alpha2 (2023-05-04)
|
## 0.7.0-alpha2 (2023-05-04)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.7.0-alpha1...0.7.0-alpha2)**
|
||||||
|
|
||||||
* Fix reading fragment string on `Html5History` initialization
|
* Fix reading fragment string on `Html5History` initialization
|
||||||
* Add fragment string parameter to reitit-frontend functions ([#604](https://github.com/metosin/reitit/pull/604))
|
* 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)
|
## 0.7.0-alpha1 (2023-05-03)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.6.0...0.7.0-alpha1)**
|
||||||
|
|
||||||
* 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)
|
* 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)
|
* Frontend: provide easy way to update current query params. [#600](https://github.com/metosin/reitit/issues/600)
|
||||||
* Updated dependencies:
|
* Updated dependencies:
|
||||||
|
|
@ -174,10 +256,10 @@ The OpenAPI3 release, Year in the making - the changes span over multiple reposi
|
||||||
[ring/ring-core "1.10.0"] is available but we use "1.9.6"
|
[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)
|
## 0.6.0 (2023-02-21)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.5.18...0.6.0)**
|
||||||
|
|
||||||
* Add reitit-frontend support for fragment string [#581](https://github.com/metosin/reitit/pull/581)
|
* Add reitit-frontend support for fragment string [#581](https://github.com/metosin/reitit/pull/581)
|
||||||
* reloading-ring-handler [#584](https://github.com/metosin/reitit/pull/584)
|
* reloading-ring-handler [#584](https://github.com/metosin/reitit/pull/584)
|
||||||
* Remove redundant s/and [#552](https://github.com/metosin/reitit/pull/552)
|
* Remove redundant s/and [#552](https://github.com/metosin/reitit/pull/552)
|
||||||
|
|
@ -203,23 +285,21 @@ The OpenAPI3 release, Year in the making - the changes span over multiple reposi
|
||||||
[com.fasterxml.jackson.core/jackson-databind "2.14.2"] is available but we use "2.14.1"
|
[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)
|
## 0.5.18 (2022-04-05)
|
||||||
|
|
||||||
|
**[compare](https://github.com/metosin/reitit/compare/0.5.17...0.5.18)**
|
||||||
|
|
||||||
* 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))
|
* 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))
|
||||||
* Save three seq constructions [#537](https://github.com/metosin/reitit/pull/537)
|
* Save three seq constructions [#537](https://github.com/metosin/reitit/pull/537)
|
||||||
* update jackson-databind for CVE-2020-36518 [#544](https://github.com/metosin/reitit/pull/544)
|
* 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)
|
* 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)
|
## 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)**
|
**[compare](https://github.com/metosin/reitit/compare/0.5.16...0.5.17)**
|
||||||
|
|
||||||
|
* FIX match-by-path is broken if there are no non-conflicting wildcard routes [#538](https://github.com/metosin/reitit/issues/538)
|
||||||
|
|
||||||
## 0.5.16 (2022-02-15)
|
## 0.5.16 (2022-02-15)
|
||||||
|
|
||||||
**[compare](https://github.com/metosin/reitit/compare/0.5.15...0.5.16)**
|
**[compare](https://github.com/metosin/reitit/compare/0.5.15...0.5.16)**
|
||||||
|
|
|
||||||
45
README.md
45
README.md
|
|
@ -66,14 +66,14 @@ modules will continue to be released under `metosin` for compatibility purposes.
|
||||||
All main modules bundled:
|
All main modules bundled:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit "0.8.0-alpha1"]
|
[metosin/reitit "0.9.2"]
|
||||||
```
|
```
|
||||||
|
|
||||||
Optionally, the parts can be required separately.
|
Optionally, the parts can be required separately.
|
||||||
|
|
||||||
Malli requires Clojure 1.11.
|
Reitit requires Clojure 1.11 and Java 11.
|
||||||
|
|
||||||
Malli is tested with the LTS releases Java 8, 11, 17 and 21.
|
Reitit is tested with the LTS releases Java 11, 17 and 21.
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
|
|
@ -109,6 +109,7 @@ A Ring routing app with input & output coercion using [data-specs](https://githu
|
||||||
(require '[reitit.ring :as ring])
|
(require '[reitit.ring :as ring])
|
||||||
(require '[reitit.coercion.spec])
|
(require '[reitit.coercion.spec])
|
||||||
(require '[reitit.ring.coercion :as rrc])
|
(require '[reitit.ring.coercion :as rrc])
|
||||||
|
(require '[reitit.ring.middleware.exception :as exception])
|
||||||
(require '[reitit.ring.middleware.muuntaja :as muuntaja])
|
(require '[reitit.ring.middleware.muuntaja :as muuntaja])
|
||||||
(require '[reitit.ring.middleware.parameters :as parameters])
|
(require '[reitit.ring.middleware.parameters :as parameters])
|
||||||
|
|
||||||
|
|
@ -124,39 +125,45 @@ A Ring routing app with input & output coercion using [data-specs](https://githu
|
||||||
;; router data affecting all routes
|
;; router data affecting all routes
|
||||||
{:data {:coercion reitit.coercion.spec/coercion
|
{:data {:coercion reitit.coercion.spec/coercion
|
||||||
:muuntaja m/instance
|
:muuntaja m/instance
|
||||||
:middleware [parameters/parameters-middleware
|
:middleware [parameters/parameters-middleware ; decoding query & form params
|
||||||
|
muuntaja/format-middleware ; content negotiation
|
||||||
|
exception/exception-middleware ; converting exceptions to HTTP responses
|
||||||
rrc/coerce-request-middleware
|
rrc/coerce-request-middleware
|
||||||
muuntaja/format-response-middleware
|
|
||||||
rrc/coerce-response-middleware]}})))
|
rrc/coerce-response-middleware]}})))
|
||||||
```
|
```
|
||||||
|
|
||||||
Valid request:
|
Valid request:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
(app {:request-method :get
|
(-> (app {:request-method :get
|
||||||
:uri "/api/math"
|
:uri "/api/math"
|
||||||
:query-params {:x "1", :y "2"}})
|
:query-params {:x "1", :y "2"}})
|
||||||
|
(update :body slurp))
|
||||||
; {:status 200
|
; {:status 200
|
||||||
; :body {:total 3}}
|
; :body "{\"total\":3}"
|
||||||
|
; :headers {"Content-Type" "application/json; charset=utf-8"}}
|
||||||
```
|
```
|
||||||
|
|
||||||
Invalid request:
|
Invalid request:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
(app {:request-method :get
|
(-> (app {:request-method :get
|
||||||
:uri "/api/math"
|
:uri "/api/math"
|
||||||
:query-params {:x "1", :y "a"}})
|
:query-params {:x "1", :y "a"}})
|
||||||
;{:status 400,
|
(update :body jsonista.core/read-value))
|
||||||
; :body {:type :reitit.coercion/request-coercion,
|
; {:status 400
|
||||||
; :coercion :spec,
|
; :headers {"Content-Type" "application/json; charset=utf-8"}
|
||||||
; :spec "(spec-tools.core/spec {:spec (clojure.spec.alpha/keys :req-un [:$spec20745/x :$spec20745/y]), :type :map, :keys #{:y :x}, :keys/req #{:y :x}})",
|
; :body {"spec" "(spec-tools.core/spec {:spec (clojure.spec.alpha/keys :req-un [:spec$8974/x :spec$8974/y]), :type :map, :leaf? false})"
|
||||||
; :problems [{:path [:y],
|
; "value" {"x" "1"
|
||||||
; :pred "clojure.core/int?",
|
; "y" "a"}
|
||||||
; :val "a",
|
; "problems" [{"via" ["spec$8974/y"]
|
||||||
; :via [:$spec20745/y],
|
; "path" ["y"]
|
||||||
; :in [:y]}],
|
; "pred" "clojure.core/int?"
|
||||||
; :value {:x "1", :y "a"},
|
; "in" ["y"]
|
||||||
; :in [:request :query-params]}}
|
; "val" "a"}]
|
||||||
|
; "type" "reitit.coercion/request-coercion"
|
||||||
|
; "coercion" "spec"
|
||||||
|
; "in" ["request" "query-params"]}}
|
||||||
```
|
```
|
||||||
|
|
||||||
## More examples
|
## More examples
|
||||||
|
|
|
||||||
3
dev-resources/public/site.webmanifest
Normal file
3
dev-resources/public/site.webmanifest
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"name": "Example"
|
||||||
|
}
|
||||||
|
|
@ -41,7 +41,7 @@ There is [#reitit](https://clojurians.slack.com/messages/reitit/) in [Clojurians
|
||||||
All bundled:
|
All bundled:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit "0.8.0-alpha1"]
|
[metosin/reitit "0.9.2"]
|
||||||
```
|
```
|
||||||
|
|
||||||
Optionally, the parts can be required separately.
|
Optionally, the parts can be required separately.
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ The default exception formatting uses `reitit.exception/exception`. It produces
|
||||||
## Pretty Errors
|
## Pretty Errors
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit-dev "0.8.0-alpha1"]
|
[metosin/reitit-dev "0.9.2"]
|
||||||
```
|
```
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,8 @@ Resolved route tree:
|
||||||
; :roles #{:db-admin}}]]
|
; :roles #{:db-admin}}]]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
See also [nested parameter definitions for coercions](../ring/coercion.md#nested-parameter-definitions)
|
||||||
|
|
||||||
## Route Data Fragments
|
## Route Data Fragments
|
||||||
|
|
||||||
Just like [fragments in React.js](https://reactjs.org/docs/fragments.html), we can create routing tree fragments by using empty path `""`. This allows us to add route data without accumulating to path.
|
Just like [fragments in React.js](https://reactjs.org/docs/fragments.html), we can create routing tree fragments by using empty path `""`. This allows us to add route data without accumulating to path.
|
||||||
|
|
|
||||||
|
|
@ -73,9 +73,10 @@ Using `create` with options to create the coercion instead of `coercion`:
|
||||||
{:transformers {:body {:default reitit.coercion.malli/default-transformer-provider
|
{:transformers {:body {:default reitit.coercion.malli/default-transformer-provider
|
||||||
:formats {"application/json" reitit.coercion.malli/json-transformer-provider}}
|
:formats {"application/json" reitit.coercion.malli/json-transformer-provider}}
|
||||||
:string {:default reitit.coercion.malli/string-transformer-provider}
|
:string {:default reitit.coercion.malli/string-transformer-provider}
|
||||||
:response {:default reitit.coercion.malli/default-transformer-provider}}
|
:response {:default reitit.coercion.malli/default-transformer-provider
|
||||||
|
:formats {"application/json" reitit.coercion.malli/json-transformer-provider}}}
|
||||||
;; set of keys to include in error messages
|
;; set of keys to include in error messages
|
||||||
:error-keys #{:type :coercion :in :schema :value :errors :humanized #_:transformed}
|
:error-keys #{:type :coercion :in #_:schema :value #_:errors :humanized #_:transformed}
|
||||||
;; support lite syntax?
|
;; support lite syntax?
|
||||||
:lite true
|
:lite true
|
||||||
;; schema identity function (default: close all map schemas)
|
;; schema identity function (default: close all map schemas)
|
||||||
|
|
@ -87,7 +88,37 @@ Using `create` with options to create the coercion instead of `coercion`:
|
||||||
;; strip-extra-keys (affects only predefined transformers)
|
;; strip-extra-keys (affects only predefined transformers)
|
||||||
:strip-extra-keys true
|
:strip-extra-keys true
|
||||||
;; add/set default values
|
;; add/set default values
|
||||||
|
;; Can be false, true or a map of options to pass to malli.transform/default-value-transformer,
|
||||||
|
;; for example {:malli.transform/add-optional-keys true}
|
||||||
:default-values true
|
:default-values true
|
||||||
|
;; encode-error
|
||||||
|
:encode-error nil
|
||||||
;; malli options
|
;; malli options
|
||||||
:options nil})
|
:options nil})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Configuring humanize error messages
|
||||||
|
|
||||||
|
Malli humanized error messages can be configured using `:options :errors`:
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(reitit.coercion.malli/create
|
||||||
|
{:options
|
||||||
|
{:errors (assoc malli.error/default-errors
|
||||||
|
:malli.core/missing-key {:error/message {:en "MISSING"}})}})
|
||||||
|
```
|
||||||
|
|
||||||
|
See the malli docs for more info.
|
||||||
|
|
||||||
|
## Custom registry
|
||||||
|
|
||||||
|
Malli registry can be configured conveniently via `:options :registry`:
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(require '[malli.core :as m])
|
||||||
|
|
||||||
|
(reitit.coercion.malli/create
|
||||||
|
{:options
|
||||||
|
{:registry {:registry (merge (m/default-schemas)
|
||||||
|
{:my-type :string})}}})
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,14 @@ clojure-lsp clean-ns
|
||||||
The documentation lives under `doc` and it is hosted on [cljdoc](https://cljdoc.org). See their
|
The documentation lives under `doc` and it is hosted on [cljdoc](https://cljdoc.org). See their
|
||||||
documentation for [library authors](https://github.com/cljdoc/cljdoc/blob/master/doc/userguide/for-library-authors.adoc)
|
documentation for [library authors](https://github.com/cljdoc/cljdoc/blob/master/doc/userguide/for-library-authors.adoc)
|
||||||
|
|
||||||
|
## Updating deps
|
||||||
|
|
||||||
|
|
||||||
|
* `lein ancient upgrade`
|
||||||
|
* Mention non-dev non-test dep upgrades in CHANGELOG.md
|
||||||
|
* `npm update --save`
|
||||||
|
* Make a PR, run CI
|
||||||
|
|
||||||
## Making a release
|
## Making a release
|
||||||
|
|
||||||
We use [Break Versioning][breakver]. Remember our promise: patch-level bumps never include breaking changes!
|
We use [Break Versioning][breakver]. Remember our promise: patch-level bumps never include breaking changes!
|
||||||
|
|
@ -32,29 +40,20 @@ We use [Break Versioning][breakver]. Remember our promise: patch-level bumps nev
|
||||||
[breakver]: https://github.com/ptaoussanis/encore/blob/master/BREAK-VERSIONING.md
|
[breakver]: https://github.com/ptaoussanis/encore/blob/master/BREAK-VERSIONING.md
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check that you're using Java 8! Making the release with a newer Java version
|
# create a release commit
|
||||||
# means that it is broken when used with Java 8.
|
|
||||||
java -version
|
|
||||||
|
|
||||||
# new version
|
|
||||||
./scripts/set-version "1.0.0"
|
./scripts/set-version "1.0.0"
|
||||||
|
|
||||||
# create a release commit and a tag
|
# !!! update the changelog
|
||||||
|
|
||||||
git add -u
|
git add -u
|
||||||
git commit -m "Release 1.0.0"
|
git commit -m "Release 1.0.0"
|
||||||
git tag 1.0.0
|
|
||||||
|
|
||||||
# works
|
# push the commit
|
||||||
./scripts/lein-modules install
|
|
||||||
lein test
|
|
||||||
|
|
||||||
# deploy to clojars
|
|
||||||
CLOJARS_USERNAME=*** CLOJARS_PASSWORD=*** ./scripts/lein-modules do clean, deploy clojars
|
|
||||||
|
|
||||||
# push the commit and the tag
|
|
||||||
git push
|
git push
|
||||||
git push --tags
|
|
||||||
|
# !!! check that tests pass on CI
|
||||||
```
|
```
|
||||||
|
|
||||||
* Remembor to update the changelog!
|
* Create a new release on github at <https://github.com/metosin/reitit/releases>
|
||||||
|
* This will trigger the automated release workflow <https://github.com/metosin/reitit/actions/workflows/release.yml>
|
||||||
* Announce the release at least on #reitit in Clojurians.
|
* Announce the release at least on #reitit in Clojurians.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Default Interceptors
|
# Default Interceptors
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit-interceptors "0.8.0-alpha1"]
|
[metosin/reitit-interceptors "0.9.2"]
|
||||||
```
|
```
|
||||||
|
|
||||||
Just like the [ring default middleware](../ring/default_middleware.md), but for interceptors.
|
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
|
## Reitit-http
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit-http "0.8.0-alpha1"]
|
[metosin/reitit-http "0.9.2"]
|
||||||
```
|
```
|
||||||
|
|
||||||
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.
|
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.
|
[Pedestal](http://pedestal.io/) is a backend web framework for Clojure. `reitit-pedestal` provides an alternative routing engine for Pedestal.
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit-pedestal "0.8.0-alpha1"]
|
[metosin/reitit-pedestal "0.9.2"]
|
||||||
```
|
```
|
||||||
|
|
||||||
Why should one use reitit instead of the Pedestal [default routing](http://pedestal.io/reference/routing-quick-reference)?
|
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
|
```clj
|
||||||
; [io.pedestal/pedestal.service "0.5.5"]
|
; [io.pedestal/pedestal.service "0.5.5"]
|
||||||
; [io.pedestal/pedestal.jetty "0.5.5"]
|
; [io.pedestal/pedestal.jetty "0.5.5"]
|
||||||
; [metosin/reitit-pedestal "0.8.0-alpha1"]
|
; [metosin/reitit-pedestal "0.9.2"]
|
||||||
; [metosin/reitit "0.8.0-alpha1"]
|
; [metosin/reitit "0.9.2"]
|
||||||
|
|
||||||
(require '[io.pedestal.http :as server])
|
(require '[io.pedestal.http :as server])
|
||||||
(require '[reitit.pedestal :as pedestal])
|
(require '[reitit.pedestal :as pedestal])
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Sieppari
|
# Sieppari
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit-sieppari "0.8.0-alpha1"]
|
[metosin/reitit-sieppari "0.9.2"]
|
||||||
```
|
```
|
||||||
|
|
||||||
[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).
|
[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
|
### Printing Context Diffs
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit-interceptors "0.8.0-alpha1"]
|
[metosin/reitit-interceptors "0.9.2"]
|
||||||
```
|
```
|
||||||
|
|
||||||
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:
|
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:
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ Coercion can be attached to route data under `:coercion` key. There can be multi
|
||||||
|
|
||||||
Parameters are defined in route data under `:parameters` key. It's value should be a map of parameter `:type` -> Coercion Schema.
|
Parameters are defined in route data under `:parameters` key. It's value should be a map of parameter `:type` -> Coercion Schema.
|
||||||
|
|
||||||
Responses are defined in route data under `:responses` key. It's value should be a map of http status code to a map which can contain `:body` key with Coercion Schema as value.
|
Responses are defined in route data under `:responses` key. It's value should be a map of http status code to a map which can contain `:body` key with Coercion Schema as value. Additionally, the key `:default` specifies the coercion for other status codes.
|
||||||
|
|
||||||
Below is an example with [Plumatic Schema](https://github.com/plumatic/schema). It defines schemas for `:query`, `:body` and `:path` parameters and for http 200 response `:body`.
|
Below is an example with [Plumatic Schema](https://github.com/plumatic/schema). It defines schemas for `:query`, `:body` and `:path` parameters and for http 200 response `:body`.
|
||||||
|
|
||||||
|
|
@ -54,7 +54,8 @@ Handlers can access the coerced parameters via the `:parameters` key in the requ
|
||||||
:parameters {:query {:x s/Int}
|
:parameters {:query {:x s/Int}
|
||||||
:body {:y s/Int}
|
:body {:y s/Int}
|
||||||
:path {:z s/Int}}
|
:path {:z s/Int}}
|
||||||
:responses {200 {:body {:total PositiveInt}}}
|
:responses {200 {:body {:total PositiveInt}}
|
||||||
|
:default {:body {:error s/Str}}}
|
||||||
:handler (fn [{:keys [parameters]}]
|
:handler (fn [{:keys [parameters]}]
|
||||||
(let [total (+ (-> parameters :query :x)
|
(let [total (+ (-> parameters :query :x)
|
||||||
(-> parameters :body :y)
|
(-> parameters :body :y)
|
||||||
|
|
@ -63,6 +64,36 @@ Handlers can access the coerced parameters via the `:parameters` key in the requ
|
||||||
:body {:total total}}))})
|
:body {:total total}}))})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Nested parameter definitions
|
||||||
|
|
||||||
|
Parameters are accumulated recursively along the route tree, just like
|
||||||
|
other [route data](../basics/route_data.md). There is special case
|
||||||
|
handling for merging eg. malli `:map` schemas.
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(def router
|
||||||
|
(reitit.ring/router
|
||||||
|
["/api" {:get {:parameters {:query [:map [:api-key :string]]}}}
|
||||||
|
["/project/:project-id" {:get {:parameters {:path [:map [:project-id :int]]}}}
|
||||||
|
["/task/:task-id" {:get {:parameters {:path [:map [:task-id :int]]
|
||||||
|
:query [:map [:details :boolean]]}
|
||||||
|
:handler (fn [req] (prn req))}}]]]
|
||||||
|
{:data {:coercion reitit.coercion.malli/coercion}}))
|
||||||
|
```
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(-> (r/match-by-path router "/api/project/1/task/2") :result :get :data :parameters)
|
||||||
|
; {:query [:map
|
||||||
|
; {:closed true}
|
||||||
|
; [:api-key :string]
|
||||||
|
; [:details :boolean]],
|
||||||
|
; :path [:map
|
||||||
|
; {:closed true}
|
||||||
|
; [:project-id :int]
|
||||||
|
; [:task-id :int]]}
|
||||||
|
```
|
||||||
|
|
||||||
## Coercion Middleware
|
## Coercion Middleware
|
||||||
|
|
||||||
Defining a coercion for a route data doesn't do anything, as it's just data. We have to attach some code to apply the actual coercion. We can use the middleware from `reitit.ring.coercion`:
|
Defining a coercion for a route data doesn't do anything, as it's just data. We have to attach some code to apply the actual coercion. We can use the middleware from `reitit.ring.coercion`:
|
||||||
|
|
@ -171,11 +202,32 @@ is:
|
||||||
"application/edn" {:schema {:x s/Int}}
|
"application/edn" {:schema {:x s/Int}}
|
||||||
:default {:schema {:ww s/Int}}}}}
|
:default {:schema {:ww s/Int}}}}}
|
||||||
:handler ...}}]]
|
:handler ...}}]]
|
||||||
{:data {:middleware [rrc/coerce-exceptions-middleware
|
{:data {:muuntaja muuntaja.core/instance
|
||||||
rrc/coerce-request-middleware
|
:middleware [reitit.ring.middleware.muuntaja/format-middleware
|
||||||
rrc/coerce-response-middleware]}})))
|
reitit.ring.coercion/coerce-exceptions-middleware
|
||||||
|
reitit.ring.coercion/coerce-request-middleware
|
||||||
|
reitit.ring.coercion/coerce-response-middleware]}})))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The resolution logic for response coercers is:
|
||||||
|
1. Get the response status, or `:default` from the `:responses` map
|
||||||
|
2. From this map, get use the first of these to coerce:
|
||||||
|
1. `:content <content-type> :schema`
|
||||||
|
2. `:content :default :schema`
|
||||||
|
3. `:body`
|
||||||
|
3. If nothing was found, do not coerce
|
||||||
|
|
||||||
|
To select the response content-type, you can either:
|
||||||
|
1. Let muuntaja pick the content-type based on things like the request Accept header
|
||||||
|
- This is what most users want
|
||||||
|
2. Set `:muuntaja/content-type` in the response to pick an explicit content type
|
||||||
|
3. Set the `"Content-Type"` header in the response
|
||||||
|
- This disables muuntaja, so you need to encode your response body in some other way!
|
||||||
|
- This is not compatible with response schema checking, since coercion won't know what to do with the already-encoded response body.
|
||||||
|
4. Use the `:extract-response-format` option to inject your own logic. See `reitit.coercion/extract-response-format-default` for the default.
|
||||||
|
|
||||||
|
See also the [muuntaja content negotiation](./content_negotiation.md) docs.
|
||||||
|
|
||||||
## Pretty printing spec errors
|
## Pretty printing spec errors
|
||||||
|
|
||||||
Spec problems are exposed as is in request & response coercion errors. Pretty-printers like [expound](https://github.com/bhb/expound) can be enabled like this:
|
Spec problems are exposed as is in request & response coercion errors. Pretty-printers like [expound](https://github.com/bhb/expound) can be enabled like this:
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,8 @@ To demonstrate the two approaches, below is the response coercion middleware wri
|
||||||
coercion (-> match :data :coercion)
|
coercion (-> match :data :coercion)
|
||||||
opts (-> match :data :opts)]
|
opts (-> match :data :opts)]
|
||||||
(if (and coercion responses)
|
(if (and coercion responses)
|
||||||
(let [coercers (response-coercers coercion responses opts)]
|
(let [coercer (response-coercer coercion responses opts)]
|
||||||
(coerce-response coercers request response))
|
(coercer request response))
|
||||||
response)))
|
response)))
|
||||||
([request respond raise]
|
([request respond raise]
|
||||||
(let [method (:request-method request)
|
(let [method (:request-method request)
|
||||||
|
|
@ -37,8 +37,8 @@ To demonstrate the two approaches, below is the response coercion middleware wri
|
||||||
coercion (-> match :data :coercion)
|
coercion (-> match :data :coercion)
|
||||||
opts (-> match :data :opts)]
|
opts (-> match :data :opts)]
|
||||||
(if (and coercion responses)
|
(if (and coercion responses)
|
||||||
(let [coercers (response-coercers coercion responses opts)]
|
(let [coercer (response-coercer coercion responses opts)]
|
||||||
(handler request #(respond (coerce-response coercers request %))))
|
(handler request #(respond (coercer request %))))
|
||||||
(handler request respond raise))))))
|
(handler request respond raise))))))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -60,13 +60,13 @@ To demonstrate the two approaches, below is the response coercion middleware wri
|
||||||
:spec ::rs/responses
|
:spec ::rs/responses
|
||||||
:compile (fn [{:keys [coercion responses]} opts]
|
:compile (fn [{:keys [coercion responses]} opts]
|
||||||
(if (and coercion responses)
|
(if (and coercion responses)
|
||||||
(let [coercers (coercion/response-coercers coercion responses opts)]
|
(let [coercer (coercion/response-coercer coercion responses opts)]
|
||||||
(fn [handler]
|
(fn [handler]
|
||||||
(fn
|
(fn
|
||||||
([request]
|
([request]
|
||||||
(coercion/coerce-response coercers request (handler request)))
|
(coercer request (handler request)))
|
||||||
([request respond raise]
|
([request respond raise]
|
||||||
(handler request #(respond (coercion/coerce-response coercers request %)) raise)))))))})
|
(handler request #(respond (coercer request %)) raise)))))))})
|
||||||
```
|
```
|
||||||
|
|
||||||
It has 50% less code, it's much easier to reason about and is much faster.
|
It has 50% less code, it's much easier to reason about and is much faster.
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
Ring [defines middleware](https://github.com/ring-clojure/ring/wiki/Concepts#middleware) as a function of type `handler & args => request => response`. It is relatively easy to understand and allows for good performance. A downside is that the middleware chain is just a opaque function, making things like debugging and composition hard. It is too easy to apply the middlewares in wrong order.
|
Ring [defines middleware](https://github.com/ring-clojure/ring/wiki/Concepts#middleware) as a function of type `handler & args => request => response`. It is relatively easy to understand and allows for good performance. A downside is that the middleware chain is just a opaque function, making things like debugging and composition hard. It is too easy to apply the middlewares in wrong order.
|
||||||
|
|
||||||
|
For the basics of reitit middleware, [read this first](ring.md#middleware).
|
||||||
|
|
||||||
Reitit defines middleware as data:
|
Reitit defines middleware as data:
|
||||||
|
|
||||||
1. A middleware can be defined as first-class data entries
|
1. A middleware can be defined as first-class data entries
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Default Middleware
|
# Default Middleware
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit-middleware "0.8.0-alpha1"]
|
[metosin/reitit-middleware "0.9.2"]
|
||||||
```
|
```
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
@ -17,8 +17,6 @@ Any Ring middleware can be used with `reitit-ring`, but using data-driven middle
|
||||||
`reitit.ring.middleware.parameters/parameters-middleware` to capture query- and form-params. Wraps
|
`reitit.ring.middleware.parameters/parameters-middleware` to capture query- and form-params. Wraps
|
||||||
`ring.middleware.params/wrap-params`.
|
`ring.middleware.params/wrap-params`.
|
||||||
|
|
||||||
**NOTE**: This middleware will be factored into two parts: a query-parameters middleware and a Muuntaja format responsible for the the `application/x-www-form-urlencoded` body format. cf. https://github.com/metosin/reitit/issues/134
|
|
||||||
|
|
||||||
## Exception Handling
|
## Exception Handling
|
||||||
|
|
||||||
See [Exception Handling with Ring](exceptions.md).
|
See [Exception Handling with Ring](exceptions.md).
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Exception Handling with Ring
|
# Exception Handling with Ring
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit-middleware "0.8.0-alpha1"]
|
[metosin/reitit-middleware "0.9.2"]
|
||||||
```
|
```
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,20 @@ Router creation fails fast if the registry doesn't contain the middleware:
|
||||||
;| :bonus | reitit.ring_test$wrap_bonus@59fddabb |
|
;| :bonus | reitit.ring_test$wrap_bonus@59fddabb |
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Middleware defined in the registry can also be used on the `ring-handler` level:
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(def app
|
||||||
|
(ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
["/api"
|
||||||
|
["/bonus" {:get (fn [{:keys [bonus]}]
|
||||||
|
{:status 200, :body {:bonus bonus}})}]]
|
||||||
|
{::middleware/registry {:bonus wrap-bonus}})
|
||||||
|
nil
|
||||||
|
{:middleware [[:bonus 15]]}))
|
||||||
|
```
|
||||||
|
|
||||||
## When to use the registry?
|
## When to use the registry?
|
||||||
|
|
||||||
Middleware as Keywords helps to keep the routes (all but handlers) as literal data (i.e. data that evaluates to itself), enabling the routes to be persisted in external formats like EDN-files and databases. Duct is a good example, where the [middleware can be referenced from EDN-files](https://github.com/duct-framework/duct/wiki/Configuration). It should be easy to make Duct configuration a Middleware Registry in `reitit-ring`.
|
Middleware as Keywords helps to keep the routes (all but handlers) as literal data (i.e. data that evaluates to itself), enabling the routes to be persisted in external formats like EDN-files and databases. Duct is a good example, where the [middleware can be referenced from EDN-files](https://github.com/duct-framework/duct/wiki/Configuration). It should be easy to make Duct configuration a Middleware Registry in `reitit-ring`.
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,7 @@ Malli:
|
||||||
[:x
|
[:x
|
||||||
{:title "X parameter"
|
{:title "X parameter"
|
||||||
:description "Description for X parameter"
|
:description "Description for X parameter"
|
||||||
|
:json-schema/deprecated true
|
||||||
:json-schema/default 42}
|
:json-schema/default 42}
|
||||||
int?]
|
int?]
|
||||||
[:y int?]]}}}]
|
[:y int?]]}}}]
|
||||||
|
|
@ -151,6 +152,7 @@ Schema:
|
||||||
{:post
|
{:post
|
||||||
{:parameters
|
{:parameters
|
||||||
{:body {:x (schema-tools.core/schema s/Num {:description "Description for X parameter"
|
{:body {:x (schema-tools.core/schema s/Num {:description "Description for X parameter"
|
||||||
|
:openapi/deprecated true
|
||||||
:openapi/example 13
|
:openapi/example 13
|
||||||
:openapi/default 42})
|
:openapi/default 42})
|
||||||
:y int?}}}}]
|
:y int?}}}}]
|
||||||
|
|
@ -165,6 +167,7 @@ Spec:
|
||||||
{:body (spec-tools.data-spec/spec ::foo
|
{:body (spec-tools.data-spec/spec ::foo
|
||||||
{:x (schema-tools.core/spec {:spec int?
|
{:x (schema-tools.core/spec {:spec int?
|
||||||
:description "Description for X parameter"
|
:description "Description for X parameter"
|
||||||
|
:openapi/deprecated true
|
||||||
:openapi/example 13
|
:openapi/example 13
|
||||||
:openapi/default 42})
|
:openapi/default 42})
|
||||||
:y int?}}}}}]
|
:y int?}}}}}]
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
Read more about the [Ring Concepts](https://github.com/ring-clojure/ring/wiki/Concepts).
|
Read more about the [Ring Concepts](https://github.com/ring-clojure/ring/wiki/Concepts).
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit-ring "0.8.0-alpha1"]
|
[metosin/reitit-ring "0.9.2"]
|
||||||
```
|
```
|
||||||
|
|
||||||
## `reitit.ring/router`
|
## `reitit.ring/router`
|
||||||
|
|
@ -141,7 +141,7 @@ Name-based reverse routing:
|
||||||
|
|
||||||
# Middleware
|
# Middleware
|
||||||
|
|
||||||
Middleware can be mounted using a `:middleware` key - either to top-level or under request method submap. Its value should be a vector of `reitit.middleware/IntoMiddleware` values. These include:
|
Middleware can be mounted using a `:middleware` key in [Route Data](../basics/route_data.md) - either to top-level or under request method submap. Its value should be a vector of `reitit.middleware/IntoMiddleware` values. These include:
|
||||||
|
|
||||||
1. normal ring middleware function `handler -> request -> response`
|
1. normal ring middleware function `handler -> request -> response`
|
||||||
2. vector of middleware function `[handler args*] -> request -> response` and it's arguments
|
2. vector of middleware function `[handler args*] -> request -> response` and it's arguments
|
||||||
|
|
@ -194,11 +194,56 @@ Top-level middleware, applied before any routing is done:
|
||||||
(def app
|
(def app
|
||||||
(ring/ring-handler
|
(ring/ring-handler
|
||||||
(ring/router
|
(ring/router
|
||||||
["/api" {:middleware [[mw :api]]}
|
["/api" {:middleware [[wrap :api]]}
|
||||||
["/get" {:get handler}]])
|
["/get" {:get handler}]])
|
||||||
nil
|
nil
|
||||||
{:middleware [[mw :top]]}))
|
{:middleware [[wrap :top]]}))
|
||||||
|
|
||||||
(app {:request-method :get, :uri "/api/get"})
|
(app {:request-method :get, :uri "/api/get"})
|
||||||
; {:status 200, :body [:top :api :ok]}
|
; {:status 200, :body [:top :api :ok]}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Same middleware for all routes, using [top-level route data](route_data.md#top-level-route-data):
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(def app
|
||||||
|
(ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
["/api"
|
||||||
|
["/get" {:get handler
|
||||||
|
:middleware [[wrap :specific]]}]]
|
||||||
|
{:data {:middleware [[wrap :generic]]}})))
|
||||||
|
|
||||||
|
(app {:request-method :get, :uri "/api/get"})
|
||||||
|
; {:status 200, :body [:generic :specific :handler]}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Execution order
|
||||||
|
|
||||||
|
Here's a full example that shows the execution order of the middleware
|
||||||
|
using all of the above techniques:
|
||||||
|
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(def app
|
||||||
|
(ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
["/api" {:middleware [[wrap :3-parent]]}
|
||||||
|
["/get" {:get handler
|
||||||
|
:middleware [[wrap :4-route]]}]]
|
||||||
|
{:data {:middleware [[wrap :2-top-level-route-data]]}})
|
||||||
|
nil
|
||||||
|
{:middleware [[wrap :1-top]]}))
|
||||||
|
|
||||||
|
(app {:request-method :get, :uri "/api/get"})
|
||||||
|
; {:status 200, :body [:1-top :2-top-level-route-data :3-parent :4-route :handler]}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Which method should I use for defining middleware?
|
||||||
|
|
||||||
|
- If you have middleware that you want to apply to the default handler (second argument of `ring/ring-handler`), use _top-level middleware_
|
||||||
|
- If you have a generic middleware, that doesn't depend on the route, use _top-level middleware_ or _top-level route data_
|
||||||
|
- If you are using top-level route data anyway for some other reasons, it might be clearest to have all the middleware there. This is what most of the reitit examples do.
|
||||||
|
- If you want to apply a middleware to only a couple of routes, use _nested middleware_ (ie. _route data_)
|
||||||
|
- If you want a middleware to apply to all routes, but use route-specific data, you need _top-level route data_ combined with [Compiling Middleware](compiling_middleware.md)
|
||||||
|
- This is what many reitit features like [Ring Coercion](coercion.md) do. Check the examples & docs for the reitit features you want to use!
|
||||||
|
|
|
||||||
|
|
@ -55,12 +55,14 @@ This way, they are only served if none of the actual routes have matched.
|
||||||
`reitit.ring/create-file-handler` and `reitit.ring/create-resource-handler` take optionally an options map to configure how the files are being served.
|
`reitit.ring/create-file-handler` and `reitit.ring/create-resource-handler` take optionally an options map to configure how the files are being served.
|
||||||
|
|
||||||
| key | description |
|
| key | description |
|
||||||
| -------------------|-------------|
|
| --------------------|-------------|
|
||||||
| :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:`
|
| :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:`
|
||||||
| :root | optional resource root, defaults to `\"public\"`
|
| :root | optional resource root, defaults to `\"public\"`
|
||||||
| :path | path to mount the handler to. Required when mounted outside of a router, does not work inside a router.
|
| :path | path to mount the handler to. Required when mounted outside of a router, does not work inside a router.
|
||||||
| :loader | optional class loader to resolve the resources
|
| :loader | optional class loader to resolve the resources
|
||||||
| :index-files | optional vector of index-files to look in a resource directory, defaults to `[\"index.html\"]`
|
| :index-files | optional vector of index-files to look in a resource directory, defaults to `[\"index.html\"]`
|
||||||
|
| :index-redirect? | optional boolean: if true (default false), redirect to index file, if false serve it directly
|
||||||
|
| :canonicalize-uris? | optional boolean: if true (default), try to serve index files for non directory paths (paths that end with slash)
|
||||||
| :not-found-handler | optional handler function to use if the requested resource is missing (404 Not Found)
|
| :not-found-handler | optional handler function to use if the requested resource is missing (404 Not Found)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Swagger Support
|
# Swagger Support
|
||||||
|
|
||||||
```
|
```
|
||||||
[metosin/reitit-swagger "0.8.0-alpha1"]
|
[metosin/reitit-swagger "0.9.2"]
|
||||||
```
|
```
|
||||||
|
|
||||||
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.
|
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.
|
[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.8.0-alpha1"]
|
[metosin/reitit-swagger-ui "0.9.2"]
|
||||||
```
|
```
|
||||||
|
|
||||||
`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:
|
`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:
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ There is an extra option in the Ring router (actually, in the underlying middlew
|
||||||
### Printing Request Diffs
|
### Printing Request Diffs
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit-middleware "0.8.0-alpha1"]
|
[metosin/reitit-middleware "0.9.2"]
|
||||||
```
|
```
|
||||||
|
|
||||||
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:
|
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:
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,6 @@
|
||||||
:description "Reitit Buddy Auth App"
|
:description "Reitit Buddy Auth App"
|
||||||
:dependencies [[org.clojure/clojure "1.11.2"]
|
:dependencies [[org.clojure/clojure "1.11.2"]
|
||||||
[ring/ring-jetty-adapter "1.12.1"]
|
[ring/ring-jetty-adapter "1.12.1"]
|
||||||
[metosin/reitit "0.8.0-alpha1"]
|
[metosin/reitit "0.9.2"]
|
||||||
[buddy "2.0.0"]]
|
[buddy "2.0.0"]]
|
||||||
:repl-options {:init-ns example.server})
|
:repl-options {:init-ns example.server})
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@
|
||||||
[ring "1.12.1"]
|
[ring "1.12.1"]
|
||||||
[hiccup "1.0.5"]
|
[hiccup "1.0.5"]
|
||||||
[org.clojure/clojurescript "1.11.132"]
|
[org.clojure/clojurescript "1.11.132"]
|
||||||
[metosin/reitit "0.8.0-alpha1"]
|
[metosin/reitit "0.9.2"]
|
||||||
[metosin/reitit-schema "0.8.0-alpha1"]
|
[metosin/reitit-schema "0.9.2"]
|
||||||
[metosin/reitit-frontend "0.8.0-alpha1"]
|
[metosin/reitit-frontend "0.9.2"]
|
||||||
[cljsjs/react "17.0.2-0"]
|
[cljsjs/react "17.0.2-0"]
|
||||||
[cljsjs/react-dom "17.0.2-0"]
|
[cljsjs/react-dom "17.0.2-0"]
|
||||||
;; Just for pretty printting the match
|
;; Just for pretty printting the match
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@
|
||||||
[ring "1.12.1"]
|
[ring "1.12.1"]
|
||||||
[hiccup "1.0.5"]
|
[hiccup "1.0.5"]
|
||||||
[org.clojure/clojurescript "1.11.132"]
|
[org.clojure/clojurescript "1.11.132"]
|
||||||
[metosin/reitit "0.8.0-alpha1"]
|
[metosin/reitit "0.9.2"]
|
||||||
[metosin/reitit-schema "0.8.0-alpha1"]
|
[metosin/reitit-schema "0.9.2"]
|
||||||
[metosin/reitit-frontend "0.8.0-alpha1"]
|
[metosin/reitit-frontend "0.9.2"]
|
||||||
[cljsjs/react "17.0.2-0"]
|
[cljsjs/react "17.0.2-0"]
|
||||||
[cljsjs/react-dom "17.0.2-0"]
|
[cljsjs/react-dom "17.0.2-0"]
|
||||||
;; Just for pretty printting the match
|
;; Just for pretty printting the match
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@
|
||||||
[ring "1.12.1"]
|
[ring "1.12.1"]
|
||||||
[hiccup "1.0.5"]
|
[hiccup "1.0.5"]
|
||||||
[org.clojure/clojurescript "1.10.520"]
|
[org.clojure/clojurescript "1.10.520"]
|
||||||
[metosin/reitit "0.8.0-alpha1"]
|
[metosin/reitit "0.9.2"]
|
||||||
[metosin/reitit-spec "0.8.0-alpha1"]
|
[metosin/reitit-spec "0.9.2"]
|
||||||
[metosin/reitit-frontend "0.8.0-alpha1"]
|
[metosin/reitit-frontend "0.9.2"]
|
||||||
[cljsjs/react "17.0.2-0"]
|
[cljsjs/react "17.0.2-0"]
|
||||||
[cljsjs/react-dom "17.0.2-0"]
|
[cljsjs/react-dom "17.0.2-0"]
|
||||||
;; Just for pretty printting the match
|
;; Just for pretty printting the match
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@
|
||||||
[ring "1.12.1"]
|
[ring "1.12.1"]
|
||||||
[hiccup "1.0.5"]
|
[hiccup "1.0.5"]
|
||||||
[org.clojure/clojurescript "1.11.132"]
|
[org.clojure/clojurescript "1.11.132"]
|
||||||
[metosin/reitit "0.8.0-alpha1"]
|
[metosin/reitit "0.9.2"]
|
||||||
[metosin/reitit-malli "0.8.0-alpha1"]
|
[metosin/reitit-malli "0.9.2"]
|
||||||
[metosin/reitit-frontend "0.8.0-alpha1"]
|
[metosin/reitit-frontend "0.9.2"]
|
||||||
[cljsjs/react "17.0.2-0"]
|
[cljsjs/react "17.0.2-0"]
|
||||||
[cljsjs/react-dom "17.0.2-0"]
|
[cljsjs/react-dom "17.0.2-0"]
|
||||||
;; Just for pretty printting the match
|
;; Just for pretty printting the match
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@
|
||||||
[ring "1.12.1"]
|
[ring "1.12.1"]
|
||||||
[hiccup "1.0.5"]
|
[hiccup "1.0.5"]
|
||||||
[org.clojure/clojurescript "1.11.132"]
|
[org.clojure/clojurescript "1.11.132"]
|
||||||
[metosin/reitit "0.8.0-alpha1"]
|
[metosin/reitit "0.9.2"]
|
||||||
[metosin/reitit-spec "0.8.0-alpha1"]
|
[metosin/reitit-spec "0.9.2"]
|
||||||
[metosin/reitit-frontend "0.8.0-alpha1"]
|
[metosin/reitit-frontend "0.9.2"]
|
||||||
[cljsjs/react "17.0.2-0"]
|
[cljsjs/react "17.0.2-0"]
|
||||||
[cljsjs/react-dom "17.0.2-0"]
|
[cljsjs/react-dom "17.0.2-0"]
|
||||||
;; Just for pretty printting the match
|
;; Just for pretty printting the match
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
(defproject frontend-re-frame "0.1.0-SNAPSHOT"
|
(defproject frontend-re-frame "0.1.0-SNAPSHOT"
|
||||||
:dependencies [[org.clojure/clojure "1.11.2"]
|
:dependencies [[org.clojure/clojure "1.11.2"]
|
||||||
[org.clojure/clojurescript "1.11.132"]
|
[org.clojure/clojurescript "1.11.132"]
|
||||||
[metosin/reitit "0.8.0-alpha1"]
|
[metosin/reitit "0.9.2"]
|
||||||
[reagent "1.2.0"]
|
[reagent "1.2.0"]
|
||||||
[re-frame "0.10.6"]
|
[re-frame "0.10.6"]
|
||||||
[cljsjs/react "17.0.2-0"]
|
[cljsjs/react "17.0.2-0"]
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@
|
||||||
[ring "1.12.1"]
|
[ring "1.12.1"]
|
||||||
[hiccup "1.0.5"]
|
[hiccup "1.0.5"]
|
||||||
[org.clojure/clojurescript "1.11.132"]
|
[org.clojure/clojurescript "1.11.132"]
|
||||||
[metosin/reitit "0.8.0-alpha1"]
|
[metosin/reitit "0.9.2"]
|
||||||
[metosin/reitit-spec "0.8.0-alpha1"]
|
[metosin/reitit-spec "0.9.2"]
|
||||||
[metosin/reitit-frontend "0.8.0-alpha1"]
|
[metosin/reitit-frontend "0.9.2"]
|
||||||
[cljsjs/react "17.0.2-0"]
|
[cljsjs/react "17.0.2-0"]
|
||||||
[cljsjs/react-dom "17.0.2-0"]
|
[cljsjs/react-dom "17.0.2-0"]
|
||||||
;; Just for pretty printting the match
|
;; Just for pretty printting the match
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,6 @@
|
||||||
:dependencies [[org.clojure/clojure "1.11.2"]
|
:dependencies [[org.clojure/clojure "1.11.2"]
|
||||||
[ring/ring-jetty-adapter "1.12.1"]
|
[ring/ring-jetty-adapter "1.12.1"]
|
||||||
[aleph "0.7.1"]
|
[aleph "0.7.1"]
|
||||||
[metosin/reitit "0.8.0-alpha1"]
|
[metosin/reitit "0.9.2"]
|
||||||
[metosin/ring-swagger-ui "5.9.0"]]
|
[metosin/ring-swagger-ui "5.9.0"]]
|
||||||
:repl-options {:init-ns example.server})
|
:repl-options {:init-ns example.server})
|
||||||
|
|
|
||||||
|
|
@ -5,5 +5,5 @@
|
||||||
[funcool/promesa "11.0.678"]
|
[funcool/promesa "11.0.678"]
|
||||||
[manifold "0.4.2"]
|
[manifold "0.4.2"]
|
||||||
[ring/ring-jetty-adapter "1.12.1"]
|
[ring/ring-jetty-adapter "1.12.1"]
|
||||||
[metosin/reitit "0.8.0-alpha1"]]
|
[metosin/reitit "0.9.2"]]
|
||||||
:repl-options {:init-ns example.server})
|
:repl-options {:init-ns example.server})
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,4 @@
|
||||||
:description "Reitit coercion with vanilla ring"
|
:description "Reitit coercion with vanilla ring"
|
||||||
:dependencies [[org.clojure/clojure "1.11.2"]
|
:dependencies [[org.clojure/clojure "1.11.2"]
|
||||||
[ring/ring-jetty-adapter "1.12.1"]
|
[ring/ring-jetty-adapter "1.12.1"]
|
||||||
[metosin/reitit "0.8.0-alpha1"]])
|
[metosin/reitit "0.9.2"]])
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
:dependencies [[org.clojure/clojure "1.11.2"]
|
:dependencies [[org.clojure/clojure "1.11.2"]
|
||||||
[metosin/jsonista "0.3.8"]
|
[metosin/jsonista "0.3.8"]
|
||||||
[ring/ring-jetty-adapter "1.12.1"]
|
[ring/ring-jetty-adapter "1.12.1"]
|
||||||
[metosin/reitit "0.8.0-alpha1"]
|
[metosin/reitit "0.9.2"]
|
||||||
[metosin/ring-swagger-ui "5.9.0"]]
|
[metosin/ring-swagger-ui "5.9.0"]]
|
||||||
:repl-options {:init-ns example.server}
|
:repl-options {:init-ns example.server}
|
||||||
:profiles {:dev {:dependencies [[ring/ring-mock "0.4.0"]]}})
|
:profiles {:dev {:dependencies [[ring/ring-mock "0.4.0"]]}})
|
||||||
|
|
|
||||||
|
|
@ -52,24 +52,35 @@
|
||||||
{:get {:summary "Fetch a pizza | Multiple content-types, multiple examples"
|
{:get {:summary "Fetch a pizza | Multiple content-types, multiple examples"
|
||||||
:responses {200 {:description "Fetch a pizza as json or EDN"
|
:responses {200 {:description "Fetch a pizza as json or EDN"
|
||||||
:content {"application/json" {:schema [:map
|
:content {"application/json" {:schema [:map
|
||||||
|
[:format [:enum :json]]
|
||||||
[:color :keyword]
|
[:color :keyword]
|
||||||
[:pineapple :boolean]]
|
[:pineapple :boolean]]
|
||||||
:examples {:white {:description "White pizza with pineapple"
|
:examples {:white {:description "White pizza with pineapple"
|
||||||
:value {:color :white
|
:value {:format :json
|
||||||
|
:color :white
|
||||||
:pineapple true}}
|
:pineapple true}}
|
||||||
:red {:description "Red pizza"
|
:red {:description "Red pizza"
|
||||||
:value {:color :red
|
:value {:format :json
|
||||||
|
:color :red
|
||||||
:pineapple false}}}}
|
:pineapple false}}}}
|
||||||
"application/edn" {:schema [:map
|
"application/edn" {:schema [:map
|
||||||
|
[:format [:enum :edn]]
|
||||||
[:color :keyword]
|
[:color :keyword]
|
||||||
[:pineapple :boolean]]
|
[:pineapple :boolean]]
|
||||||
:examples {:red {:description "Red pizza with pineapple"
|
:examples {:red {:description "Red pizza with pineapple"
|
||||||
:value (pr-str {:color :red :pineapple true})}}}}}}
|
:value (pr-str {:format :edn :color :red :pineapple true})}}}}}}
|
||||||
:handler (fn [_request]
|
:handler (fn [_request]
|
||||||
|
(rand-nth [{:status 200
|
||||||
|
:muuntaja/content-type "application/json"
|
||||||
|
:body {:format :json
|
||||||
|
:color :red
|
||||||
|
:pineapple true}}
|
||||||
{:status 200
|
{:status 200
|
||||||
:body {:color :red
|
:muuntaja/content-type "application/edn"
|
||||||
:pineapple true}})}
|
:body {:format :edn
|
||||||
:post {:summary "Create a pizza | Multiple content-types, multiple examples"
|
:color :red
|
||||||
|
:pineapple true}}]))}
|
||||||
|
:post {:summary "Create a pizza | Multiple content-types, multiple examples | Default response schema"
|
||||||
:request {:description "Create a pizza using json or EDN"
|
:request {:description "Create a pizza using json or EDN"
|
||||||
:content {"application/json" {:schema [:map
|
:content {"application/json" {:schema [:map
|
||||||
[:color :keyword]
|
[:color :keyword]
|
||||||
|
|
@ -83,10 +94,16 @@
|
||||||
:pineapple false})}}}}}
|
:pineapple false})}}}}}
|
||||||
:responses {200 {:description "Success"
|
:responses {200 {:description "Success"
|
||||||
:content {:default {:schema [:map [:success :boolean]]
|
:content {:default {:schema [:map [:success :boolean]]
|
||||||
:example {:success true}}}}}
|
:example {:success true}}}}
|
||||||
|
:default {:description "Not success"
|
||||||
|
:content {:default {:schema [:map [:error :string]]
|
||||||
|
:example {:error "error"}}}}}
|
||||||
:handler (fn [_request]
|
:handler (fn [_request]
|
||||||
|
(if (< (Math/random) 0.5)
|
||||||
{:status 200
|
{:status 200
|
||||||
:body {:success true}})}}]
|
:body {:success true}}
|
||||||
|
{:status 500
|
||||||
|
:body {:error "an error happened"}}))}}]
|
||||||
|
|
||||||
|
|
||||||
["/contact"
|
["/contact"
|
||||||
|
|
@ -97,6 +114,10 @@
|
||||||
:json-schema/default 30
|
:json-schema/default 30
|
||||||
:json-schema/example 10}
|
:json-schema/example 10}
|
||||||
int?]
|
int?]
|
||||||
|
[:charset {:title "Which charset to use?"
|
||||||
|
:optional true
|
||||||
|
:json-schema/deprecated true}
|
||||||
|
string?]
|
||||||
[:email {:title "Email address to search for"
|
[:email {:title "Email address to search for"
|
||||||
:json-schema/format "email"}
|
:json-schema/format "email"}
|
||||||
string?]]}
|
string?]]}
|
||||||
|
|
@ -127,6 +148,32 @@
|
||||||
{:from "0003"
|
{:from "0003"
|
||||||
:amount -6.5}]}})}}]
|
:amount -6.5}]}})}}]
|
||||||
|
|
||||||
|
["/complex"
|
||||||
|
{:post {:summary "Complex schema with :multi, :enum, :tuple etc."
|
||||||
|
:request {:content
|
||||||
|
{:default
|
||||||
|
{:schema [:map
|
||||||
|
[:vector-of-tuples [:vector [:tuple :string :int]]]
|
||||||
|
[:regex [:re "[0-9]+"]]
|
||||||
|
[:enum [:enum 1 3 5 42]]
|
||||||
|
[:multi [:multi {:dispatch :type}
|
||||||
|
[:literal [:map
|
||||||
|
[:type [:= :literal]]
|
||||||
|
[:value [:or :int :string]]]]
|
||||||
|
[:reference [:map
|
||||||
|
[:type [:= :reference]]
|
||||||
|
[:description :string]
|
||||||
|
[:ref :uuid]]]]]]
|
||||||
|
:example {:vector-of-tuples [["a" 1] ["b" 2]]
|
||||||
|
:regex "01234"
|
||||||
|
:enum 5
|
||||||
|
:multi {:type :literal
|
||||||
|
:value "x"}}}}}
|
||||||
|
:responses {200 {:content {:default {:schema [:map]}}}}
|
||||||
|
:handler (fn [request]
|
||||||
|
{:status 200
|
||||||
|
:body (:body request)})}}]
|
||||||
|
|
||||||
["/secure"
|
["/secure"
|
||||||
{:tags #{"secure"}
|
{:tags #{"secure"}
|
||||||
:openapi {:security [{"auth" []}]}}
|
:openapi {:security [{"auth" []}]}}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
:dependencies [[org.clojure/clojure "1.11.2"]
|
:dependencies [[org.clojure/clojure "1.11.2"]
|
||||||
[io.pedestal/pedestal.service "0.6.3"]
|
[io.pedestal/pedestal.service "0.6.3"]
|
||||||
[io.pedestal/pedestal.jetty "0.6.3"]
|
[io.pedestal/pedestal.jetty "0.6.3"]
|
||||||
[metosin/reitit-malli "0.8.0-alpha1"]
|
[metosin/reitit-malli "0.9.2"]
|
||||||
[metosin/reitit-pedestal "0.8.0-alpha1"]
|
[metosin/reitit-pedestal "0.9.2"]
|
||||||
[metosin/reitit "0.8.0-alpha1"]]
|
[metosin/reitit "0.9.2"]]
|
||||||
:repl-options {:init-ns server})
|
:repl-options {:init-ns server})
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,6 @@
|
||||||
:dependencies [[org.clojure/clojure "1.11.2"]
|
:dependencies [[org.clojure/clojure "1.11.2"]
|
||||||
[io.pedestal/pedestal.service "0.6.3"]
|
[io.pedestal/pedestal.service "0.6.3"]
|
||||||
[io.pedestal/pedestal.jetty "0.6.3"]
|
[io.pedestal/pedestal.jetty "0.6.3"]
|
||||||
[metosin/reitit-pedestal "0.8.0-alpha1"]
|
[metosin/reitit-pedestal "0.9.2"]
|
||||||
[metosin/reitit "0.8.0-alpha1"]]
|
[metosin/reitit "0.9.2"]]
|
||||||
:repl-options {:init-ns example.server})
|
:repl-options {:init-ns example.server})
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,6 @@
|
||||||
:dependencies [[org.clojure/clojure "1.11.2"]
|
:dependencies [[org.clojure/clojure "1.11.2"]
|
||||||
[io.pedestal/pedestal.service "0.6.3"]
|
[io.pedestal/pedestal.service "0.6.3"]
|
||||||
[io.pedestal/pedestal.jetty "0.6.3"]
|
[io.pedestal/pedestal.jetty "0.6.3"]
|
||||||
[metosin/reitit-pedestal "0.8.0-alpha1"]
|
[metosin/reitit-pedestal "0.9.2"]
|
||||||
[metosin/reitit "0.8.0-alpha1"]]
|
[metosin/reitit "0.9.2"]]
|
||||||
:repl-options {:init-ns example.server})
|
:repl-options {:init-ns example.server})
|
||||||
|
|
|
||||||
|
|
@ -2,5 +2,5 @@
|
||||||
:description "Reitit Ring App"
|
:description "Reitit Ring App"
|
||||||
:dependencies [[org.clojure/clojure "1.11.2"]
|
:dependencies [[org.clojure/clojure "1.11.2"]
|
||||||
[ring/ring-jetty-adapter "1.12.1"]
|
[ring/ring-jetty-adapter "1.12.1"]
|
||||||
[metosin/reitit "0.8.0-alpha1"]]
|
[metosin/reitit "0.9.2"]]
|
||||||
:repl-options {:init-ns example.server})
|
:repl-options {:init-ns example.server})
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
:description "Reitit Ring App with Integrant"
|
:description "Reitit Ring App with Integrant"
|
||||||
:dependencies [[org.clojure/clojure "1.11.2"]
|
:dependencies [[org.clojure/clojure "1.11.2"]
|
||||||
[ring/ring-jetty-adapter "1.12.1"]
|
[ring/ring-jetty-adapter "1.12.1"]
|
||||||
[metosin/reitit "0.8.0-alpha1"]
|
[metosin/reitit "0.9.2"]
|
||||||
[integrant "0.8.1"]]
|
[integrant "0.8.1"]]
|
||||||
:main example.server
|
:main example.server
|
||||||
:repl-options {:init-ns user}
|
:repl-options {:init-ns user}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,6 @@
|
||||||
:dependencies [[org.clojure/clojure "1.11.2"]
|
:dependencies [[org.clojure/clojure "1.11.2"]
|
||||||
[metosin/jsonista "0.3.8"]
|
[metosin/jsonista "0.3.8"]
|
||||||
[ring/ring-jetty-adapter "1.12.1"]
|
[ring/ring-jetty-adapter "1.12.1"]
|
||||||
[metosin/reitit "0.8.0-alpha1"]]
|
[metosin/reitit "0.9.2"]]
|
||||||
:repl-options {:init-ns example.server}
|
:repl-options {:init-ns example.server}
|
||||||
:profiles {:dev {:dependencies [[ring/ring-mock "0.4.0"]]}})
|
:profiles {:dev {:dependencies [[ring/ring-mock "0.4.0"]]}})
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
:dependencies [[org.clojure/clojure "1.11.2"]
|
:dependencies [[org.clojure/clojure "1.11.2"]
|
||||||
[metosin/jsonista "0.3.8"]
|
[metosin/jsonista "0.3.8"]
|
||||||
[ring/ring-jetty-adapter "1.12.1"]
|
[ring/ring-jetty-adapter "1.12.1"]
|
||||||
[metosin/reitit "0.8.0-alpha1"]
|
[metosin/reitit "0.9.2"]
|
||||||
[metosin/ring-swagger-ui "5.9.0"]]
|
[metosin/ring-swagger-ui "5.9.0"]]
|
||||||
:repl-options {:init-ns example.server}
|
:repl-options {:init-ns example.server}
|
||||||
:profiles {:dev {:dependencies [[ring/ring-mock "0.4.0"]]}})
|
:profiles {:dev {:dependencies [[ring/ring-mock "0.4.0"]]}})
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
:description "Reitit Ring App with Swagger"
|
:description "Reitit Ring App with Swagger"
|
||||||
:dependencies [[org.clojure/clojure "1.11.2"]
|
:dependencies [[org.clojure/clojure "1.11.2"]
|
||||||
[ring/ring-jetty-adapter "1.12.1"]
|
[ring/ring-jetty-adapter "1.12.1"]
|
||||||
[metosin/reitit "0.8.0-alpha1"]
|
[metosin/reitit "0.9.2"]
|
||||||
[metosin/ring-swagger-ui "5.9.0"]]
|
[metosin/ring-swagger-ui "5.9.0"]]
|
||||||
:repl-options {:init-ns example.server}
|
:repl-options {:init-ns example.server}
|
||||||
:profiles {:dev {:dependencies [[ring/ring-mock "0.4.0"]]}})
|
:profiles {:dev {:dependencies [[ring/ring-mock "0.4.0"]]}})
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
(defproject metosin/reitit-core "0.8.0-alpha1"
|
(defproject metosin/reitit-core "0.9.2"
|
||||||
:description "Snappy data-driven router for Clojure(Script)"
|
:description "Snappy data-driven router for Clojure(Script)"
|
||||||
:url "https://github.com/metosin/reitit"
|
:url "https://github.com/metosin/reitit"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
|
|
@ -10,5 +10,5 @@
|
||||||
:parent-project {:path "../../project.clj"
|
:parent-project {:path "../../project.clj"
|
||||||
:inherit [:deploy-repositories :managed-dependencies]}
|
:inherit [:deploy-repositories :managed-dependencies]}
|
||||||
:java-source-paths ["java-src"]
|
:java-source-paths ["java-src"]
|
||||||
:javac-options ["-Xlint:unchecked" "-target" "1.8" "-source" "1.8"]
|
:javac-options ["-Xlint:unchecked" "-target" "11" "-source" "11"]
|
||||||
:dependencies [[meta-merge]])
|
:dependencies [[meta-merge]])
|
||||||
|
|
|
||||||
|
|
@ -130,29 +130,6 @@
|
||||||
(request-coercion-failed! result coercion value in request serialize-failed-result)
|
(request-coercion-failed! result coercion value in request serialize-failed-result)
|
||||||
result)))))))
|
result)))))))
|
||||||
|
|
||||||
(defn extract-response-format-default [request _]
|
|
||||||
(-> request :muuntaja/response :format))
|
|
||||||
|
|
||||||
(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 [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 (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)
|
|
||||||
result)))))))
|
|
||||||
|
|
||||||
(defn encode-error [data]
|
(defn encode-error [data]
|
||||||
(-> data
|
(-> data
|
||||||
(dissoc :request :response)
|
(dissoc :request :response)
|
||||||
|
|
@ -165,12 +142,6 @@
|
||||||
(impl/fast-assoc acc k (coercer request)))
|
(impl/fast-assoc acc k (coercer request)))
|
||||||
{} coercers))
|
{} coercers))
|
||||||
|
|
||||||
(defn coerce-response [coercers request response]
|
|
||||||
(if response
|
|
||||||
(if-let [coercer (or (coercers (:status response)) (coercers :default))]
|
|
||||||
(impl/fast-assoc response :body (coercer request response))
|
|
||||||
response)))
|
|
||||||
|
|
||||||
(defn request-coercers
|
(defn request-coercers
|
||||||
([coercion parameters opts]
|
([coercion parameters opts]
|
||||||
(some->> (for [[k v] parameters, :when v]
|
(some->> (for [[k v] parameters, :when v]
|
||||||
|
|
@ -181,10 +152,45 @@
|
||||||
rcs (request-coercers coercion parameters (cond-> opts route-request (assoc ::skip #{:body})))]
|
rcs (request-coercers coercion parameters (cond-> opts route-request (assoc ::skip #{:body})))]
|
||||||
(if (and crc rcs) (into crc (vec rcs)) (or crc rcs)))))
|
(if (and crc rcs) (into crc (vec rcs)) (or crc rcs)))))
|
||||||
|
|
||||||
(defn response-coercers [coercion responses opts]
|
(defn extract-response-format-default [request response]
|
||||||
(some->> (for [[status model] responses]
|
(or (get-in response [:headers "Content-Type"])
|
||||||
[status (response-coercer coercion model opts)])
|
(:muuntaja/content-type response)
|
||||||
(filter second) (seq) (into {})))
|
(-> request :muuntaja/response :format)))
|
||||||
|
|
||||||
|
(defn -format->coercer [coercion {:keys [content body]} _opts]
|
||||||
|
(->> (concat (when body
|
||||||
|
[[:default (-response-coercer coercion body)]])
|
||||||
|
(for [[format {:keys [schema]}] content, :when schema]
|
||||||
|
[format (-response-coercer coercion schema)]))
|
||||||
|
(filter second) (into (array-map))))
|
||||||
|
|
||||||
|
(defn response-coercer [coercion responses {:keys [extract-response-format serialize-failed-result]
|
||||||
|
:or {extract-response-format extract-response-format-default}
|
||||||
|
:as opts}]
|
||||||
|
(when coercion
|
||||||
|
(let [status->format->coercer
|
||||||
|
(into {}
|
||||||
|
(for [[status model] responses]
|
||||||
|
(do
|
||||||
|
(when-not (or (= :default status) (int? status))
|
||||||
|
(throw (ex-info "Response status must be int or :default" {:status status})))
|
||||||
|
[status (-format->coercer coercion model opts)])))]
|
||||||
|
(when-not (every? empty? (vals status->format->coercer)) ;; fast path: return nil if there are no models to coerce
|
||||||
|
(fn [request response]
|
||||||
|
(let [format->coercer (or (status->format->coercer (:status response))
|
||||||
|
(status->format->coercer :default))
|
||||||
|
format (extract-response-format request response)
|
||||||
|
coercer (when format->coercer
|
||||||
|
(or (format->coercer format)
|
||||||
|
(format->coercer :default)))]
|
||||||
|
(if-not coercer
|
||||||
|
response
|
||||||
|
(let [value (:body response)
|
||||||
|
coerced (coercer (:body response) format)
|
||||||
|
result (if (error? coerced)
|
||||||
|
(response-coercion-failed! coerced coercion value request response serialize-failed-result)
|
||||||
|
coerced)]
|
||||||
|
(impl/fast-assoc response :body result)))))))))
|
||||||
|
|
||||||
(defn -compile-parameters [data coercion]
|
(defn -compile-parameters [data coercion]
|
||||||
(impl/path-update data [[[:parameters any?] #(-compile-model coercion % nil)]]))
|
(impl/path-update data [[[:parameters any?] #(-compile-model coercion % nil)]]))
|
||||||
|
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
(ns reitit.dependency
|
|
||||||
"Dependency resolution for middleware/interceptors."
|
|
||||||
(:require [reitit.exception :as exception]))
|
|
||||||
|
|
||||||
(defn- providers
|
|
||||||
"Map from provision key to provider. `get-provides` should return the provision keys of a dependent."
|
|
||||||
[get-provides nodes]
|
|
||||||
(reduce (fn [acc dependent]
|
|
||||||
(into acc
|
|
||||||
(map (fn [provide]
|
|
||||||
(when (contains? acc provide)
|
|
||||||
(exception/fail!
|
|
||||||
(str "multiple providers for: " provide)
|
|
||||||
{::multiple-providers provide}))
|
|
||||||
[provide dependent]))
|
|
||||||
(get-provides dependent)))
|
|
||||||
{} nodes))
|
|
||||||
|
|
||||||
(defn- get-provider
|
|
||||||
"Get the provider for `k`, throw if no provider can be found for it."
|
|
||||||
[providers k]
|
|
||||||
(if (contains? providers k)
|
|
||||||
(get providers k)
|
|
||||||
(exception/fail!
|
|
||||||
(str "provider missing for dependency: " k)
|
|
||||||
{::missing-provider k})))
|
|
||||||
|
|
||||||
(defn post-order
|
|
||||||
"Put `nodes` in post-order. Can also be described as a reverse topological sort.
|
|
||||||
`get-provides` and `get-requires` are callbacks that you can provide to compute the provide and depend
|
|
||||||
key sets of nodes, the defaults are `:provides` and `:requires`."
|
|
||||||
([nodes] (post-order :provides :requires nodes))
|
|
||||||
([get-provides get-requires nodes]
|
|
||||||
(let [providers-by-key (providers get-provides nodes)]
|
|
||||||
(letfn [(toposort [node path colors]
|
|
||||||
(case (get colors node)
|
|
||||||
:white (let [requires (get-requires node)
|
|
||||||
[nodes* colors] (toposort-seq (map (partial get-provider providers-by-key) requires)
|
|
||||||
(conj path node)
|
|
||||||
(assoc colors node :grey))]
|
|
||||||
[(conj nodes* node)
|
|
||||||
(assoc colors node :black)])
|
|
||||||
:grey (exception/fail! "circular dependency" {:cycle (drop-while #(not= % node) (conj path node))})
|
|
||||||
:black [() colors]))
|
|
||||||
|
|
||||||
(toposort-seq [nodes path colors]
|
|
||||||
(reduce (fn [[nodes* colors] node]
|
|
||||||
(let [[nodes** colors] (toposort node path colors)]
|
|
||||||
[(into nodes* nodes**) colors]))
|
|
||||||
[[] colors] nodes))]
|
|
||||||
|
|
||||||
(first (toposort-seq nodes [] (zipmap nodes (repeat :white))))))))
|
|
||||||
|
|
@ -198,9 +198,8 @@
|
||||||
(:path route)))
|
(:path route)))
|
||||||
|
|
||||||
(defn throw-on-missing-path-params [template required path-params]
|
(defn throw-on-missing-path-params [template required path-params]
|
||||||
(when-not (every? #(contains? path-params %) required)
|
(let [missing (set (remove #(get path-params %) required))]
|
||||||
(let [defined (-> path-params keys set)
|
(when-not (empty? missing)
|
||||||
missing (set/difference required defined)]
|
|
||||||
(ex/fail!
|
(ex/fail!
|
||||||
(str "missing path-params for route " template " -> " missing)
|
(str "missing path-params for route " template " -> " missing)
|
||||||
{:path-params path-params, :required required}))))
|
{:path-params path-params, :required required}))))
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,11 @@
|
||||||
;; Default data
|
;; Default data
|
||||||
;;
|
;;
|
||||||
|
|
||||||
|
(defn -multi? [x]
|
||||||
|
(instance? #?(:clj clojure.lang.MultiFn :cljs cljs.core.MultiFn) x))
|
||||||
|
|
||||||
(s/def ::name keyword?)
|
(s/def ::name keyword?)
|
||||||
(s/def ::handler (s/or :fn fn? :var var?))
|
(s/def ::handler (s/or :fn fn? :var var? :multi -multi?))
|
||||||
(s/def ::no-doc boolean?)
|
(s/def ::no-doc boolean?)
|
||||||
(s/def ::conflicting boolean?)
|
(s/def ::conflicting boolean?)
|
||||||
(s/def ::default-data
|
(s/def ::default-data
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
(defproject metosin/reitit-dev "0.8.0-alpha1"
|
(defproject metosin/reitit-dev "0.9.2"
|
||||||
:description "Snappy data-driven router for Clojure(Script)"
|
:description "Snappy data-driven router for Clojure(Script)"
|
||||||
:url "https://github.com/metosin/reitit"
|
:url "https://github.com/metosin/reitit"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
(defproject metosin/reitit-frontend "0.8.0-alpha1"
|
(defproject metosin/reitit-frontend "0.9.2"
|
||||||
:description "Reitit: Clojurescript frontend routing core"
|
:description "Reitit: Clojurescript frontend routing core"
|
||||||
:url "https://github.com/metosin/reitit"
|
:url "https://github.com/metosin/reitit"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
(defproject metosin/reitit-http "0.8.0-alpha1"
|
(defproject metosin/reitit-http "0.9.2"
|
||||||
:description "Reitit: HTTP routing with interceptors"
|
:description "Reitit: HTTP routing with interceptors"
|
||||||
:url "https://github.com/metosin/reitit"
|
:url "https://github.com/metosin/reitit"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
|
|
|
||||||
|
|
@ -41,11 +41,11 @@
|
||||||
(not responses) {}
|
(not responses) {}
|
||||||
;; mount
|
;; mount
|
||||||
:else
|
:else
|
||||||
(if-let [coercers (coercion/response-coercers coercion responses opts)]
|
(if-let [coercer (coercion/response-coercer coercion responses opts)]
|
||||||
{:leave (fn [ctx]
|
{:leave (fn [ctx]
|
||||||
(let [request (:request ctx)
|
(let [request (:request ctx)
|
||||||
response (:response ctx)
|
response (:response ctx)
|
||||||
response (coercion/coerce-response coercers request response)]
|
response (coercer request response)]
|
||||||
(assoc ctx :response response)))}
|
(assoc ctx :response response)))}
|
||||||
{})))})
|
{})))})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
(defproject metosin/reitit-interceptors "0.8.0-alpha1"
|
(defproject metosin/reitit-interceptors "0.9.2"
|
||||||
:description "Reitit, common interceptors bundled"
|
:description "Reitit, common interceptors bundled"
|
||||||
:url "https://github.com/metosin/reitit"
|
:url "https://github.com/metosin/reitit"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
(defproject metosin/reitit-malli "0.8.0-alpha1"
|
(defproject metosin/reitit-malli "0.9.2"
|
||||||
:description "Reitit: Malli coercion"
|
:description "Reitit: Malli coercion"
|
||||||
:url "https://github.com/metosin/reitit"
|
:url "https://github.com/metosin/reitit"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,7 @@
|
||||||
[malli.swagger :as swagger]
|
[malli.swagger :as swagger]
|
||||||
[malli.transform :as mt]
|
[malli.transform :as mt]
|
||||||
[malli.util :as mu]
|
[malli.util :as mu]
|
||||||
[reitit.coercion :as coercion]
|
[reitit.coercion :as coercion]))
|
||||||
[clojure.string :as string]))
|
|
||||||
|
|
||||||
;;
|
;;
|
||||||
;; coercion
|
;; coercion
|
||||||
|
|
@ -31,7 +30,7 @@
|
||||||
(mt/transformer
|
(mt/transformer
|
||||||
(if strip-extra-keys (mt/strip-extra-keys-transformer))
|
(if strip-extra-keys (mt/strip-extra-keys-transformer))
|
||||||
transformer
|
transformer
|
||||||
(if default-values (mt/default-value-transformer))))))
|
(if default-values (mt/default-value-transformer (if (map? default-values) default-values {})))))))
|
||||||
|
|
||||||
(def string-transformer-provider (-provider (mt/string-transformer)))
|
(def string-transformer-provider (-provider (mt/string-transformer)))
|
||||||
(def json-transformer-provider (-provider (mt/json-transformer)))
|
(def json-transformer-provider (-provider (mt/json-transformer)))
|
||||||
|
|
@ -116,7 +115,9 @@
|
||||||
:enabled true
|
:enabled true
|
||||||
;; strip-extra-keys (affects only predefined transformers)
|
;; strip-extra-keys (affects only predefined transformers)
|
||||||
:strip-extra-keys true
|
:strip-extra-keys true
|
||||||
;; add/set default values
|
;; add/set default values.
|
||||||
|
;; Can be false, true or a map of options to pass to malli.transform/default-value-transformer,
|
||||||
|
;; for example {:malli.transform/add-optional-keys true}
|
||||||
:default-values true
|
:default-values true
|
||||||
;; encode-error
|
;; encode-error
|
||||||
:encode-error nil
|
:encode-error nil
|
||||||
|
|
@ -187,7 +188,8 @@
|
||||||
(-open-model [_ schema] schema)
|
(-open-model [_ schema] schema)
|
||||||
(-encode-error [_ error]
|
(-encode-error [_ error]
|
||||||
(cond-> error
|
(cond-> error
|
||||||
(show? :humanized) (assoc :humanized (me/humanize error {:wrap :message}))
|
(show? :humanized) (assoc :humanized (me/humanize error (cond-> {:wrap :message}
|
||||||
|
options (merge options))))
|
||||||
(show? :schema) (update :schema edn/write-string opts)
|
(show? :schema) (update :schema edn/write-string opts)
|
||||||
(show? :errors) (-> (me/with-error-messages opts)
|
(show? :errors) (-> (me/with-error-messages opts)
|
||||||
(update :errors (partial map #(update % :schema edn/write-string opts))))
|
(update :errors (partial map #(update % :schema edn/write-string opts))))
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
(defproject metosin/reitit-middleware "0.8.0-alpha1"
|
(defproject metosin/reitit-middleware "0.9.2"
|
||||||
:description "Reitit, common middleware bundled"
|
:description "Reitit, common middleware bundled"
|
||||||
:url "https://github.com/metosin/reitit"
|
:url "https://github.com/metosin/reitit"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,10 @@
|
||||||
"Creates a Middleware to handle the multipart params, based on
|
"Creates a Middleware to handle the multipart params, based on
|
||||||
ring.middleware.multipart-params, taking same options. Mounts only
|
ring.middleware.multipart-params, taking same options. Mounts only
|
||||||
if endpoint has `[:parameters :multipart]` defined. Publishes coerced
|
if endpoint has `[:parameters :multipart]` defined. Publishes coerced
|
||||||
parameters into `[:parameters :multipart]` under request."
|
parameters into `[:parameters :multipart]` under request.
|
||||||
|
|
||||||
|
Note! You want to have multipart-middleware after coerce-request-middleware,
|
||||||
|
because coerce-request-middleware overwrites `:parameters`."
|
||||||
([]
|
([]
|
||||||
(create-multipart-middleware nil))
|
(create-multipart-middleware nil))
|
||||||
([options]
|
([options]
|
||||||
|
|
@ -69,5 +72,8 @@
|
||||||
"Middleware to handle the multipart params, based on
|
"Middleware to handle the multipart params, based on
|
||||||
ring.middleware.multipart-params, taking same options. Mounts only
|
ring.middleware.multipart-params, taking same options. Mounts only
|
||||||
if endpoint has `[:parameters :multipart]` defined. Publishes coerced
|
if endpoint has `[:parameters :multipart]` defined. Publishes coerced
|
||||||
parameters into `[:parameters :multipart]` under request."
|
parameters into `[:parameters :multipart]` under request.
|
||||||
|
|
||||||
|
Note! You want to have multipart-middleware after coerce-request-middleware,
|
||||||
|
because coerce-request-middleware overwrites `:parameters`."
|
||||||
(create-multipart-middleware))
|
(create-multipart-middleware))
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
(defproject fi.metosin/reitit-openapi "0.8.0-alpha1"
|
(defproject fi.metosin/reitit-openapi "0.9.2"
|
||||||
:description "Reitit: OpenAPI-support"
|
:description "Reitit: OpenAPI-support"
|
||||||
:url "https://github.com/metosin/reitit"
|
:url "https://github.com/metosin/reitit"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
|
|
|
||||||
|
|
@ -110,8 +110,8 @@
|
||||||
(merge {:in (name in)
|
(merge {:in (name in)
|
||||||
:name k
|
:name k
|
||||||
:required (required? k)
|
:required (required? k)
|
||||||
:schema schema}
|
:schema (dissoc schema :description :deprecated)}
|
||||||
(select-keys schema [:description])))
|
(select-keys schema [:description :deprecated])))
|
||||||
(into []))})
|
(into []))})
|
||||||
(when body
|
(when body
|
||||||
;; :body uses a single schema to describe every :requestBody
|
;; :body uses a single schema to describe every :requestBody
|
||||||
|
|
@ -206,9 +206,10 @@
|
||||||
accept-route (fn [route]
|
accept-route (fn [route]
|
||||||
(-> route second :openapi :id (or ::default) (trie/into-set) (set/intersection ids) seq))
|
(-> route second :openapi :id (or ::default) (trie/into-set) (set/intersection ids) seq))
|
||||||
definitions (volatile! {})
|
definitions (volatile! {})
|
||||||
transform-endpoint (fn [[method {{:keys [coercion no-doc openapi] :as data} :data
|
transform-endpoint (fn [path [method {{:keys [coercion no-doc openapi] :as data} :data
|
||||||
middleware :middleware
|
middleware :middleware
|
||||||
interceptors :interceptors}]]
|
interceptors :interceptors}]]
|
||||||
|
(try
|
||||||
(if (and data (not no-doc))
|
(if (and data (not no-doc))
|
||||||
[method
|
[method
|
||||||
(meta-merge
|
(meta-merge
|
||||||
|
|
@ -217,9 +218,11 @@
|
||||||
(if coercion
|
(if coercion
|
||||||
(-get-apidocs-openapi coercion data definitions))
|
(-get-apidocs-openapi coercion data definitions))
|
||||||
(select-keys data [:tags :summary :description])
|
(select-keys data [:tags :summary :description])
|
||||||
(strip-top-level-keys openapi))]))
|
(strip-top-level-keys openapi))])
|
||||||
|
(catch Throwable t
|
||||||
|
(throw (ex-info "While building openapi docs" {:path path :method method} t)))))
|
||||||
transform-path (fn [[p _ c]]
|
transform-path (fn [[p _ c]]
|
||||||
(if-let [endpoint (some->> c (keep transform-endpoint) (seq) (into {}))]
|
(if-let [endpoint (some->> c (keep (partial transform-endpoint p)) (seq) (into {}))]
|
||||||
[(openapi-path p (r/options router)) endpoint]))
|
[(openapi-path p (r/options router)) endpoint]))
|
||||||
map-in-order #(->> % (apply concat) (apply array-map))
|
map-in-order #(->> % (apply concat) (apply array-map))
|
||||||
paths (->> router (r/compiled-routes) (filter accept-route) (map transform-path) map-in-order)]
|
paths (->> router (r/compiled-routes) (filter accept-route) (map transform-path) map-in-order)]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
(defproject metosin/reitit-pedestal "0.8.0-alpha1"
|
(defproject metosin/reitit-pedestal "0.9.2"
|
||||||
:description "Reitit + Pedestal Integration"
|
:description "Reitit + Pedestal Integration"
|
||||||
:url "https://github.com/metosin/reitit"
|
:url "https://github.com/metosin/reitit"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
(defproject metosin/reitit-ring "0.8.0-alpha1"
|
(defproject metosin/reitit-ring "0.9.2"
|
||||||
:description "Reitit: Ring routing"
|
:description "Reitit: Ring routing"
|
||||||
:url "https://github.com/metosin/reitit"
|
:url "https://github.com/metosin/reitit"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
|
|
|
||||||
|
|
@ -227,18 +227,21 @@
|
||||||
;; TODO: ring.middleware.head/wrap-head
|
;; TODO: ring.middleware.head/wrap-head
|
||||||
;; TODO: handle etags
|
;; TODO: handle etags
|
||||||
(defn -create-file-or-resource-handler
|
(defn -create-file-or-resource-handler
|
||||||
[response-fn {:keys [parameter root path loader allow-symlinks? index-files paths not-found-handler]
|
[response-fn {:keys [parameter root path loader allow-symlinks? index-files index-redirect? canonicalize-uris? paths not-found-handler mime-types]
|
||||||
:or {parameter (keyword "")
|
:or {parameter (keyword "")
|
||||||
root "public"
|
root "public"
|
||||||
index-files ["index.html"]
|
index-files ["index.html"]
|
||||||
paths (constantly nil)
|
index-redirect? false
|
||||||
not-found-handler (if path
|
canonicalize-uris? true
|
||||||
(constantly nil)
|
paths (constantly nil)}}]
|
||||||
(constantly {:status 404, :body "", :headers {}}))}}]
|
|
||||||
(let [options {:root root
|
(let [options {:root root
|
||||||
:loader loader
|
:loader loader
|
||||||
:index-files? false
|
:index-files? false
|
||||||
:allow-symlinks? allow-symlinks?}
|
:allow-symlinks? allow-symlinks?}
|
||||||
|
not-found-handler (or not-found-handler
|
||||||
|
(if path
|
||||||
|
(constantly nil)
|
||||||
|
(constantly {:status 404, :body "", :headers {}})))
|
||||||
path-size (count path)
|
path-size (count path)
|
||||||
create (fn [handler]
|
create (fn [handler]
|
||||||
(fn
|
(fn
|
||||||
|
|
@ -247,16 +250,27 @@
|
||||||
join-paths (fn [& paths]
|
join-paths (fn [& paths]
|
||||||
(str/replace (str/replace (str/join "/" paths) #"([/]+)" "/") #"/$" ""))
|
(str/replace (str/replace (str/join "/" paths) #"([/]+)" "/") #"/$" ""))
|
||||||
response (fn [path]
|
response (fn [path]
|
||||||
(if-let [response (or (paths (join-paths "/" path))
|
(when-let [response (or (paths (join-paths "/" path))
|
||||||
(response-fn path options))]
|
(response-fn path options))]
|
||||||
(response/content-type response (mime-type/ext-mime-type path))))
|
(if-let [content-type (mime-type/ext-mime-type path mime-types)]
|
||||||
|
(response/content-type response content-type)
|
||||||
|
response)))
|
||||||
path-or-index-response (fn [path uri]
|
path-or-index-response (fn [path uri]
|
||||||
(or (response path)
|
(or (response path)
|
||||||
|
(when (or canonicalize-uris? (str/ends-with? uri "/"))
|
||||||
(loop [[file & files] index-files]
|
(loop [[file & files] index-files]
|
||||||
(if file
|
(if file
|
||||||
(if (response (join-paths path file))
|
(if-let [resp (response (join-paths path file))]
|
||||||
|
(cond
|
||||||
|
index-redirect?
|
||||||
(response/redirect (join-paths uri file))
|
(response/redirect (join-paths uri file))
|
||||||
(recur files))))))
|
|
||||||
|
(not (str/ends-with? uri "/"))
|
||||||
|
(response/redirect (str uri "/"))
|
||||||
|
|
||||||
|
:else
|
||||||
|
resp)
|
||||||
|
(recur files)))))))
|
||||||
handler (if path
|
handler (if path
|
||||||
(fn [request]
|
(fn [request]
|
||||||
(let [uri (impl/url-decode (:uri request))]
|
(let [uri (impl/url-decode (:uri request))]
|
||||||
|
|
@ -275,13 +289,17 @@
|
||||||
"A ring handler for serving classpath resources, configured via options:
|
"A ring handler for serving classpath resources, configured via options:
|
||||||
|
|
||||||
| key | description |
|
| key | description |
|
||||||
| -------------------|-------------|
|
| --------------------|-------------|
|
||||||
| :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:`
|
| :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:`
|
||||||
| :root | optional resource root, defaults to `\"public\"`
|
| :root | optional resource root, defaults to `\"public\"`
|
||||||
| :path | path to mount the handler to. Required when mounted outside of a router, does not work inside a router.
|
| :path | path to mount the handler to. Required when mounted outside of a router, does not work inside a router.
|
||||||
| :loader | optional class loader to resolve the resources
|
| :loader | optional class loader to resolve the resources
|
||||||
| :index-files | optional vector of index-files to look in a resource directory, defaults to `[\"index.html\"]`
|
| :index-files | optional vector of index-files to look in a resource directory, defaults to `[\"index.html\"]`
|
||||||
| :not-found-handler | optional handler function to use if the requested resource is missing (404 Not Found)"
|
| :index-redirect? | optional boolean: if true (default false), redirect to index file, if false serve it directly
|
||||||
|
| :canonicalize-uris? | optional boolean: if true (default), try to serve index files for non directory paths (paths that end with slash)
|
||||||
|
| :not-found-handler | optional handler function to use if the requested resource is missing (404 Not Found)
|
||||||
|
| :mime-types | optional map of filename extensions to mime-types that will be used to guess the content type in addition to the ones defined in ring.util.mime-type/default-mime-types
|
||||||
|
| :allow-symlinks? | allow symlinks that lead to paths outside the root classpath directories, defaults to false"
|
||||||
([]
|
([]
|
||||||
(create-resource-handler nil))
|
(create-resource-handler nil))
|
||||||
([opts]
|
([opts]
|
||||||
|
|
@ -292,13 +310,17 @@
|
||||||
"A ring handler for serving file resources, configured via options:
|
"A ring handler for serving file resources, configured via options:
|
||||||
|
|
||||||
| key | description |
|
| key | description |
|
||||||
| -------------------|-------------|
|
| --------------------|-------------|
|
||||||
| :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:`
|
| :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:`
|
||||||
| :root | optional resource root, defaults to `\"public\"`
|
| :root | optional resource root, defaults to `\"public\"`
|
||||||
| :path | path to mount the handler to. Required when mounted outside of a router, does not work inside a router.
|
| :path | path to mount the handler to. Required when mounted outside of a router, does not work inside a router.
|
||||||
| :loader | optional class loader to resolve the resources
|
| :loader | optional class loader to resolve the resources
|
||||||
| :index-files | optional vector of index-files to look in a resource directory, defaults to `[\"index.html\"]`
|
| :index-files | optional vector of index-files to look in a resource directory, defaults to `[\"index.html\"]`
|
||||||
| :not-found-handler | optional handler function to use if the requested resource is missing (404 Not Found)"
|
| :index-redirect? | optional boolean: if true (default false), redirect to index file, if false serve it directly
|
||||||
|
| :canonicalize-uris? | optional boolean: if true (default), try to serve index files for non directory paths (paths that end with slash)
|
||||||
|
| :not-found-handler | optional handler function to use if the requested resource is missing (404 Not Found)
|
||||||
|
| :mime-types | optional map of filename extensions to mime-types that will be used to guess the content type in addition to the ones defined in ring.util.mime-type/default-mime-types
|
||||||
|
| :allow-symlinks? | allow symlinks that lead to paths outside the root classpath directories, defaults to false"
|
||||||
([]
|
([]
|
||||||
(create-file-handler nil))
|
(create-file-handler nil))
|
||||||
([opts]
|
([opts]
|
||||||
|
|
@ -350,7 +372,7 @@
|
||||||
([router default-handler {:keys [middleware inject-match? inject-router?]
|
([router default-handler {:keys [middleware inject-match? inject-router?]
|
||||||
:or {inject-match? true, inject-router? true}}]
|
:or {inject-match? true, inject-router? true}}]
|
||||||
(let [default-handler (or default-handler (fn ([_]) ([_ respond _] (respond nil))))
|
(let [default-handler (or default-handler (fn ([_]) ([_ respond _] (respond nil))))
|
||||||
wrap (if middleware (partial middleware/chain middleware) identity)
|
wrap (if middleware #(middleware/chain middleware % nil (r/options router)) identity)
|
||||||
enrich-request (create-enrich-request inject-match? inject-router?)
|
enrich-request (create-enrich-request inject-match? inject-router?)
|
||||||
enrich-default-request (create-enrich-default-request inject-router?)]
|
enrich-default-request (create-enrich-default-request inject-router?)]
|
||||||
(with-meta
|
(with-meta
|
||||||
|
|
|
||||||
|
|
@ -58,13 +58,13 @@
|
||||||
(not responses) {}
|
(not responses) {}
|
||||||
;; mount
|
;; mount
|
||||||
:else
|
:else
|
||||||
(if-let [coercers (coercion/response-coercers coercion responses opts)]
|
(if-let [coercer (coercion/response-coercer coercion responses opts)]
|
||||||
(fn [handler]
|
(fn [handler]
|
||||||
(fn
|
(fn
|
||||||
([request]
|
([request]
|
||||||
(coercion/coerce-response coercers request (handler request)))
|
(coercer request (handler request)))
|
||||||
([request respond raise]
|
([request respond raise]
|
||||||
(handler request #(respond (coercion/coerce-response coercers request %)) raise))))
|
(handler request #(respond (coercer request %)) raise))))
|
||||||
{})))})
|
{})))})
|
||||||
|
|
||||||
(def coerce-exceptions-middleware
|
(def coerce-exceptions-middleware
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
(defproject metosin/reitit-schema "0.8.0-alpha1"
|
(defproject metosin/reitit-schema "0.9.2"
|
||||||
:description "Reitit: Plumatic Schema coercion"
|
:description "Reitit: Plumatic Schema coercion"
|
||||||
:url "https://github.com/metosin/reitit"
|
:url "https://github.com/metosin/reitit"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
(defproject metosin/reitit-sieppari "0.8.0-alpha1"
|
(defproject metosin/reitit-sieppari "0.9.2"
|
||||||
:description "Reitit: Sieppari Interceptors"
|
:description "Reitit: Sieppari Interceptors"
|
||||||
:url "https://github.com/metosin/reitit"
|
:url "https://github.com/metosin/reitit"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
(defproject metosin/reitit-spec "0.8.0-alpha1"
|
(defproject metosin/reitit-spec "0.9.2"
|
||||||
:description "Reitit: clojure.spec coercion"
|
:description "Reitit: clojure.spec coercion"
|
||||||
:url "https://github.com/metosin/reitit"
|
:url "https://github.com/metosin/reitit"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
(defproject metosin/reitit-swagger-ui "0.8.0-alpha1"
|
(defproject metosin/reitit-swagger-ui "0.9.2"
|
||||||
:description "Reitit: Swagger-ui support"
|
:description "Reitit: Swagger-ui support"
|
||||||
:url "https://github.com/metosin/reitit"
|
:url "https://github.com/metosin/reitit"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
(defproject metosin/reitit-swagger "0.8.0-alpha1"
|
(defproject metosin/reitit-swagger "0.9.2"
|
||||||
:description "Reitit: Swagger-support"
|
:description "Reitit: Swagger-support"
|
||||||
:url "https://github.com/metosin/reitit"
|
:url "https://github.com/metosin/reitit"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
(defproject metosin/reitit "0.8.0-alpha1"
|
(defproject metosin/reitit "0.9.2"
|
||||||
:description "Snappy data-driven router for Clojure(Script)"
|
:description "Snappy data-driven router for Clojure(Script)"
|
||||||
:url "https://github.com/metosin/reitit"
|
:url "https://github.com/metosin/reitit"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
|
|
|
||||||
2144
package-lock.json
generated
2144
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -2,13 +2,13 @@
|
||||||
"name": "reitit",
|
"name": "reitit",
|
||||||
"private": true,
|
"private": true,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@seriousme/openapi-schema-validator": "^2.3.1",
|
"@seriousme/openapi-schema-validator": "^2.7.0",
|
||||||
"karma": "^6.4.4",
|
"karma": "^6.4.4",
|
||||||
"karma-chrome-launcher": "^3.2.0",
|
"karma-chrome-launcher": "^3.2.0",
|
||||||
"karma-cli": "^2.0.0",
|
"karma-cli": "^2.0.0",
|
||||||
"karma-cljs-test": "^0.1.0"
|
"karma-cljs-test": "^0.1.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"shadow-cljs": "^2.28.20"
|
"shadow-cljs": "^3.2.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
107
project.clj
107
project.clj
|
|
@ -1,4 +1,4 @@
|
||||||
(defproject metosin/reitit-parent "0.8.0-alpha1"
|
(defproject metosin/reitit-parent "0.9.2"
|
||||||
:description "Snappy data-driven router for Clojure(Script)"
|
:description "Snappy data-driven router for Clojure(Script)"
|
||||||
:url "https://github.com/metosin/reitit"
|
:url "https://github.com/metosin/reitit"
|
||||||
:license {:name "Eclipse Public License"
|
:license {:name "Eclipse Public License"
|
||||||
|
|
@ -6,7 +6,8 @@
|
||||||
:test-paths ["test/clj" "test/cljc"]
|
:test-paths ["test/clj" "test/cljc"]
|
||||||
:deploy-repositories [["clojars" {:url "https://repo.clojars.org"
|
:deploy-repositories [["clojars" {:url "https://repo.clojars.org"
|
||||||
:username :env/clojars_username
|
:username :env/clojars_username
|
||||||
:password :env/clojars_password}]]
|
:password :env/clojars_password
|
||||||
|
:sign-releases false}]]
|
||||||
:repositories [["clojars" {:url "https://repo.clojars.org"
|
:repositories [["clojars" {:url "https://repo.clojars.org"
|
||||||
:username :env/clojars_username
|
:username :env/clojars_username
|
||||||
:password :env/clojars_password}]]
|
:password :env/clojars_password}]]
|
||||||
|
|
@ -15,47 +16,47 @@
|
||||||
:metadata {:doc/format :markdown}}
|
:metadata {:doc/format :markdown}}
|
||||||
:scm {:name "git"
|
:scm {:name "git"
|
||||||
:url "https://github.com/metosin/reitit"}
|
:url "https://github.com/metosin/reitit"}
|
||||||
;; TODO: need to verify that the code actually worked with Java1.8, see #242
|
;; Ring 1.13.1 drops support for Java 1.8 so lets target 11
|
||||||
:javac-options ["-Xlint:unchecked" "-target" "1.8" "-source" "1.8"]
|
:javac-options ["-Xlint:unchecked" "-target" "11" "-source" "11"]
|
||||||
:managed-dependencies [[metosin/reitit "0.8.0-alpha1"]
|
:managed-dependencies [[metosin/reitit "0.9.2"]
|
||||||
[metosin/reitit-core "0.8.0-alpha1"]
|
[metosin/reitit-core "0.9.2"]
|
||||||
[metosin/reitit-dev "0.8.0-alpha1"]
|
[metosin/reitit-dev "0.9.2"]
|
||||||
[metosin/reitit-spec "0.8.0-alpha1"]
|
[metosin/reitit-spec "0.9.2"]
|
||||||
[metosin/reitit-malli "0.8.0-alpha1"]
|
[metosin/reitit-malli "0.9.2"]
|
||||||
[metosin/reitit-schema "0.8.0-alpha1"]
|
[metosin/reitit-schema "0.9.2"]
|
||||||
[metosin/reitit-ring "0.8.0-alpha1"]
|
[metosin/reitit-ring "0.9.2"]
|
||||||
[metosin/reitit-middleware "0.8.0-alpha1"]
|
[metosin/reitit-middleware "0.9.2"]
|
||||||
[metosin/reitit-http "0.8.0-alpha1"]
|
[metosin/reitit-http "0.9.2"]
|
||||||
[metosin/reitit-interceptors "0.8.0-alpha1"]
|
[metosin/reitit-interceptors "0.9.2"]
|
||||||
[metosin/reitit-swagger "0.8.0-alpha1"]
|
[metosin/reitit-swagger "0.9.2"]
|
||||||
[fi.metosin/reitit-openapi "0.8.0-alpha1"]
|
[fi.metosin/reitit-openapi "0.9.2"]
|
||||||
[metosin/reitit-swagger-ui "0.8.0-alpha1"]
|
[metosin/reitit-swagger-ui "0.9.2"]
|
||||||
[metosin/reitit-frontend "0.8.0-alpha1"]
|
[metosin/reitit-frontend "0.9.2"]
|
||||||
[metosin/reitit-sieppari "0.8.0-alpha1"]
|
[metosin/reitit-sieppari "0.9.2"]
|
||||||
[metosin/reitit-pedestal "0.8.0-alpha1"]
|
[metosin/reitit-pedestal "0.9.2"]
|
||||||
[metosin/ring-swagger-ui "5.9.0"]
|
[metosin/ring-swagger-ui "5.20.0"]
|
||||||
[metosin/spec-tools "0.10.7"]
|
[metosin/spec-tools "0.10.8"]
|
||||||
[metosin/schema-tools "0.13.1"]
|
[metosin/schema-tools "0.13.1"]
|
||||||
[metosin/muuntaja "0.6.10"]
|
[metosin/muuntaja "0.6.11"]
|
||||||
[metosin/jsonista "0.3.10"]
|
[metosin/jsonista "0.3.13"]
|
||||||
[metosin/sieppari "0.0.0-alpha13"]
|
[metosin/sieppari "0.0.0-alpha13"]
|
||||||
[metosin/malli "0.16.4"]
|
[metosin/malli "0.19.2"]
|
||||||
|
|
||||||
;; https://clojureverse.org/t/depending-on-the-right-versions-of-jackson-libraries/5111
|
;; https://clojureverse.org/t/depending-on-the-right-versions-of-jackson-libraries/5111
|
||||||
[com.fasterxml.jackson.core/jackson-core "2.17.2"]
|
[com.fasterxml.jackson.core/jackson-core "2.20.0"]
|
||||||
[com.fasterxml.jackson.core/jackson-databind "2.17.2"]
|
[com.fasterxml.jackson.core/jackson-databind "2.20.0"]
|
||||||
|
|
||||||
[meta-merge "1.0.0"]
|
[meta-merge "1.0.0"]
|
||||||
[fipp "0.6.26" :exclusions [org.clojure/core.rrb-vector]]
|
[fipp "0.6.29" :exclusions [org.clojure/core.rrb-vector]]
|
||||||
;; Deep-diff uses this version, override olders versiom from fipp.
|
;; Deep-diff uses this version, override olders versiom from fipp.
|
||||||
[org.clojure/core.rrb-vector "0.2.0"]
|
[org.clojure/core.rrb-vector "0.2.0"]
|
||||||
[expound "0.9.0"]
|
[expound "0.9.0"]
|
||||||
[lambdaisland/deep-diff "0.0-47"]
|
[lambdaisland/deep-diff "0.0-47"]
|
||||||
[com.bhauman/spell-spec "0.1.2"]
|
[com.bhauman/spell-spec "0.1.2"]
|
||||||
[mvxcvi/arrangement "2.1.0"]
|
[mvxcvi/arrangement "2.1.0"]
|
||||||
[ring/ring-core "1.12.2"]
|
[ring/ring-core "1.15.3"]
|
||||||
|
|
||||||
[io.pedestal/pedestal.service "0.6.4"]]
|
[io.pedestal/pedestal.service "0.6.4" :upgrade false]]
|
||||||
|
|
||||||
:plugins [[jonase/eastwood "1.4.3"]
|
:plugins [[jonase/eastwood "1.4.3"]
|
||||||
;[lein-virgil "0.1.7"]
|
;[lein-virgil "0.1.7"]
|
||||||
|
|
@ -89,64 +90,64 @@
|
||||||
:java-source-paths ["modules/reitit-core/java-src"]
|
:java-source-paths ["modules/reitit-core/java-src"]
|
||||||
|
|
||||||
:dependencies [[org.clojure/clojure "1.11.4"]
|
:dependencies [[org.clojure/clojure "1.11.4"]
|
||||||
[thheller/shadow-cljs "2.28.20"]
|
[thheller/shadow-cljs "3.2.1"]
|
||||||
[org.clojure/clojurescript "1.11.132"]
|
[org.clojure/clojurescript "1.12.42"]
|
||||||
|
|
||||||
;; modules dependencies
|
;; modules dependencies
|
||||||
[metosin/schema-tools "0.13.1"]
|
[metosin/schema-tools "0.13.1"]
|
||||||
[metosin/spec-tools "0.10.7"]
|
[metosin/spec-tools "0.10.8"]
|
||||||
[metosin/muuntaja "0.6.10"]
|
[metosin/muuntaja "0.6.11"]
|
||||||
[metosin/sieppari "0.0.0-alpha13"]
|
[metosin/sieppari "0.0.0-alpha13"]
|
||||||
[metosin/jsonista "0.3.10"]
|
[metosin/jsonista "0.3.13"]
|
||||||
[metosin/malli "0.16.4"]
|
[metosin/malli "0.19.2"]
|
||||||
[lambdaisland/deep-diff "0.0-47"]
|
[lambdaisland/deep-diff "0.0-47"]
|
||||||
[meta-merge "1.0.0"]
|
[meta-merge "1.0.0"]
|
||||||
[com.bhauman/spell-spec "0.1.2"]
|
[com.bhauman/spell-spec "0.1.2"]
|
||||||
[expound "0.9.0"]
|
[expound "0.9.0"]
|
||||||
[fipp "0.6.26"]
|
[fipp "0.6.29"]
|
||||||
|
|
||||||
[orchestra "2021.01.01-1"]
|
[orchestra "2021.01.01-1"]
|
||||||
|
|
||||||
[ring "1.12.2"]
|
[ring "1.15.3"]
|
||||||
[ikitommi/immutant-web "3.0.0-alpha1"]
|
[ikitommi/immutant-web "3.0.0-alpha1"]
|
||||||
[metosin/ring-http-response "0.9.4"]
|
[metosin/ring-http-response "0.9.5"]
|
||||||
[metosin/ring-swagger-ui "5.9.0"]
|
[metosin/ring-swagger-ui "5.20.0"]
|
||||||
[org.clojure/tools.analyzer "1.2.0"]
|
[org.clojure/tools.analyzer "1.2.0"]
|
||||||
|
|
||||||
[criterium "0.4.6"]
|
[criterium "0.4.6"]
|
||||||
[org.clojure/test.check "1.1.1"]
|
[org.clojure/test.check "1.1.1"]
|
||||||
[org.clojure/tools.namespace "1.5.0"]
|
[org.clojure/tools.namespace "1.5.0"]
|
||||||
[com.gfredericks/test.chuck "0.2.14"]
|
[com.gfredericks/test.chuck "0.2.15"]
|
||||||
[nubank/matcher-combinators "3.9.1"]
|
[nubank/matcher-combinators "3.9.2"]
|
||||||
|
|
||||||
[io.pedestal/pedestal.service "0.6.4"]
|
;; TODO: adapt to breaking changes in pedestal 0.7 and 0.8
|
||||||
|
[io.pedestal/pedestal.service "0.6.4" :upgrade false]
|
||||||
|
|
||||||
[org.clojure/core.async "1.6.681"]
|
[org.clojure/core.async "1.8.741"]
|
||||||
[manifold "0.4.3"]
|
[manifold "0.4.3"]
|
||||||
[funcool/promesa "11.0.678"]
|
[funcool/promesa "11.0.678"]
|
||||||
|
|
||||||
[com.clojure-goes-fast/clj-async-profiler "1.2.2"]
|
[com.clojure-goes-fast/clj-async-profiler "1.6.2"]
|
||||||
[ring-cors "0.1.13"]
|
[ring-cors "0.1.13"]
|
||||||
|
|
||||||
[com.bhauman/rebel-readline "0.1.4"]]}
|
[com.bhauman/rebel-readline "0.1.5"]]}
|
||||||
:shadow {:test-paths ["test/cljs"]}
|
:shadow {:test-paths ["test/cljs"]}
|
||||||
:perf {:jvm-opts ^:replace ["-server"
|
:perf {:jvm-opts ^:replace ["-server"
|
||||||
"-Xmx4096m"
|
"-Xmx4096m"
|
||||||
"-Dclojure.compiler.direct-linking=true"]
|
"-Dclojure.compiler.direct-linking=true"]
|
||||||
:test-paths ["perf-test/clj"]
|
:test-paths ["perf-test/clj"]
|
||||||
:dependencies [[compojure "1.7.1"]
|
:dependencies [[compojure "1.7.2"]
|
||||||
[ring/ring-defaults "0.5.0"]
|
[ring/ring-defaults "0.7.0"]
|
||||||
[ikitommi/immutant-web "3.0.0-alpha1"]
|
[ikitommi/immutant-web "3.0.0-alpha1"]
|
||||||
[io.pedestal/pedestal.service "0.6.4"]
|
[io.pedestal/pedestal.service "0.6.4" :upgrade false]
|
||||||
[io.pedestal/pedestal.jetty "0.6.4"]
|
[io.pedestal/pedestal.jetty "0.6.4" :upgrade false]
|
||||||
[calfpath "0.8.1"]
|
[calfpath "0.8.1"]
|
||||||
[org.clojure/core.async "1.6.681"]
|
[org.clojure/core.async "1.8.741"]
|
||||||
[manifold "0.4.3"]
|
[manifold "0.4.3"]
|
||||||
[funcool/promesa "11.0.678"]
|
[funcool/promesa "11.0.678"]
|
||||||
[metosin/sieppari]
|
[metosin/sieppari]
|
||||||
[yada "1.2.16"]
|
[yada "1.2.16"]
|
||||||
[aleph "0.8.1"]
|
[aleph "0.9.3"]
|
||||||
[ring/ring-defaults "0.5.0"]
|
|
||||||
[ataraxy "0.4.3"]
|
[ataraxy "0.4.3"]
|
||||||
[bidi "2.1.6"]
|
[bidi "2.1.6"]
|
||||||
[janus "1.3.2"]]}
|
[janus "1.3.2"]]}
|
||||||
|
|
|
||||||
|
|
@ -386,9 +386,9 @@
|
||||||
|
|
||||||
(testing "index-files"
|
(testing "index-files"
|
||||||
(let [response (app (request "/docs"))]
|
(let [response (app (request "/docs"))]
|
||||||
(is (= (redirect "/docs/index.html") response)))
|
(is (= (redirect "/docs/") response)))
|
||||||
(let [response (app (request "/docs/"))]
|
(let [response (app (request "/docs/"))]
|
||||||
(is (= (redirect "/docs/index.html") response))))
|
(is (= 200 (:status response)))))
|
||||||
|
|
||||||
(testing "not found"
|
(testing "not found"
|
||||||
(let [response (app (request "/not-found"))]
|
(let [response (app (request "/not-found"))]
|
||||||
|
|
@ -424,9 +424,9 @@
|
||||||
|
|
||||||
(testing "index-files"
|
(testing "index-files"
|
||||||
(let [response (app (request "/docs"))]
|
(let [response (app (request "/docs"))]
|
||||||
(is (= (redirect "/docs/index.html") response)))
|
(is (= (redirect "/docs/") response)))
|
||||||
(let [response (app (request "/docs/"))]
|
(let [response (app (request "/docs/"))]
|
||||||
(is (= (redirect "/docs/index.html") response))))
|
(is (= 200 (:status response)))))
|
||||||
|
|
||||||
(testing "not found"
|
(testing "not found"
|
||||||
(let [response (app (request "/not-found"))]
|
(let [response (app (request "/not-found"))]
|
||||||
|
|
@ -463,9 +463,9 @@
|
||||||
|
|
||||||
(testing "index-files"
|
(testing "index-files"
|
||||||
(let [response (app (request "/docs"))]
|
(let [response (app (request "/docs"))]
|
||||||
(is (= (redirect "/docs/index.html") response)))
|
(is (= (redirect "/docs/") response)))
|
||||||
(let [response (app (request "/docs/"))]
|
(let [response (app (request "/docs/"))]
|
||||||
(is (= (redirect "/docs/index.html") response))))
|
(is (= 200 (:status response)))))
|
||||||
|
|
||||||
(testing "not found"
|
(testing "not found"
|
||||||
(let [response (app (request "/not-found"))]
|
(let [response (app (request "/not-found"))]
|
||||||
|
|
@ -502,9 +502,9 @@
|
||||||
|
|
||||||
(testing "index-files"
|
(testing "index-files"
|
||||||
(let [response (app (request "/docs"))]
|
(let [response (app (request "/docs"))]
|
||||||
(is (= (redirect "/docs/index.html") response)))
|
(is (= (redirect "/docs/") response)))
|
||||||
(let [response (app (request "/docs/"))]
|
(let [response (app (request "/docs/"))]
|
||||||
(is (= (redirect "/docs/index.html") response))))
|
(is (= 200 (:status response)))))
|
||||||
|
|
||||||
(testing "not found"
|
(testing "not found"
|
||||||
(let [response (app (request "/not-found"))]
|
(let [response (app (request "/not-found"))]
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@
|
||||||
[reitit.ring.coercion]
|
[reitit.ring.coercion]
|
||||||
[reitit.ring.middleware.exception :as exception]
|
[reitit.ring.middleware.exception :as exception]
|
||||||
[ring.util.http-response :as http-response])
|
[ring.util.http-response :as http-response])
|
||||||
(:import (java.sql SQLException SQLWarning)))
|
(:import (clojure.lang ExceptionInfo)
|
||||||
|
(java.sql SQLException SQLWarning)))
|
||||||
|
|
||||||
(derive ::kikka ::kukka)
|
(derive ::kikka ::kukka)
|
||||||
|
|
||||||
|
|
@ -190,3 +191,15 @@
|
||||||
(is (contains? problems ::s/spec))
|
(is (contains? problems ::s/spec))
|
||||||
(is (contains? problems ::s/value))
|
(is (contains? problems ::s/value))
|
||||||
(is (contains? problems ::s/problems))))))))
|
(is (contains? problems ::s/problems))))))))
|
||||||
|
|
||||||
|
(deftest response-keys-test
|
||||||
|
(is (thrown-with-msg?
|
||||||
|
ExceptionInfo
|
||||||
|
#"Response status must be int"
|
||||||
|
(ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
[["/coercion"
|
||||||
|
{:middleware [reitit.ring.coercion/coerce-response-middleware]
|
||||||
|
:coercion reitit.coercion.spec/coercion
|
||||||
|
:responses {:200 {:body {:total pos-int?}}}
|
||||||
|
:handler identity}]])))))
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,36 @@
|
||||||
(let [m (r/match-by-path r "/none/kikka/abba")]
|
(let [m (r/match-by-path r "/none/kikka/abba")]
|
||||||
(is (= nil (coercion/coerce! m))))))))
|
(is (= nil (coercion/coerce! m))))))))
|
||||||
|
|
||||||
|
(deftest malli-query-parameter-coercion-test
|
||||||
|
(let [router (fn [coercion]
|
||||||
|
(r/router ["/test"
|
||||||
|
{:coercion coercion
|
||||||
|
:parameters {:query [:map
|
||||||
|
[:a [:string {:default "a"}]]
|
||||||
|
[:x {:optional true} [:keyword {:default :a}]]]}}]
|
||||||
|
{:compile coercion/compile-request-coercers}))]
|
||||||
|
(testing "default values for :optional query keys do not get added"
|
||||||
|
(is (= {:query {:a "a"}}
|
||||||
|
(-> (r/match-by-path (router reitit.coercion.malli/coercion) "/test")
|
||||||
|
(assoc :query-params {})
|
||||||
|
(coercion/coerce!)))))
|
||||||
|
(testing "default values for :optional query keys get added when :malli.transform/add-optional-keys is set"
|
||||||
|
(is (= {:query {:a "a" :x :a}}
|
||||||
|
(-> (r/match-by-path (router (reitit.coercion.malli/create
|
||||||
|
(assoc reitit.coercion.malli/default-options
|
||||||
|
:default-values {:malli.transform/add-optional-keys true}))) "/test")
|
||||||
|
(assoc :query-params {})
|
||||||
|
(coercion/coerce!)))))
|
||||||
|
(testing "default values can be disabled"
|
||||||
|
(is (thrown-with-msg?
|
||||||
|
ExceptionInfo
|
||||||
|
#"Request coercion failed"
|
||||||
|
(-> (r/match-by-path (router (reitit.coercion.malli/create
|
||||||
|
(assoc reitit.coercion.malli/default-options
|
||||||
|
:default-values false))) "/test")
|
||||||
|
(assoc :query-params {})
|
||||||
|
(coercion/coerce!)))))))
|
||||||
|
|
||||||
(defn match-by-path-and-coerce! [router path]
|
(defn match-by-path-and-coerce! [router path]
|
||||||
(if-let [match (r/match-by-path router path)]
|
(if-let [match (r/match-by-path router path)]
|
||||||
(assoc match :parameters (coercion/coerce! match))))
|
(assoc match :parameters (coercion/coerce! match))))
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,7 @@
|
||||||
|
|
||||||
(testing "routers handling wildcard paths"
|
(testing "routers handling wildcard paths"
|
||||||
(are [r name]
|
(are [r name]
|
||||||
(testing "wild"
|
(testing (str name)
|
||||||
|
|
||||||
(testing "simple"
|
(testing "simple"
|
||||||
(let [router (r/router ["/api" ["/ipa" ["/:size" ::beer]]] {:router r})]
|
(let [router (r/router ["/api" ["/ipa" ["/:size" ::beer]]] {:router r})]
|
||||||
(is (= name (r/router-name router)))
|
(is (= name (r/router-name router)))
|
||||||
|
|
@ -52,10 +51,17 @@
|
||||||
:path-params nil})
|
:path-params nil})
|
||||||
(r/match-by-name router ::beer)))
|
(r/match-by-name router ::beer)))
|
||||||
(is (r/partial-match? (r/match-by-name router ::beer)))
|
(is (r/partial-match? (r/match-by-name router ::beer)))
|
||||||
|
(is (r/partial-match? (r/match-by-name router ::beer {:size nil}))
|
||||||
|
"nil counts as missing")
|
||||||
(is (thrown-with-msg?
|
(is (thrown-with-msg?
|
||||||
ExceptionInfo
|
ExceptionInfo
|
||||||
#"^missing path-params for route /api/ipa/:size -> \#\{:size\}$"
|
#"^missing path-params for route /api/ipa/:size -> \#\{:size\}$"
|
||||||
(r/match-by-name! router ::beer))))))
|
(r/match-by-name! router ::beer)))
|
||||||
|
(is (thrown-with-msg?
|
||||||
|
ExceptionInfo
|
||||||
|
#"^missing path-params for route /api/ipa/:size -> \#\{:size\}$"
|
||||||
|
(r/match-by-name! router ::beer {:size nil}))
|
||||||
|
"nil counts as missing"))))
|
||||||
|
|
||||||
(testing "decode %-encoded path params"
|
(testing "decode %-encoded path params"
|
||||||
(let [router (r/router [["/one-param-path/:param1" ::one]
|
(let [router (r/router [["/one-param-path/:param1" ::one]
|
||||||
|
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
(ns reitit.dependency-test
|
|
||||||
(:require [clojure.test :refer [are deftest is testing]]
|
|
||||||
[reitit.dependency :as rc])
|
|
||||||
#?(:clj (:import [clojure.lang ExceptionInfo])))
|
|
||||||
|
|
||||||
(deftest post-order-test
|
|
||||||
(let [base-middlewares [{:name ::bar, :provides #{:bar}, :requires #{:foo}, :wrap identity}
|
|
||||||
{:name ::baz, :provides #{:baz}, :requires #{:bar :foo}, :wrap identity}
|
|
||||||
{:name ::foo, :provides #{:foo}, :requires #{}, :wrap identity}]]
|
|
||||||
(testing "happy cases"
|
|
||||||
(testing "default ordering works"
|
|
||||||
(is (= (rc/post-order base-middlewares)
|
|
||||||
(into (vec (drop 2 base-middlewares)) (take 2 base-middlewares)))))
|
|
||||||
|
|
||||||
(testing "custom provides and requires work"
|
|
||||||
(is (= (rc/post-order (comp hash-set :name)
|
|
||||||
(fn [node] (into #{} (map (fn [k] (keyword "reitit.dependency-test" (name k))))
|
|
||||||
(:requires node)))
|
|
||||||
base-middlewares)
|
|
||||||
(into (vec (drop 2 base-middlewares)) (take 2 base-middlewares))))))
|
|
||||||
|
|
||||||
(testing "errors"
|
|
||||||
(testing "missing dependency detection"
|
|
||||||
(is (thrown-with-msg? ExceptionInfo #"missing"
|
|
||||||
(rc/post-order (drop 1 base-middlewares)))))
|
|
||||||
|
|
||||||
(testing "ambiguous dependency detection"
|
|
||||||
(is (thrown-with-msg? ExceptionInfo #"multiple providers"
|
|
||||||
(rc/post-order (update-in base-middlewares [0 :provides] conj :foo)))))
|
|
||||||
|
|
||||||
(testing "circular dependency detection"
|
|
||||||
(is (thrown-with-msg? ExceptionInfo #"circular"
|
|
||||||
(rc/post-order (assoc-in base-middlewares [2 :requires] #{:baz}))))))))
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
[reitit.swagger-ui :as swagger-ui]
|
[reitit.swagger-ui :as swagger-ui]
|
||||||
[schema.core :as s]
|
[schema.core :as s]
|
||||||
[schema-tools.core]
|
[schema-tools.core]
|
||||||
|
[clojure.spec.alpha :as sp]
|
||||||
[spec-tools.core :as st]
|
[spec-tools.core :as st]
|
||||||
[spec-tools.data-spec :as ds]))
|
[spec-tools.data-spec :as ds]))
|
||||||
|
|
||||||
|
|
@ -47,7 +48,9 @@
|
||||||
["/plus/:z"
|
["/plus/:z"
|
||||||
{:get {:summary "plus"
|
{:get {:summary "plus"
|
||||||
:tags [:plus :spec]
|
:tags [:plus :spec]
|
||||||
:parameters {:query {:x int?, :y int?}
|
:parameters {:query {:x int?, :y (st/spec {:spec int?
|
||||||
|
:description "this is deprecated"
|
||||||
|
:openapi/deprecated true})}
|
||||||
:path {:z int?}}
|
:path {:z int?}}
|
||||||
:openapi {:operationId "spec-plus"
|
:openapi {:operationId "spec-plus"
|
||||||
:deprecated true
|
:deprecated true
|
||||||
|
|
@ -67,7 +70,7 @@
|
||||||
:responses {200 {:description "success"
|
:responses {200 {:description "success"
|
||||||
:body {:total int?}}
|
:body {:total int?}}
|
||||||
500 {:description "fail"}
|
500 {:description "fail"}
|
||||||
504 {:description "default"
|
:default {:description "default"
|
||||||
:content {:default {:schema {:error string?}}}
|
:content {:default {:schema {:error string?}}}
|
||||||
:body {:masked string?}}}
|
:body {:masked string?}}}
|
||||||
:handler (fn [{{{:keys [z]} :path
|
:handler (fn [{{{:keys [z]} :path
|
||||||
|
|
@ -78,7 +81,8 @@
|
||||||
["/plus/*z"
|
["/plus/*z"
|
||||||
{:get {:summary "plus"
|
{:get {:summary "plus"
|
||||||
:tags [:plus :malli]
|
:tags [:plus :malli]
|
||||||
:parameters {:query [:map [:x int?] [:y int?]]
|
:parameters {:query [:map [:x int?] [:y {:json-schema/deprecated true
|
||||||
|
:description "this is deprecated"} int?]]
|
||||||
:path [:map [:z int?]]}
|
:path [:map [:z int?]]}
|
||||||
:openapi {:responses {400 {:description "kosh"
|
:openapi {:responses {400 {:description "kosh"
|
||||||
:content {"application/json" {:schema {:type "string"}}}}}}
|
:content {"application/json" {:schema {:type "string"}}}}}}
|
||||||
|
|
@ -96,7 +100,7 @@
|
||||||
:responses {200 {:description "success"
|
:responses {200 {:description "success"
|
||||||
:body [:map [:total int?]]}
|
:body [:map [:total int?]]}
|
||||||
500 {:description "fail"}
|
500 {:description "fail"}
|
||||||
504 {:description "default"
|
:default {:description "default"
|
||||||
:content {:default {:schema {:error string?}}}
|
:content {:default {:schema {:error string?}}}
|
||||||
:body {:masked string?}}}
|
:body {:masked string?}}}
|
||||||
:handler (fn [{{{:keys [z]} :path
|
:handler (fn [{{{:keys [z]} :path
|
||||||
|
|
@ -107,7 +111,8 @@
|
||||||
["/plus/*z"
|
["/plus/*z"
|
||||||
{:get {:summary "plus"
|
{:get {:summary "plus"
|
||||||
:tags [:plus :schema]
|
:tags [:plus :schema]
|
||||||
:parameters {:query {:x s/Int, :y s/Int}
|
:parameters {:query {:x s/Int, :y (schema-tools.core/schema s/Int {:description "this is deprecated"
|
||||||
|
:openapi/deprecated true})}
|
||||||
:path {:z s/Int}}
|
:path {:z s/Int}}
|
||||||
:openapi {:responses {400 {:content {"application/json" {:schema {:type "string"}}}
|
:openapi {:responses {400 {:content {"application/json" {:schema {:type "string"}}}
|
||||||
:description "kosh"}}}
|
:description "kosh"}}}
|
||||||
|
|
@ -125,7 +130,7 @@
|
||||||
:responses {200 {:description "success"
|
:responses {200 {:description "success"
|
||||||
:body {:total s/Int}}
|
:body {:total s/Int}}
|
||||||
500 {:description "fail"}
|
500 {:description "fail"}
|
||||||
504 {:description "default"
|
:default {:description "default"
|
||||||
:content {:default {:schema {:error s/Str}}}
|
:content {:default {:schema {:error s/Str}}}
|
||||||
:body {:masked s/Str}}}
|
:body {:masked s/Str}}}
|
||||||
:handler (fn [{{{:keys [z]} :path
|
:handler (fn [{{{:keys [z]} :path
|
||||||
|
|
@ -163,6 +168,8 @@
|
||||||
:format "int64"}}
|
:format "int64"}}
|
||||||
{:in "query"
|
{:in "query"
|
||||||
:name "y"
|
:name "y"
|
||||||
|
:description "this is deprecated"
|
||||||
|
:deprecated true
|
||||||
:required true
|
:required true
|
||||||
:schema {:type "integer"
|
:schema {:type "integer"
|
||||||
:format "int64"}}
|
:format "int64"}}
|
||||||
|
|
@ -200,7 +207,7 @@
|
||||||
400 {:content {"application/json" {:schema {:type "string"}}}
|
400 {:content {"application/json" {:schema {:type "string"}}}
|
||||||
:description "kosh"}
|
:description "kosh"}
|
||||||
500 {:description "fail"}
|
500 {:description "fail"}
|
||||||
504 {:description "default"
|
:default {:description "default"
|
||||||
:content {"application/json" {:schema {:properties {"error" {:type "string"}}
|
:content {"application/json" {:schema {:properties {"error" {:type "string"}}
|
||||||
:required ["error"]
|
:required ["error"]
|
||||||
:type "object"}}}}}
|
:type "object"}}}}}
|
||||||
|
|
@ -212,6 +219,8 @@
|
||||||
{:in "query"
|
{:in "query"
|
||||||
:name :y
|
:name :y
|
||||||
:required true
|
:required true
|
||||||
|
:description "this is deprecated"
|
||||||
|
:deprecated true
|
||||||
:schema {:type "integer"}}
|
:schema {:type "integer"}}
|
||||||
{:in "path"
|
{:in "path"
|
||||||
:name :z
|
:name :z
|
||||||
|
|
@ -242,7 +251,7 @@
|
||||||
400 {:description "kosh"
|
400 {:description "kosh"
|
||||||
:content {"application/json" {:schema {:type "string"}}}}
|
:content {"application/json" {:schema {:type "string"}}}}
|
||||||
500 {:description "fail"}
|
500 {:description "fail"}
|
||||||
504 {:description "default"
|
:default {:description "default"
|
||||||
:content {"application/json" {:schema {:additionalProperties false
|
:content {"application/json" {:schema {:additionalProperties false
|
||||||
:properties {:error {:type "string"}}
|
:properties {:error {:type "string"}}
|
||||||
:required [:error]
|
:required [:error]
|
||||||
|
|
@ -256,6 +265,8 @@
|
||||||
{:in "query"
|
{:in "query"
|
||||||
:name "y"
|
:name "y"
|
||||||
:required true
|
:required true
|
||||||
|
:description "this is deprecated"
|
||||||
|
:deprecated true
|
||||||
:schema {:type "integer"
|
:schema {:type "integer"
|
||||||
:format "int32"}}
|
:format "int32"}}
|
||||||
{:in "path"
|
{:in "path"
|
||||||
|
|
@ -292,7 +303,7 @@
|
||||||
400 {:description "kosh"
|
400 {:description "kosh"
|
||||||
:content {"application/json" {:schema {:type "string"}}}}
|
:content {"application/json" {:schema {:type "string"}}}}
|
||||||
500 {:description "fail"}
|
500 {:description "fail"}
|
||||||
504 {:description "default"
|
:default {:description "default"
|
||||||
:content {"application/json" {:schema {:additionalProperties false
|
:content {"application/json" {:schema {:additionalProperties false
|
||||||
:properties {"error" {:type "string"}}
|
:properties {"error" {:type "string"}}
|
||||||
:required ["error"]
|
:required ["error"]
|
||||||
|
|
@ -337,7 +348,7 @@
|
||||||
{:path "/"
|
{:path "/"
|
||||||
:url "/openapi.json"
|
:url "/openapi.json"
|
||||||
:config {:jsonEditor true}})]
|
:config {:jsonEditor true}})]
|
||||||
(is (= 302 (:status (app {:request-method :get, :uri "/"}))))
|
(is (= 200 (:status (app {:request-method :get, :uri "/"}))))
|
||||||
(is (= 200 (:status (app {:request-method :get, :uri "/index.html"}))))
|
(is (= 200 (:status (app {:request-method :get, :uri "/index.html"}))))
|
||||||
(is (= {:jsonEditor true, :url "/openapi.json"}
|
(is (= {:jsonEditor true, :url "/openapi.json"}
|
||||||
(->> {:request-method :get, :uri "/config.json"}
|
(->> {:request-method :get, :uri "/config.json"}
|
||||||
|
|
@ -891,13 +902,11 @@
|
||||||
:request {:description "body description"
|
:request {:description "body description"
|
||||||
:content {"application/json" {:schema {:x int?, :y int?}
|
:content {"application/json" {:schema {:x int?, :y int?}
|
||||||
:examples {"1+1" {:value {:x 1, :y 1}}
|
:examples {"1+1" {:value {:x 1, :y 1}}
|
||||||
"1+2" {:value {:x 1, :y 2}}}
|
"1+2" {:value {:x 1, :y 2}}}}}}
|
||||||
:openapi {:example {:x 2, :y 2}}}}}
|
|
||||||
:responses {200 {:description "success"
|
:responses {200 {:description "success"
|
||||||
:content {"application/json" {:schema {:total int?}
|
:content {"application/json" {:schema {:total int?}
|
||||||
:examples {"2" {:value {:total 2}}
|
:examples {"2" {:value {:total 2}}
|
||||||
"3" {:value {:total 3}}}
|
"3" {:value {:total 3}}}}}}}
|
||||||
:openapi {:example {:total 4}}}}}}
|
|
||||||
:handler (fn [request]
|
:handler (fn [request]
|
||||||
(let [{:keys [x y]} (-> request :parameters :body)]
|
(let [{:keys [x y]} (-> request :parameters :body)]
|
||||||
{:status 200, :body {:total (+ x y)}}))}}]]]
|
{:status 200, :body {:total (+ x y)}}))}}]]]
|
||||||
|
|
@ -915,16 +924,14 @@
|
||||||
:required [:x :y],
|
:required [:x :y],
|
||||||
:additionalProperties false},
|
:additionalProperties false},
|
||||||
:examples {"1+1" {:value {:x 1, :y 1}}
|
:examples {"1+1" {:value {:x 1, :y 1}}
|
||||||
"1+2" {:value {:x 1, :y 2}}},
|
"1+2" {:value {:x 1, :y 2}}}}}},
|
||||||
:example {:x 2, :y 2}}}},
|
|
||||||
:responses {200 {:description "success",
|
:responses {200 {:description "success",
|
||||||
:content {"application/json" {:schema {:type "object",
|
:content {"application/json" {:schema {:type "object",
|
||||||
:properties {:total {:type "integer"}},
|
:properties {:total {:type "integer"}},
|
||||||
:required [:total],
|
:required [:total],
|
||||||
:additionalProperties false},
|
:additionalProperties false},
|
||||||
:examples {"2" {:value {:total 2}},
|
:examples {"2" {:value {:total 2}},
|
||||||
"3" {:value {:total 3}}},
|
"3" {:value {:total 3}}}}}}},
|
||||||
:example {:total 4}}}}},
|
|
||||||
:summary "plus with body"}}}
|
:summary "plus with body"}}}
|
||||||
(:paths spec)))
|
(:paths spec)))
|
||||||
(is (nil? (validate spec))))
|
(is (nil? (validate spec))))
|
||||||
|
|
@ -999,7 +1006,7 @@
|
||||||
{:content
|
{:content
|
||||||
{"application/json"
|
{"application/json"
|
||||||
{:schema
|
{:schema
|
||||||
{:$ref "#/components/schemas/reitit.openapi-test~1Plus"}}}}}}
|
{:$ref "#/components/schemas/reitit.openapi-test.Plus"}}}}}}
|
||||||
"/get"
|
"/get"
|
||||||
{:get
|
{:get
|
||||||
{:parameters
|
{:parameters
|
||||||
|
|
@ -1009,19 +1016,48 @@
|
||||||
{:in "query"
|
{:in "query"
|
||||||
:name :y
|
:name :y
|
||||||
:required true
|
:required true
|
||||||
:schema {:$ref "#/components/schemas/reitit.openapi-test~1Y"}}]}}}
|
:schema {:$ref "#/components/schemas/reitit.openapi-test.Y"}}]}}}
|
||||||
:components
|
:components
|
||||||
{:schemas
|
{:schemas
|
||||||
{"reitit.openapi-test/Plus"
|
{"reitit.openapi-test.Plus"
|
||||||
{:type "object"
|
{:type "object"
|
||||||
:properties
|
:properties
|
||||||
{:x {:type "integer"}
|
{:x {:type "integer"}
|
||||||
:y {:$ref "#/components/schemas/reitit.openapi-test~1Y"}}
|
:y {:$ref "#/components/schemas/reitit.openapi-test.Y"}}
|
||||||
:required [:x :y]}
|
:required [:x :y]}
|
||||||
"reitit.openapi-test/Y" {:type "integer"}}}}
|
"reitit.openapi-test.Y" {:type "integer"}}}}
|
||||||
spec))
|
spec))
|
||||||
;; TODO: the OAS 3.1 json schema disallows "/" in :components :schemas keys,
|
|
||||||
;; even though the text of the spec allows it. See:
|
|
||||||
;; https://github.com/seriousme/openapi-schema-validator/blob/772375bf4895f0e641d103c27140cdd1d2afc34e/schemas/v3.1/schema.json#L282
|
|
||||||
#_
|
|
||||||
(is (nil? (validate spec))))))
|
(is (nil? (validate spec))))))
|
||||||
|
|
||||||
|
(sp/def ::address string?)
|
||||||
|
(sp/def ::zip int?)
|
||||||
|
(sp/def ::city string?)
|
||||||
|
(sp/def ::street string?)
|
||||||
|
(sp/def ::or-and-schema (sp/keys :req-un [(or (and ::address ::zip) (and ::city ::street))]))
|
||||||
|
|
||||||
|
(deftest openapi-spec-tests
|
||||||
|
(testing "s/keys + or maps to :anyOf"
|
||||||
|
(let [app (ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
[["/openapi.json"
|
||||||
|
{:get {:no-doc true
|
||||||
|
:openapi {:info {:title "" :version "0.0.1"}}
|
||||||
|
:handler (openapi/create-openapi-handler)}}]
|
||||||
|
|
||||||
|
["/spec" {:coercion spec/coercion
|
||||||
|
:post {:summary "or-and-schema"
|
||||||
|
:request {:content {"application/json" {:schema ::or-and-schema}}}
|
||||||
|
:handler identity}}]]
|
||||||
|
{:validate reitit.ring.spec/validate
|
||||||
|
:data {:middleware [openapi/openapi-feature]}}))
|
||||||
|
spec (:body (app {:request-method :get :uri "/openapi.json"}))]
|
||||||
|
(is (nil? (validate spec)))
|
||||||
|
(is (= {:title "reitit.openapi-test/or-and-schema"
|
||||||
|
:type "object"
|
||||||
|
:properties {"address" {:type "string"}
|
||||||
|
"zip" {:type "integer" :format "int64"}
|
||||||
|
"city" {:type "string"}
|
||||||
|
"street" {:type "string"}}
|
||||||
|
:anyOf [{:required ["address" "zip"]}
|
||||||
|
{:required ["city" "street"]}]}
|
||||||
|
(get-in spec [:paths "/spec" :post :requestBody :content "application/json" :schema]))))))
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
(ns reitit.ring-coercion-test
|
(ns reitit.ring-coercion-test
|
||||||
(:require [clojure.test :refer [deftest is testing]]
|
(:require [clojure.test :refer [deftest is testing]]
|
||||||
[malli.experimental.lite :as l]
|
[malli.experimental.lite :as l]
|
||||||
#?@(:clj [[muuntaja.middleware]
|
#?@(:clj [[muuntaja.core]
|
||||||
[jsonista.core :as j]])
|
[muuntaja.middleware]
|
||||||
|
[jsonista.core :as j]
|
||||||
|
[reitit.ring.middleware.muuntaja]])
|
||||||
[malli.core :as m]
|
[malli.core :as m]
|
||||||
[malli.util :as mu]
|
[malli.util :as mu]
|
||||||
[meta-merge.core :refer [meta-merge]]
|
[meta-merge.core :refer [meta-merge]]
|
||||||
|
|
@ -581,10 +583,45 @@
|
||||||
:request-method :get}))]
|
:request-method :get}))]
|
||||||
|
|
||||||
(is (= {:status 200, :body {:total "FOO: this, BAR: that"}} (call m/schema custom-meta-merge-checking-schema)))
|
(is (= {:status 200, :body {:total "FOO: this, BAR: that"}} (call m/schema custom-meta-merge-checking-schema)))
|
||||||
(is (= {:status 200, :body {:total "FOO: this, BAR: that"}} (call identity custom-meta-merge-checking-parameters)))))))
|
(is (= {:status 200, :body {:total "FOO: this, BAR: that"}} (call identity custom-meta-merge-checking-parameters)))))
|
||||||
|
|
||||||
|
(testing "malli options"
|
||||||
|
(let [->app (fn [options]
|
||||||
|
(ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
["/api" {:get {:parameters {:body [:map
|
||||||
|
[:i :int]
|
||||||
|
[:x :string]]}
|
||||||
|
:handler (fn [{{:keys [body]} :parameters}]
|
||||||
|
{:status 200 :body body})}}]
|
||||||
|
{:data {:middleware [rrc/coerce-exceptions-middleware
|
||||||
|
rrc/coerce-request-middleware
|
||||||
|
rrc/coerce-response-middleware]
|
||||||
|
:coercion (malli/create options)}})))
|
||||||
|
request {:uri "/api"
|
||||||
|
:request-method :get
|
||||||
|
:muuntaja/request {:format "application/json"}}]
|
||||||
|
(testing "humanize options"
|
||||||
|
(is (= {:i ["should be an integer"] :x ["missing required key"]}
|
||||||
|
(-> ((->app nil) (assoc request :body-params {:i "x"}))
|
||||||
|
:body
|
||||||
|
:humanized)))
|
||||||
|
(is (= {:i ["SHOULD INT"] :x ["MISSING"]}
|
||||||
|
(-> ((->app {:options {:errors {:int {:error/message {:en "SHOULD INT"}}
|
||||||
|
:malli.core/missing-key {:error/message {:en "MISSING"}}}}})
|
||||||
|
(assoc request :body-params {:i "x"}))
|
||||||
|
:body
|
||||||
|
:humanized))))
|
||||||
|
(testing "overriding registry"
|
||||||
|
(is (= {:body {:i "x" :x "x"} :status 200}
|
||||||
|
(-> ((->app {:options {:registry (merge (m/default-schemas)
|
||||||
|
{:int :string})}})
|
||||||
|
(assoc request :body-params {:i "x" :x "x"}))))))))))
|
||||||
|
|
||||||
#?(:clj
|
#?(:clj
|
||||||
(deftest per-content-type-test
|
(deftest per-content-type-test
|
||||||
|
(let [normalize-json (fn [resp]
|
||||||
|
(update resp :body #(-> % j/write-value-as-string (j/read-value j/keyword-keys-object-mapper))))]
|
||||||
(doseq [[coercion json-request edn-request default-request json-response edn-response default-response]
|
(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 :json]] [:response any?]]
|
||||||
|
|
@ -654,9 +691,7 @@
|
||||||
:uri "/foo"
|
:uri "/foo"
|
||||||
:muuntaja/request {:format request-format}
|
:muuntaja/request {:format request-format}
|
||||||
:muuntaja/response {:format response-format}
|
:muuntaja/response {:format response-format}
|
||||||
:body-params body})
|
:body-params body})]
|
||||||
normalize-json (fn[body]
|
|
||||||
(-> body j/write-value-as-string (j/read-value j/keyword-keys-object-mapper)))]
|
|
||||||
(testing "succesful call"
|
(testing "succesful call"
|
||||||
(is (= {:status 200 :body {:request "json", :response "json"}}
|
(is (= {:status 200 :body {:request "json", :response "json"}}
|
||||||
(normalize-json (call (request "application/json" "application/json" {:request :json :response :json})))))
|
(normalize-json (call (request "application/json" "application/json" {:request :json :response :json})))))
|
||||||
|
|
@ -677,9 +712,128 @@
|
||||||
(is (= {:type :reitit.coercion/response-coercion :in [:response :body]}
|
(is (= {:type :reitit.coercion/response-coercion :in [:response :body]}
|
||||||
(call (request "application/json" "application/edn" {:request :json :response :json}))))
|
(call (request "application/json" "application/edn" {:request :json :response :json}))))
|
||||||
(is (= {:type :reitit.coercion/response-coercion :in [:response :body]}
|
(is (= {:type :reitit.coercion/response-coercion :in [:response :body]}
|
||||||
(call (request "application/json" "application/transit" {:request :json :response :json}))))))))))))
|
(call (request "application/json" "application/transit" {:request :json :response :json}))))))))
|
||||||
|
(testing "explicit response content type"
|
||||||
|
(let [response (atom nil)
|
||||||
|
app (ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
["/foo" {:post {:responses {200 {:content {"application/json" {:schema json-response}
|
||||||
|
"application/edn" {:schema edn-response}
|
||||||
|
:default {:schema default-response}}}}
|
||||||
|
:handler (fn [req]
|
||||||
|
@response)}}]
|
||||||
|
{: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
|
||||||
|
#_(ex-data e)
|
||||||
|
(select-keys (ex-data e) [:type :in]))))
|
||||||
|
request (fn [request-format body resp]
|
||||||
|
(reset! response resp)
|
||||||
|
{:request-method :post
|
||||||
|
:uri "/foo"
|
||||||
|
:muuntaja/request {:format request-format}
|
||||||
|
:body-params body})]
|
||||||
|
(testing "via :muuntaja/content-type"
|
||||||
|
(is (= {:status 200 :body {:request "json" :response "json"} :muuntaja/content-type "application/json"}
|
||||||
|
(normalize-json (call (request "application/json" {:request :json :response :json} {:status 200 :body {:request :json :response :json} :muuntaja/content-type "application/json"}))))
|
||||||
|
"valid reponse")
|
||||||
|
(is (= {:in [:response :body] :type :reitit.coercion/response-coercion}
|
||||||
|
(call (request "application/json" {:request :json :response :json} {:status 200 :body {:request :json :response :invalid} :muuntaja/content-type "application/json"})))
|
||||||
|
"invalid reponse")))))))))
|
||||||
|
|
||||||
|
|
||||||
|
#?(:clj
|
||||||
|
(deftest response-coercion-test
|
||||||
|
(doseq [[coercion schema-200 schema-default]
|
||||||
|
[[malli/coercion
|
||||||
|
[:map [:a :int]]
|
||||||
|
[:map [:b :int]]]
|
||||||
|
[schema/coercion
|
||||||
|
{:a s/Int}
|
||||||
|
{:b s/Int}]
|
||||||
|
[spec/coercion
|
||||||
|
{:a int?}
|
||||||
|
{:b int?}]]]
|
||||||
|
(testing (str coercion)
|
||||||
|
(let [app (ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
[["/foo" {:post {:responses {200 {:content {:default {:schema schema-200}}}
|
||||||
|
201 {:content {"application/edn" {:schema schema-200}}}
|
||||||
|
202 {:description "status code and content-type explicitly mentioned, but no :schema"
|
||||||
|
:content {"application/edn" {}
|
||||||
|
"application/json" {}}}
|
||||||
|
:default {:content {"application/json" {:schema schema-default}}}}
|
||||||
|
:handler (fn [req]
|
||||||
|
{:status (-> req :body-params :status)
|
||||||
|
:body (-> req :body-params :response)})}}]
|
||||||
|
["/bar" {:post {:responses {200 {:content {:default {:schema schema-200}}}}
|
||||||
|
:handler (fn [req]
|
||||||
|
{:status (-> req :body-params :status)
|
||||||
|
:body (-> req :body-params :response)})}}]
|
||||||
|
["/quux" {:post {:handler (fn [req]
|
||||||
|
{:status (-> req :body-params :status)
|
||||||
|
:body (-> req :body-params :response)})}}]]
|
||||||
|
{: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 [uri body]
|
||||||
|
{:request-method :post
|
||||||
|
:uri uri
|
||||||
|
:muuntaja/request {:format "application/json"}
|
||||||
|
:muuntaja/response {:format (:format body "application/json")}
|
||||||
|
:body-params body})]
|
||||||
|
(testing "explicit response schema"
|
||||||
|
(is (= {:status 200 :body {:a 1}}
|
||||||
|
(call (request "/foo" {:status 200 :response {:a 1}})))
|
||||||
|
"valid response")
|
||||||
|
(is (= {:type :reitit.coercion/response-coercion, :in [:response :body]}
|
||||||
|
(call (request "/foo" {:status 200 :response {:b 1}})))
|
||||||
|
"invalid response")
|
||||||
|
(is (= {:type :reitit.coercion/response-coercion, :in [:response :body]}
|
||||||
|
(call (request "/foo" {:status 200 :response {:b 1} :format "application/edn"})))
|
||||||
|
"invalid response, different content-type"))
|
||||||
|
(testing "explicit response schema, but for the wrong content-type"
|
||||||
|
(is (= {:status 201 :body "anything goes!"}
|
||||||
|
(call (request "/foo" {:status 201 :response "anything goes!"})))
|
||||||
|
"no coercion applied"))
|
||||||
|
(testing "response config without :schema"
|
||||||
|
(is (= {:status 202 :body "anything goes!"}
|
||||||
|
(call (request "/foo" {:status 202 :response "anything goes!"})))
|
||||||
|
"no coercion applied"))
|
||||||
|
(testing "default response schema"
|
||||||
|
(is (= {:status 300 :body {:b 2}}
|
||||||
|
(call (request "/foo" {:status 300 :response {:b 2}})))
|
||||||
|
"valid response")
|
||||||
|
(is (= {:type :reitit.coercion/response-coercion, :in [:response :body]}
|
||||||
|
(call (request "/foo" {:status 300 :response {:a 2}})))
|
||||||
|
"invalid response")
|
||||||
|
(is (= {:status 300 :body "anything goes!"}
|
||||||
|
(call (request "/foo" {:status 300 :response "anything goes!" :format "application/edn"})))
|
||||||
|
"no coercion applied due to content-type"))
|
||||||
|
(testing "no default"
|
||||||
|
(is (= {:status 200 :body {:a 1}}
|
||||||
|
(call (request "/bar" {:status 200 :response {:a 1}})))
|
||||||
|
"valid response")
|
||||||
|
(testing "unlisted response code"
|
||||||
|
(is (= {:status 202 :body "anything goes!"}
|
||||||
|
(call (request "/bar" {:status 202 :response "anything goes!"})))
|
||||||
|
"no coercion applied")))
|
||||||
|
(testing "no response coercion"
|
||||||
|
(is (= {:status 200 :body "anything goes!"}
|
||||||
|
(call (request "/quux" {:status 200 :response "anything goes!"})))
|
||||||
|
"no coercion applied")))))))
|
||||||
|
|
||||||
#?(:clj
|
#?(:clj
|
||||||
(deftest muuntaja-test
|
(deftest muuntaja-test
|
||||||
(let [app (ring/ring-handler
|
(let [app (ring/ring-handler
|
||||||
|
|
@ -714,3 +868,100 @@
|
||||||
(app) :body slurp (read-string))]
|
(app) :body slurp (read-string))]
|
||||||
(is (= data-edn (e2e (assoc data-edn :EXTRA "VALUE"))))
|
(is (= data-edn (e2e (assoc data-edn :EXTRA "VALUE"))))
|
||||||
(is (thrown? ExceptionInfo (e2e data-json))))))))
|
(is (thrown? ExceptionInfo (e2e data-json))))))))
|
||||||
|
|
||||||
|
#?(:clj
|
||||||
|
(deftest muuntaja-per-content-type-coercion-test
|
||||||
|
;; Test integration between per-content-type coercion and muuntaja.
|
||||||
|
;; Malli-only for now.
|
||||||
|
(let [response (atom nil)
|
||||||
|
app (ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
["/foo" {:post {:request {:content {"application/json" {:schema [:map [:request [:enum :json]]]}
|
||||||
|
"application/edn" {:schema [:map [:request [:enum :edn]]]}
|
||||||
|
:default {:schema [:map [:request [:enum :default]]]}}}
|
||||||
|
:responses {200 {:content {"application/json" {:schema [:map [:response [:enum :json]]]}
|
||||||
|
"application/edn" {:schema [:map [:response [:enum :edn]]]}
|
||||||
|
:default {}}}}
|
||||||
|
:handler (fn [req] @response)}}]
|
||||||
|
{:data {:middleware [reitit.ring.middleware.muuntaja/format-middleware
|
||||||
|
rrc/coerce-request-middleware
|
||||||
|
rrc/coerce-response-middleware]
|
||||||
|
:muuntaja muuntaja.core/instance
|
||||||
|
:coercion malli/coercion}}))
|
||||||
|
maybe-slurp #(if (instance? java.io.InputStream %)
|
||||||
|
(slurp %)
|
||||||
|
%)
|
||||||
|
call (fn [request resp]
|
||||||
|
(reset! response resp)
|
||||||
|
(try
|
||||||
|
(-> (merge {:request-method :post :uri "/foo"} request)
|
||||||
|
(update :body #(ByteArrayInputStream. (.getBytes % "UTF-8")))
|
||||||
|
(app))
|
||||||
|
(catch ExceptionInfo e
|
||||||
|
#_(ex-data e)
|
||||||
|
(select-keys (ex-data e) [:in :type]))))
|
||||||
|
read-json #(j/read-value % (j/object-mapper {:decode-key-fn true}))
|
||||||
|
json-response? (fn [resp]
|
||||||
|
(and (.startsWith (get-in resp [:headers "Content-Type"]) "application/json") ;; ignore the ;charset=utf-8 part
|
||||||
|
(= {:response "json"} (read-json (maybe-slurp (:body resp))))))
|
||||||
|
edn-response? (fn [resp]
|
||||||
|
(and (.startsWith (get-in resp [:headers "Content-Type"]) "application/edn") ;; ignore the ;charset=utf-8 part
|
||||||
|
(= {:response :edn} (read-string (maybe-slurp (:body resp))))))
|
||||||
|
custom-response? (fn [resp]
|
||||||
|
(and (= (get-in resp [:headers "Content-Type"]) "application/custom")
|
||||||
|
(= "custom data" (maybe-slurp (:body resp)))))]
|
||||||
|
(testing "response content-type defaults to json"
|
||||||
|
(is (json-response?
|
||||||
|
(call {:headers {"content-type" "application/json"}
|
||||||
|
:body (j/write-value-as-string {:request :json})}
|
||||||
|
{:status 200
|
||||||
|
:body {:response :json}})))
|
||||||
|
(is (json-response?
|
||||||
|
(call {:headers {"content-type" "application/edn"}
|
||||||
|
:body (pr-str {:request :edn})}
|
||||||
|
{:status 200
|
||||||
|
:body {:response :json}})))
|
||||||
|
(is (= {:in [:response :body] :type :reitit.coercion/response-coercion}
|
||||||
|
(call {:headers {"content-type" "application/json"}
|
||||||
|
:body (j/write-value-as-string {:request :json})}
|
||||||
|
{:status 200
|
||||||
|
:body {:response :invalid}}))
|
||||||
|
"invalid response"))
|
||||||
|
(testing "response content-type negotiated via accept header"
|
||||||
|
(is (json-response?
|
||||||
|
(call {:headers {"content-type" "application/json" "accept" "application/json"}
|
||||||
|
:body (j/write-value-as-string {:request :json})}
|
||||||
|
{:status 200
|
||||||
|
:body {:response :json}})))
|
||||||
|
(is (edn-response?
|
||||||
|
(call {:headers {"content-type" "application/json" "accept" "application/edn"}
|
||||||
|
:body (j/write-value-as-string {:request :json})}
|
||||||
|
{:status 200
|
||||||
|
:body {:response :edn}})))
|
||||||
|
(is (= {:in [:response :body] :type :reitit.coercion/response-coercion}
|
||||||
|
(call {:headers {"content-type" "application/json" "accept" "application/edn"}
|
||||||
|
:body (j/write-value-as-string {:request :json})}
|
||||||
|
{:status 200
|
||||||
|
:body {:response :invalid}}))
|
||||||
|
"invalid response"))
|
||||||
|
(testing "response content-type set via :muuntaja/content-type"
|
||||||
|
(is (edn-response?
|
||||||
|
(call {:headers {"content-type" "application/json" "accept" "application/json"}
|
||||||
|
:body (j/write-value-as-string {:request :json})}
|
||||||
|
{:status 200
|
||||||
|
:muuntaja/content-type "application/edn"
|
||||||
|
:body {:response :edn}})))
|
||||||
|
(is (= {:in [:response :body] :type :reitit.coercion/response-coercion}
|
||||||
|
(call {:headers {"content-type" "application/json" "accept" "application/json"}
|
||||||
|
:body (j/write-value-as-string {:request :json})}
|
||||||
|
{:status 200
|
||||||
|
:muuntaja/content-type "application/edn"
|
||||||
|
:body {:response :invalid}}))
|
||||||
|
"invalid response"))
|
||||||
|
(testing "response content-type set via Content-Type header. muuntaja disabled for response."
|
||||||
|
(is (custom-response?
|
||||||
|
(call {:headers {"content-type" "application/json" "accept" "application/json"}
|
||||||
|
:body (j/write-value-as-string {:request :json})}
|
||||||
|
{:status 200
|
||||||
|
:headers {"Content-Type" "application/custom"}
|
||||||
|
:body "custom data"})))))))
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,9 @@
|
||||||
(s/def ::role #{:admin :user})
|
(s/def ::role #{:admin :user})
|
||||||
(s/def ::roles (s/and (s/coll-of ::role :into #{}) set?))
|
(s/def ::roles (s/and (s/coll-of ::role :into #{}) set?))
|
||||||
|
|
||||||
|
(defmulti my-multi (constantly :default))
|
||||||
|
(defmethod my-multi :default [x] x)
|
||||||
|
|
||||||
(deftest route-data-validation-test
|
(deftest route-data-validation-test
|
||||||
(testing "validation is turned off by default"
|
(testing "validation is turned off by default"
|
||||||
(is (r/router?
|
(is (r/router?
|
||||||
|
|
@ -85,6 +88,12 @@
|
||||||
(ring/router
|
(ring/router
|
||||||
["/api" {:handler identity
|
["/api" {:handler identity
|
||||||
:middleware '()}]
|
:middleware '()}]
|
||||||
|
{:validate rrs/validate}))))
|
||||||
|
|
||||||
|
(testing "handler can be a multimethod"
|
||||||
|
(is (r/router?
|
||||||
|
(ring/router
|
||||||
|
["/api" {:get {:handler my-multi}}]
|
||||||
{:validate rrs/validate})))))
|
{:validate rrs/validate})))))
|
||||||
|
|
||||||
(deftest coercion-spec-test
|
(deftest coercion-spec-test
|
||||||
|
|
@ -125,7 +134,7 @@
|
||||||
(ring/router
|
(ring/router
|
||||||
["/api"
|
["/api"
|
||||||
["/plus/:e"
|
["/plus/:e"
|
||||||
{:get {:responses {"200" {}}
|
{:get {:responses {200 {:description 1}}
|
||||||
:handler identity}}]]
|
:handler identity}}]]
|
||||||
{:data {:middleware [rrc/coerce-exceptions-middleware
|
{:data {:middleware [rrc/coerce-exceptions-middleware
|
||||||
rrc/coerce-request-middleware
|
rrc/coerce-request-middleware
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,25 @@
|
||||||
(is (= {:status 200, :body [:top :api :ok]}
|
(is (= {:status 200, :body [:top :api :ok]}
|
||||||
(app {:uri "/api/get" :request-method :get}))))))
|
(app {:uri "/api/get" :request-method :get}))))))
|
||||||
|
|
||||||
|
(testing "middleware from registry"
|
||||||
|
(let [router (ring/router
|
||||||
|
["/api" {:middleware [:mw-foo]}
|
||||||
|
["/get" {:middleware [[:mw :inner]]
|
||||||
|
:get handler}]]
|
||||||
|
{::middleware/registry {:mw mw
|
||||||
|
:mw-foo #(mw % :foo)}})
|
||||||
|
app (ring/ring-handler router nil {:middleware [[:mw :top]]})]
|
||||||
|
|
||||||
|
(testing "router can be extracted"
|
||||||
|
(is (= router (ring/get-router app))))
|
||||||
|
|
||||||
|
(testing "not found"
|
||||||
|
(is (= nil (app {:uri "/favicon.ico"}))))
|
||||||
|
|
||||||
|
(testing "on match"
|
||||||
|
(is (= {:status 200, :body [:top :foo :inner :ok]}
|
||||||
|
(app {:uri "/api/get" :request-method :get}))))))
|
||||||
|
|
||||||
(testing "named routes"
|
(testing "named routes"
|
||||||
(let [router (ring/router
|
(let [router (ring/router
|
||||||
[["/api"
|
[["/api"
|
||||||
|
|
@ -526,9 +545,9 @@
|
||||||
|
|
||||||
(testing "index-files"
|
(testing "index-files"
|
||||||
(let [response (app (request "/docs"))]
|
(let [response (app (request "/docs"))]
|
||||||
(is (= (redirect "/docs/index.html") response)))
|
(is (= (redirect "/docs/") response)))
|
||||||
(let [response (app (request "/docs/"))]
|
(let [response (app (request "/docs/"))]
|
||||||
(is (= (redirect "/docs/index.html") response))))
|
(is (= 200 (:status response)))))
|
||||||
|
|
||||||
(testing "not found"
|
(testing "not found"
|
||||||
(let [response (app (request "/not-found"))]
|
(let [response (app (request "/not-found"))]
|
||||||
|
|
@ -567,9 +586,9 @@
|
||||||
|
|
||||||
(testing "index-files"
|
(testing "index-files"
|
||||||
(let [response (app (request "/docs"))]
|
(let [response (app (request "/docs"))]
|
||||||
(is (= (redirect "/docs/index.html") response)))
|
(is (= (redirect "/docs/") response)))
|
||||||
(let [response (app (request "/docs/"))]
|
(let [response (app (request "/docs/"))]
|
||||||
(is (= (redirect "/docs/index.html") response))))
|
(is (= 200 (:status response)))))
|
||||||
|
|
||||||
(testing "not found"
|
(testing "not found"
|
||||||
(let [response (app (request "/not-found"))]
|
(let [response (app (request "/not-found"))]
|
||||||
|
|
@ -609,9 +628,9 @@
|
||||||
|
|
||||||
(testing "index-files"
|
(testing "index-files"
|
||||||
(let [response (app (request "/docs"))]
|
(let [response (app (request "/docs"))]
|
||||||
(is (= (redirect "/docs/index.html") response)))
|
(is (= (redirect "/docs/") response)))
|
||||||
(let [response (app (request "/docs/"))]
|
(let [response (app (request "/docs/"))]
|
||||||
(is (= (redirect "/docs/index.html") response))))
|
(is (= 200 (:status response)))))
|
||||||
|
|
||||||
(testing "not found"
|
(testing "not found"
|
||||||
(let [response (app (request "/not-found"))]
|
(let [response (app (request "/not-found"))]
|
||||||
|
|
@ -652,9 +671,9 @@
|
||||||
|
|
||||||
(testing "index-files"
|
(testing "index-files"
|
||||||
(let [response (app (request "/docs"))]
|
(let [response (app (request "/docs"))]
|
||||||
(is (= (redirect "/docs/index.html") response)))
|
(is (= (redirect "/docs/") response)))
|
||||||
(let [response (app (request "/docs/"))]
|
(let [response (app (request "/docs/"))]
|
||||||
(is (= (redirect "/docs/index.html") response))))
|
(is (= 200 (:status response)))))
|
||||||
|
|
||||||
(testing "not found"
|
(testing "not found"
|
||||||
(let [response (app {:uri "/not-found" :request-method :get})]
|
(let [response (app {:uri "/not-found" :request-method :get})]
|
||||||
|
|
@ -671,7 +690,93 @@
|
||||||
(app (request "/hello.xml") respond raise)
|
(app (request "/hello.xml") respond raise)
|
||||||
(is (= "text/xml" (get-in @result [:headers "Content-Type"])))
|
(is (= "text/xml" (get-in @result [:headers "Content-Type"])))
|
||||||
(is (get-in @result [:headers "Last-Modified"]))
|
(is (get-in @result [:headers "Last-Modified"]))
|
||||||
(is (= "<xml><hello>file</hello></xml>\n" (slurp (:body @result))))))))))))))
|
(is (= "<xml><hello>file</hello></xml>\n" (slurp (:body @result)))))))))
|
||||||
|
|
||||||
|
(testing "with index-redirect"
|
||||||
|
(let [app (ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
["/*" (create {:index-redirect? true})])
|
||||||
|
(ring/create-default-handler))]
|
||||||
|
|
||||||
|
(testing "index-files"
|
||||||
|
(let [response (app (request "/docs"))]
|
||||||
|
(is (= (redirect "/docs/index.html") response)))
|
||||||
|
(let [response (app (request "/docs/"))]
|
||||||
|
(is (= (redirect "/docs/index.html") response))))))
|
||||||
|
|
||||||
|
(testing "without index-redirect"
|
||||||
|
(let [app (ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
["/*" (create {:canonicalize-uris? false
|
||||||
|
:index-redirect? false})])
|
||||||
|
(ring/create-default-handler))]
|
||||||
|
|
||||||
|
(testing "index-files"
|
||||||
|
(let [response (app (request "/docs"))]
|
||||||
|
(is (= 404 (:status response))))
|
||||||
|
(let [response (app (request "/docs/"))]
|
||||||
|
(is (= 200 (:status response)))
|
||||||
|
(is (= "<h1>hello</h1>\n" (slurp (:body response))))))))
|
||||||
|
|
||||||
|
(testing "with canonicalize-uris"
|
||||||
|
(let [app (ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
["/*" (create {:canonicalize-uris? true})])
|
||||||
|
(ring/create-default-handler))]
|
||||||
|
|
||||||
|
(testing "index-files"
|
||||||
|
(let [response (app (request "/docs"))]
|
||||||
|
(is (= (redirect "/docs/") response)))
|
||||||
|
(testing "not found if dir doesn't exist"
|
||||||
|
(let [response (app (request "/foobar"))]
|
||||||
|
(is (= 404 (:status response)))))
|
||||||
|
(let [response (app (request "/docs/"))]
|
||||||
|
(is (= 200 (:status response))))
|
||||||
|
(let [response (app (request "/docs/index.html"))]
|
||||||
|
(is (= 200 (:status response)))))))
|
||||||
|
|
||||||
|
(testing "with canonicalize-uris and index-redirect"
|
||||||
|
(let [app (ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
["/*" (create {:canonicalize-uris? true
|
||||||
|
:index-redirect? true})])
|
||||||
|
(ring/create-default-handler))]
|
||||||
|
|
||||||
|
(testing "index-files"
|
||||||
|
(let [response (app (request "/docs"))]
|
||||||
|
(is (= (redirect "/docs/index.html") response)))
|
||||||
|
(let [response (app (request "/docs/"))]
|
||||||
|
(is (= (redirect "/docs/index.html") response))))))
|
||||||
|
|
||||||
|
(testing "without canonicalize-uris"
|
||||||
|
(let [app (ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
["/*" (create {:canonicalize-uris? false
|
||||||
|
:index-redirect? true})])
|
||||||
|
(ring/create-default-handler))]
|
||||||
|
|
||||||
|
(testing "index-files"
|
||||||
|
(let [response (app (request "/docs"))]
|
||||||
|
(is (= 404 (:status response))))
|
||||||
|
(let [response (app (request "/docs/"))]
|
||||||
|
(is (= (redirect "/docs/index.html") response)))
|
||||||
|
(let [response (app (request "/foobar"))]
|
||||||
|
(is (= 404 (:status response)))))))
|
||||||
|
|
||||||
|
(testing "with additional mime types"
|
||||||
|
(let [app (ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
["/*" (create {:mime-types {"webmanifest" "application/manifest+json"}})])
|
||||||
|
(ring/create-default-handler))
|
||||||
|
response (app (request "/site.webmanifest"))]
|
||||||
|
(is (= "application/manifest+json" (get-in response [:headers "Content-Type"])))))
|
||||||
|
(testing "when content type cannot be guessed"
|
||||||
|
(let [app (ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
["/*" (create nil)])
|
||||||
|
(ring/create-default-handler))
|
||||||
|
response (app (request "/site.webmanifest"))]
|
||||||
|
(is (not (contains? (:headers response) "Content-Type"))))))))))
|
||||||
|
|
||||||
#?(:clj
|
#?(:clj
|
||||||
(deftest file-resource-handler-not-found-test
|
(deftest file-resource-handler-not-found-test
|
||||||
|
|
|
||||||
|
|
@ -161,14 +161,14 @@
|
||||||
expected {:x-id #{::math}
|
expected {:x-id #{::math}
|
||||||
:swagger "2.0"
|
:swagger "2.0"
|
||||||
:info {:title "my-api"}
|
:info {:title "my-api"}
|
||||||
:definitions {"reitit.swagger-test/req-key" {:type "string"
|
:definitions {"reitit.swagger-test.req-key" {:type "string"
|
||||||
:x-anyOf [{:type "string"}
|
:x-anyOf [{:type "string"}
|
||||||
{:type "string"}]}
|
{:type "string"}]}
|
||||||
"reitit.swagger-test/req-val" {:type "object"
|
"reitit.swagger-test.req-val" {:type "object"
|
||||||
:x-anyOf [{:type "object"}
|
:x-anyOf [{:type "object"}
|
||||||
{:type "string"}]}
|
{:type "string"}]}
|
||||||
"reitit.swagger-test/resp-map" {:type "object"},
|
"reitit.swagger-test.resp-map" {:type "object"},
|
||||||
"reitit.swagger-test/resp-string" {:type "string"
|
"reitit.swagger-test.resp-string" {:type "string"
|
||||||
:minLength 1}}
|
:minLength 1}}
|
||||||
:paths {"/api/spec/plus/{z}" {:patch {:parameters []
|
:paths {"/api/spec/plus/{z}" {:patch {:parameters []
|
||||||
:summary "patch"
|
:summary "patch"
|
||||||
|
|
@ -287,12 +287,12 @@
|
||||||
:schema
|
:schema
|
||||||
{:type "object"
|
{:type "object"
|
||||||
:additionalProperties
|
:additionalProperties
|
||||||
{:$ref "#/definitions/reitit.swagger-test~1req-val"}}}]
|
{:$ref "#/definitions/reitit.swagger-test.req-val"}}}]
|
||||||
:responses {200
|
:responses {200
|
||||||
{:schema
|
{:schema
|
||||||
{:$ref "#/definitions/reitit.swagger-test~1resp-map"
|
{:$ref "#/definitions/reitit.swagger-test.resp-map"
|
||||||
:x-anyOf [{:$ref "#/definitions/reitit.swagger-test~1resp-map"}
|
:x-anyOf [{:$ref "#/definitions/reitit.swagger-test.resp-map"}
|
||||||
{:$ref "#/definitions/reitit.swagger-test~1resp-string"}]}
|
{:$ref "#/definitions/reitit.swagger-test.resp-string"}]}
|
||||||
:description ""}
|
:description ""}
|
||||||
500 {:description "fail"}}
|
500 {:description "fail"}}
|
||||||
:summary "plus put with definitions"}}
|
:summary "plus put with definitions"}}
|
||||||
|
|
@ -392,7 +392,7 @@
|
||||||
(let [app (swagger-ui/create-swagger-ui-handler
|
(let [app (swagger-ui/create-swagger-ui-handler
|
||||||
{:path "/"
|
{:path "/"
|
||||||
:config {:jsonEditor true}})]
|
:config {:jsonEditor true}})]
|
||||||
(is (= 302 (:status (app {:request-method :get, :uri "/"}))))
|
(is (= 200 (:status (app {:request-method :get, :uri "/"}))))
|
||||||
(is (= 200 (:status (app {:request-method :get, :uri "/index.html"}))))
|
(is (= 200 (:status (app {:request-method :get, :uri "/index.html"}))))
|
||||||
(is (= {:jsonEditor true, :url "/swagger.json"}
|
(is (= {:jsonEditor true, :url "/swagger.json"}
|
||||||
(->> {:request-method :get, :uri "/config.json"}
|
(->> {:request-method :get, :uri "/config.json"}
|
||||||
|
|
@ -532,24 +532,24 @@
|
||||||
{:get {:no-doc true
|
{:get {:no-doc true
|
||||||
:handler (swagger/create-swagger-handler)}}]]))
|
:handler (swagger/create-swagger-handler)}}]]))
|
||||||
spec (:body (app {:request-method :get, :uri "/swagger.json"}))]
|
spec (:body (app {:request-method :get, :uri "/swagger.json"}))]
|
||||||
(is (= {:definitions {"reitit.swagger-test/Plus" {:properties {:x {:$ref "#/definitions/reitit.swagger-test~1X"},
|
(is (= {:definitions {"reitit.swagger-test.Plus" {:properties {:x {:$ref "#/definitions/reitit.swagger-test.X"},
|
||||||
:y {:$ref "#/definitions/reitit.swagger-test~1Y"}},
|
:y {:$ref "#/definitions/reitit.swagger-test.Y"}},
|
||||||
:required [:x :y],
|
:required [:x :y],
|
||||||
:type "object"},
|
:type "object"},
|
||||||
"reitit.swagger-test/X" {:format "int64",
|
"reitit.swagger-test.X" {:format "int64",
|
||||||
:type "integer"},
|
:type "integer"},
|
||||||
"reitit.swagger-test/Y" {:format "int64",
|
"reitit.swagger-test.Y" {:format "int64",
|
||||||
:type "integer"},
|
:type "integer"},
|
||||||
"reitit.swagger-test/Result" {:type "object",
|
"reitit.swagger-test.Result" {:type "object",
|
||||||
:properties {:result {:type "integer", :format "int64"}},
|
:properties {:result {:type "integer", :format "int64"}},
|
||||||
:required [:result]}},
|
:required [:result]}},
|
||||||
:paths {"/post" {:post {:parameters [{:description "",
|
:paths {"/post" {:post {:parameters [{:description "",
|
||||||
:in "body",
|
:in "body",
|
||||||
:name "body",
|
:name "body",
|
||||||
:required true,
|
:required true,
|
||||||
:schema {:$ref "#/definitions/reitit.swagger-test~1Plus"}}]
|
:schema {:$ref "#/definitions/reitit.swagger-test.Plus"}}]
|
||||||
:responses {200 {:description ""
|
:responses {200 {:description ""
|
||||||
:schema {:$ref "#/definitions/reitit.swagger-test~1Result"}}}}}
|
:schema {:$ref "#/definitions/reitit.swagger-test.Result"}}}}}
|
||||||
"/get" {:get {:parameters [{:in "query"
|
"/get" {:get {:parameters [{:in "query"
|
||||||
:name :x
|
:name :x
|
||||||
:description ""
|
:description ""
|
||||||
|
|
@ -563,7 +563,7 @@
|
||||||
:required true
|
:required true
|
||||||
:format "int64"}]
|
:format "int64"}]
|
||||||
:responses {200 {:description ""
|
:responses {200 {:description ""
|
||||||
:schema {:$ref "#/definitions/reitit.swagger-test~1Result"}}}}}}
|
:schema {:$ref "#/definitions/reitit.swagger-test.Result"}}}}}}
|
||||||
:swagger "2.0",
|
:swagger "2.0",
|
||||||
:x-id #{:reitit.swagger/default}}
|
:x-id #{:reitit.swagger/default}}
|
||||||
spec))))
|
spec))))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue