mirror of
https://github.com/metosin/reitit.git
synced 2025-12-18 08:51:12 +00:00
Merge branch 'master' into support-operationid
This commit is contained in:
commit
42e988e518
160 changed files with 3544 additions and 2555 deletions
|
|
@ -1,86 +0,0 @@
|
|||
version: 2.1
|
||||
|
||||
jobs:
|
||||
test-clj:
|
||||
working_directory: ~/test
|
||||
parameters:
|
||||
image-name:
|
||||
type: string
|
||||
docker:
|
||||
- image: << parameters.image-name >>
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- 'v1-clj-{{ checksum "project.clj" }}'
|
||||
- 'v1-clj-'
|
||||
- 'v1-test-'
|
||||
- run:
|
||||
name: Install modules
|
||||
command: ./scripts/lein-modules install
|
||||
- run:
|
||||
name: Run tests
|
||||
command: ./scripts/test.sh clj
|
||||
- run:
|
||||
name: Install curl if missing
|
||||
command: apt update && apt install -y curl
|
||||
- run:
|
||||
name: Verify cljdoc.edn
|
||||
command: curl -fsSL https://raw.githubusercontent.com/cljdoc/cljdoc/master/script/verify-cljdoc-edn | bash -s doc/cljdoc.edn
|
||||
- store_test_results:
|
||||
# path must be a directory under which there a subdirectories that
|
||||
# contain the JUnit XML files.
|
||||
path: ~/test/target/results
|
||||
# - run:
|
||||
# name: Run coverage
|
||||
# command: ./scripts/submit-to-coveralls.sh clj
|
||||
- save_cache:
|
||||
key: 'v1-clj-{{ checksum "project.clj" }}'
|
||||
paths:
|
||||
- ~/.m2
|
||||
- ~/.cljs/.aot_cache
|
||||
|
||||
test-cljs:
|
||||
working_directory: ~/test
|
||||
docker:
|
||||
- image: circleci/clojure:lein-2.8.1-node-browsers
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- 'v1-cljs-{{ checksum "project.clj" }}-{{ checksum "package.json" }}'
|
||||
- 'v1-cljs-'
|
||||
- run:
|
||||
name: Install npm dependencies
|
||||
command: npm install
|
||||
- run:
|
||||
name: Install modules
|
||||
command: ./scripts/lein-modules install
|
||||
- run:
|
||||
name: Run tests
|
||||
command: ./scripts/test.sh cljs
|
||||
- store_test_results:
|
||||
path: ~/test/target/results
|
||||
- save_cache:
|
||||
key: 'v1-cljs-{{ checksum "project.clj" }}-{{ checksum "package.json" }}'
|
||||
paths:
|
||||
- ~/.m2
|
||||
- ~/test/node_modules
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
test-and-build-docs:
|
||||
jobs:
|
||||
- test-clj:
|
||||
name: jdk8
|
||||
image-name: clojure:openjdk-8-lein-2.9.1
|
||||
- test-clj:
|
||||
name: jdk11
|
||||
image-name: clojure:openjdk-11-lein-2.9.1
|
||||
- test-clj:
|
||||
name: jdk13
|
||||
image-name: clojure:openjdk-13-lein-2.9.1
|
||||
- test-clj:
|
||||
name: jdk14
|
||||
image-name: clojure:openjdk-14-lein-2.9.1
|
||||
- test-cljs
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{;;:skip-comments true
|
||||
:lint-as {potemkin/def-derived-map clojure.core/defrecord}
|
||||
:linters {:if {:level :off}
|
||||
:linters {:missing-else-branch {:level :off}
|
||||
:unused-binding {:level :off}
|
||||
:unused-referred-var {:exclude {clojure.test [deftest testing is are]
|
||||
cljs.test [deftest testing is are]}}}}
|
||||
|
|
|
|||
1
.clj-kondo/module_config.edn
Normal file
1
.clj-kondo/module_config.edn
Normal file
|
|
@ -0,0 +1 @@
|
|||
{:config-paths ["../../../.clj-kondo"]}
|
||||
78
.github/workflows/testsuite.yml
vendored
Normal file
78
.github/workflows/testsuite.yml
vendored
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
name: testsuite
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build-clj:
|
||||
strategy:
|
||||
matrix:
|
||||
# Supported Java versions: LTS releases 8 and 11 and the latest release
|
||||
jdk: [8, 11, 15]
|
||||
|
||||
name: Clojure (Java ${{ matrix.jdk }})
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-clj-${{ hashFiles('**/project.clj') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-clj-
|
||||
- name: Setup Java ${{ matrix.jdk }}
|
||||
uses: actions/setup-java@v1.4.3
|
||||
with:
|
||||
java-version: ${{ matrix.jdk }}
|
||||
- name: Setup Clojure
|
||||
uses: DeLaGuardo/setup-clojure@3.1
|
||||
with:
|
||||
lein: 2.9.5
|
||||
- name: Run tests
|
||||
run: ./scripts/test.sh clj
|
||||
|
||||
build-cljs:
|
||||
name: ClojureScript
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.m2/repository
|
||||
**/node_modules
|
||||
key: ${{ runner.os }}-cljs-${{ hashFiles('**/project.clj', '**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cljs-
|
||||
- name: Setup Java 11
|
||||
uses: actions/setup-java@v1.4.3
|
||||
with:
|
||||
java-version: 11
|
||||
- name: Setup Clojure
|
||||
uses: DeLaGuardo/setup-clojure@3.1
|
||||
with:
|
||||
lein: 2.9.5
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2.1.2
|
||||
with:
|
||||
node-version: 12
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
npm ci
|
||||
- name: Install modules
|
||||
run: ./scripts/lein-modules install
|
||||
- name: Run tests
|
||||
run: ./scripts/test.sh cljs
|
||||
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Verify cljdoc.edn
|
||||
run: curl -fsSL https://raw.githubusercontent.com/cljdoc/cljdoc/master/script/verify-cljdoc-edn | bash -s doc/cljdoc.edn
|
||||
3
.lsp/config.edn
Normal file
3
.lsp/config.edn
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{:cljfmt {:indents {for-all [[:inner 0]]
|
||||
are [[:inner 0]]}}
|
||||
:clean {:ns-inner-blocks-indentation :same-line}}
|
||||
148
CHANGELOG.md
148
CHANGELOG.md
|
|
@ -12,8 +12,138 @@ We use [Break Versioning][breakver]. The version numbers follow a `<major>.<mino
|
|||
|
||||
[breakver]: https://github.com/ptaoussanis/encore/blob/master/BREAK-VERSIONING.md
|
||||
|
||||
## 0.5.18 (2022-04-05)
|
||||
|
||||
* FIX [#334](https://github.com/metosin/reitit/pull/334) - Frontend: there is no way to catch the exception if coercion fails (via [#549](https://github.com/metosin/reitit/pull/549))
|
||||
* 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)
|
||||
* Balance parenthesis in docs [#547](https://github.com/metosin/reitit/pull/547)
|
||||
|
||||
## 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)
|
||||
|
||||
|
||||
## 0.5.16 (2022-02-15)
|
||||
|
||||
**[compare](https://github.com/metosin/reitit/compare/0.5.15...0.5.16)**
|
||||
|
||||
* Support for [Malli Lite Syntax](https://github.com/metosin/malli#lite) in coercion (enabled by default):
|
||||
|
||||
```clj
|
||||
["/add/:id" {:post {:parameters {:path {:id int?}
|
||||
:query {:a (l/optional int?)}
|
||||
:body {:id int?
|
||||
:data {:id (l/maybe int?)
|
||||
:orders (l/map-of uuid? {:name string?})}}}
|
||||
:responses {200 {:body {:total pos-int?}}
|
||||
500 {:description "fail"}}}}]
|
||||
```
|
||||
|
||||
* Improved Reitit-frontend function docstrings
|
||||
|
||||
* Updated deps:
|
||||
|
||||
```clj
|
||||
[metosin/ring-swagger-ui "4.3.0"] is available but we use "3.46.0"
|
||||
[metosin/jsonista "0.3.5"] is available but we use "0.3.3"
|
||||
[metosin/malli "0.8.2"] is available but we use "0.5.1"
|
||||
[com.fasterxml.jackson.core/jackson-core "2.13.1"] is available but we use "2.12.4"
|
||||
[com.fasterxml.jackson.core/jackson-databind "2.13.1"] is available but we use "2.12.4"
|
||||
[fipp "0.6.25"] is available but we use "0.6.24"
|
||||
[expound "0.9.0"] is available but we use "0.8.9"
|
||||
[ring/ring-core "1.9.5"] is available but we use "1.9.4"
|
||||
```
|
||||
|
||||
## 0.5.15 (2021-08-05)
|
||||
|
||||
**[compare](https://github.com/metosin/reitit/compare/0.5.14...0.5.15)**
|
||||
|
||||
* recompiled with Java8
|
||||
|
||||
## 0.5.14 (2021-08-03)
|
||||
|
||||
**[compare](https://github.com/metosin/reitit/compare/0.5.13...0.5.14)**
|
||||
|
||||
* updated deps:
|
||||
|
||||
```clj
|
||||
[metosin/ring-swagger-ui "3.46.0"] is available but we use "3.36.0"
|
||||
[metosin/jsonista "0.3.3"] is available but we use "0.3.1"
|
||||
[metosin/malli "0.5.1"] is available but we use "0.3.0"
|
||||
[com.fasterxml.jackson.core/jackson-core "2.12.4"] is available but we use "2.12.1"
|
||||
[com.fasterxml.jackson.core/jackson-databind "2.12.4"] is available but we use "2.12.1"
|
||||
[fipp "0.6.24"] is available but we use "0.6.23"
|
||||
[ring/ring-core "1.9.4"] is available but we use "1.9.1"
|
||||
[io.pedestal/pedestal.service "0.5.9"] is available but we use "0.5.8"
|
||||
```
|
||||
|
||||
### `reitit-ring`
|
||||
|
||||
* Fixes `reitit.ring/create-resource-handler` and `reitit.ring/create-file-handler` to support URL-escaped characters. [#484](https://github.com/metosin/reitit/issues/484). PR [#489](https://github.com/metosin/reitit/pull/489).
|
||||
|
||||
### `reitit-malli`
|
||||
|
||||
* FIX: Malli response coercision seems to do nothing at all [#498](https://github.com/metosin/reitit/pull/501)
|
||||
|
||||
### `reitit-pedestal`
|
||||
|
||||
* Enrich request for pedestal/routing-interceptor default-queue [#495](https://github.com/metosin/reitit/pull/495)
|
||||
|
||||
## 0.5.13 (2021-04-23)
|
||||
|
||||
**[compare](https://github.com/metosin/reitit/compare/0.5.12...0.5.13)**
|
||||
|
||||
* updated deps:
|
||||
|
||||
```clj
|
||||
[metosin/malli "0.3.0"] is available but we use "0.2.1"
|
||||
[metosin/schema-tools "0.12.3"] is available but we use "0.12.2"
|
||||
[ring/ring-core "1.9.1"] is available but we use "1.9.0"
|
||||
[metosin/schema-tools "0.12.3"] is available but we use "0.12.2"
|
||||
[expound "0.8.9"] is available but we use "0.8.7"
|
||||
[ring "1.9.1"] is available but we use "1.9.0"
|
||||
```
|
||||
|
||||
### `reitit-ring`
|
||||
|
||||
* Make reitit.ring/create-resource-handler's `:not-found-handler` work when used outside of a router. [#464](https://github.com/metosin/reitit/issues/464). PR [#471](https://github.com/metosin/reitit/pull/471) by Kari Marttila and Metosin Maintenance Mob.
|
||||
|
||||
## 0.5.12 (2021-02-01)
|
||||
|
||||
**[compare](https://github.com/metosin/reitit/compare/0.5.11...0.5.12)**
|
||||
|
||||
* updated deps:
|
||||
|
||||
```
|
||||
[metosin/spec-tools "0.10.5"] is available but we use "0.10.4"
|
||||
[metosin/jsonista "0.3.1"] is available but we use "0.3.0"
|
||||
[metosin/muuntaja "0.6.8"] is available but we use "0.6.7"
|
||||
[ring/ring-core "1.9.0"] is available but we use "1.8.2"
|
||||
[metosin/muuntaja "0.6.8"] is available but we use "0.6.7"
|
||||
[com.fasterxml.jackson.core/jackson-core "2.12.1"] is available but we use "2.12.0"
|
||||
[com.fasterxml.jackson.core/jackson-databind "2.12.1"] is available but we use "2.12.0"
|
||||
```
|
||||
|
||||
* Allow whitespace as separator in route paths. [#411](https://github.com/metosin/reitit/issues/411). PR [#466](https://github.com/metosin/reitit/pull/466) by Kimmo Koskinen and Metosin Maintenance Mob.
|
||||
|
||||
## 0.5.11 (2020-12-27)
|
||||
|
||||
**[compare](https://github.com/metosin/reitit/compare/0.5.10...0.5.11)**
|
||||
|
||||
* updated deps:
|
||||
|
||||
```clj
|
||||
[metosin/ring-swagger-ui "3.36.0"] is available but we use "3.25.3"
|
||||
[metosin/jsonista "0.3.0"] is available but we use "0.2.7"
|
||||
[com.fasterxml.jackson.core/jackson-core "2.12.0"] is available but we use "2.11.2"
|
||||
[com.fasterxml.jackson.core/jackson-databind "2.12.0"] is available but we use "2.11.2"
|
||||
```
|
||||
|
||||
## 0.5.10 (2020-10-22)
|
||||
|
||||
**[compare](https://github.com/metosin/reitit/compare/0.5.9...0.5.10)**
|
||||
|
||||
* updated deps:
|
||||
|
||||
```clj
|
||||
|
|
@ -26,6 +156,8 @@ We use [Break Versioning][breakver]. The version numbers follow a `<major>.<mino
|
|||
|
||||
## 0.5.9 (2020-10-19)
|
||||
|
||||
**[compare](https://github.com/metosin/reitit/compare/0.5.8...0.5.9)**
|
||||
|
||||
### `reitit-frontend`
|
||||
|
||||
- `reitit.frontend.easy/start!` now correctly removes old event listeners
|
||||
|
|
@ -34,10 +166,14 @@ when called repeatedly (e.g. with hot code reload workflow)
|
|||
|
||||
## 0.5.8 (2020-10-19)
|
||||
|
||||
**[compare](https://github.com/metosin/reitit/compare/0.5.7...0.5.8)**
|
||||
|
||||
* Add `:conflicting` to route data spec [#444](https://github.com/metosin/reitit/pull/444).
|
||||
|
||||
## 0.5.7 (2020-10-18)
|
||||
|
||||
**[compare](https://github.com/metosin/reitit/compare/0.5.6...0.5.7)**
|
||||
|
||||
* updated deps:
|
||||
|
||||
```clj
|
||||
|
|
@ -55,6 +191,8 @@ when called repeatedly (e.g. with hot code reload workflow)
|
|||
|
||||
## 0.5.6 (2020-09-26)
|
||||
|
||||
**[compare](https://github.com/metosin/reitit/compare/0.5.5...0.5.6)**
|
||||
|
||||
* updated deps:
|
||||
|
||||
```clj
|
||||
|
|
@ -79,6 +217,8 @@ when called repeatedly (e.g. with hot code reload workflow)
|
|||
|
||||
## 0.5.5 (2020-07-15)
|
||||
|
||||
**[compare](https://github.com/metosin/reitit/compare/0.5.4...0.5.5)**
|
||||
|
||||
* recompile with Java8
|
||||
|
||||
```clj
|
||||
|
|
@ -87,18 +227,24 @@ when called repeatedly (e.g. with hot code reload workflow)
|
|||
|
||||
## 0.5.4 (2020-07-13)
|
||||
|
||||
**[compare](https://github.com/metosin/reitit/compare/0.5.3...0.5.4)**
|
||||
|
||||
```clj
|
||||
[metosin/malli "0.0.1-20200713.080243-20"] is available but we use "0.0.1-20200709.163702-18"
|
||||
```
|
||||
|
||||
## 0.5.3 (2020-07-09)
|
||||
|
||||
**[compare](https://github.com/metosin/reitit/compare/0.5.2...0.5.3)**
|
||||
|
||||
```clj
|
||||
[metosin/malli "0.0.1-20200709.163702-18"] is available but we use "0.0.1-20200525.162645-15"
|
||||
```
|
||||
|
||||
## 0.5.2 (2020-05-27)
|
||||
|
||||
**[compare](https://github.com/metosin/reitit/compare/0.5.1...0.5.2)**
|
||||
|
||||
```clj
|
||||
[metosin/malli "0.0.1-20200525.162645-15"] is available but we use "0.0.1-20200404.091302-14"
|
||||
```
|
||||
|
|
@ -124,6 +270,8 @@ when called repeatedly (e.g. with hot code reload workflow)
|
|||
|
||||
## 0.5.1 (2020-05-18)
|
||||
|
||||
**[compare](https://github.com/metosin/reitit/compare/0.5.0...0.5.1)**
|
||||
|
||||
```clj
|
||||
[metosin/sieppari "0.0.0-alpha13"] is available but we use "0.0.0-alpha10"
|
||||
```
|
||||
|
|
|
|||
38
README.md
38
README.md
|
|
@ -1,4 +1,4 @@
|
|||
# reitit [](https://circleci.com/gh/metosin/reitit) [](https://cljdoc.org/jump/release/metosin/reitit) [](https://clojurians.slack.com/messages/reitit/)
|
||||
# reitit [](https://github.com/metosin/reitit/actions?query=workflow%3Atestsuite) [](https://cljdoc.org/jump/release/metosin/reitit) [](https://clojurians.slack.com/messages/reitit/)
|
||||
|
||||
A fast data-driven router for Clojure(Script).
|
||||
|
||||
|
|
@ -20,6 +20,8 @@ Presentations:
|
|||
* [Data-Driven Ring with Reitit](https://www.metosin.fi/blog/reitit-ring/)
|
||||
* [Reitit, Data-Driven Routing with Clojure(Script)](https://www.metosin.fi/blog/reitit/)
|
||||
|
||||
**Status:** [stable](https://github.com/metosin/open-source#project-lifecycle-model)
|
||||
|
||||
## [Full Documentation](https://cljdoc.org/d/metosin/reitit/CURRENT)
|
||||
|
||||
There is [#reitit](https://clojurians.slack.com/messages/reitit/) in [Clojurians Slack](http://clojurians.net/) for discussion & help.
|
||||
|
|
@ -50,7 +52,7 @@ There is [#reitit](https://clojurians.slack.com/messages/reitit/) in [Clojurians
|
|||
All main modules bundled:
|
||||
|
||||
```clj
|
||||
[metosin/reitit "0.5.10"]
|
||||
[metosin/reitit "0.5.18"]
|
||||
```
|
||||
|
||||
Optionally, the parts can be required separately.
|
||||
|
|
@ -85,23 +87,28 @@ Optionally, the parts can be required separately.
|
|||
A Ring routing app with input & output coercion using [data-specs](https://github.com/metosin/spec-tools/blob/master/README.md#data-specs).
|
||||
|
||||
```clj
|
||||
(require '[muuntaja.core :as m])
|
||||
(require '[reitit.ring :as ring])
|
||||
(require '[reitit.coercion.spec])
|
||||
(require '[reitit.ring.coercion :as rrc])
|
||||
(require '[reitit.ring.middleware.muuntaja :as muuntaja])
|
||||
(require '[reitit.ring.middleware.parameters :as parameters])
|
||||
|
||||
(def app
|
||||
(ring/ring-handler
|
||||
(ring/router
|
||||
["/api"
|
||||
["/math" {:get {:parameters {:query {:x int?, :y int?}}
|
||||
:responses {200 {:body {:total pos-int?}}}
|
||||
:handler (fn [{{{:keys [x y]} :query} :parameters}]
|
||||
{:status 200
|
||||
:body {:total (+ x y)}})}}]]
|
||||
;; router data effecting all routes
|
||||
{:data {:coercion reitit.coercion.spec/coercion
|
||||
:middleware [rrc/coerce-exceptions-middleware
|
||||
:responses {200 {:body {:total int?}}}
|
||||
:handler (fn [{{{:keys [x y]} :query} :parameters}]
|
||||
{:status 200
|
||||
:body {:total (+ x y)}})}}]]
|
||||
;; router data affecting all routes
|
||||
{:data {:coercion reitit.coercion.spec/coercion
|
||||
:muuntaja m/instance
|
||||
:middleware [parameters/parameters-middleware
|
||||
rrc/coerce-request-middleware
|
||||
muuntaja/format-response-middleware
|
||||
rrc/coerce-response-middleware]}})))
|
||||
```
|
||||
|
||||
|
|
@ -146,12 +153,17 @@ All examples are in https://github.com/metosin/reitit/tree/master/examples
|
|||
|
||||
## External resources
|
||||
* Simple web application using Ring/Reitit and Integrant: https://github.com/PrestanceDesign/usermanager-reitit-integrant-example
|
||||
* A simple [ClojureScript](https://clojurescript.org/) frontend and Clojure backend using Reitit, [JUXT Clip](https://github.com/juxt/clip), [next.jdbc](https://github.com/seancorfield/next-jdbc) and other bits and bobs...
|
||||
* [startrek](https://git.sr.ht/~dharrigan/startrek)
|
||||
* [startrek-ui](https://git.sr.ht/~dharrigan/startrek-ui)
|
||||
* A simple Clojure backend using Reitit to serve up a RESTful API: [startrek](https://github.com/dharrigan/startrek). Technologies include:
|
||||
* [Donut System](https://github.com/donut-party/system)
|
||||
* [next-jdbc](https://github.com/seancorfield/next-jdbc)
|
||||
* [JUXT Clip](https://github.com/juxt/clip)
|
||||
* [Flyway](https://github.com/flyway/flyway)
|
||||
* [HoneySQL](https://github.com/seancorfield/honeysql)
|
||||
* [Babashka](https://babashka.org)
|
||||
* https://www.learnreitit.com/
|
||||
* Lipas, liikuntapalvelut: https://github.com/lipas-liikuntapaikat/lipas
|
||||
* Implementation of the Todo-Backend API spec, using Clojure, Ring/Reitit and next-jdbc: https://github.com/PrestanceDesign/todo-backend-clojure-reitit
|
||||
* Ping CRM, a single page app written in Clojure Ring, Reitit, Integrant and next.jdbc: https://github.com/prestancedesign/clojure-inertia-pingcrm-demo
|
||||
|
||||
## More info
|
||||
|
||||
|
|
@ -172,6 +184,6 @@ Roadmap is mostly written in [issues](https://github.com/metosin/reitit/issues).
|
|||
|
||||
## License
|
||||
|
||||
Copyright © 2017-2020 [Metosin Oy](http://www.metosin.fi)
|
||||
Copyright © 2017-2021 [Metosin Oy](http://www.metosin.fi)
|
||||
|
||||
Distributed under the Eclipse Public License, the same as Clojure.
|
||||
|
|
|
|||
1
dev-resources/public/with space.txt
Normal file
1
dev-resources/public/with space.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
hello
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
* Route [conflict resolution](./basics/route_conflicts.md)
|
||||
* First-class [route data](./basics/route_data.md)
|
||||
* Bi-directional routing
|
||||
* [Pluggable coercion](./coercion/coercion.md) ([schema](https://github.com/plumatic/schema) & [clojure.spec](https://clojure.org/about/spec))
|
||||
* [Pluggable coercion](./coercion/coercion.md) ([schema](https://github.com/plumatic/schema), [clojure.spec](https://clojure.org/about/spec), [malli](https://github.com/metosin/malli))
|
||||
* Helpers for [ring](./ring/ring.md), [http](./http/interceptors.md), [pedestal](./http/pedestal.md) & [frontend](./frontend/basics.md)
|
||||
* Friendly [Error Messages](./basics/error_messages.md)
|
||||
* Extendable
|
||||
|
|
@ -40,7 +40,7 @@ There is [#reitit](https://clojurians.slack.com/messages/reitit/) in [Clojurians
|
|||
All bundled:
|
||||
|
||||
```clj
|
||||
[metosin/reitit "0.5.10"]
|
||||
[metosin/reitit "0.5.18"]
|
||||
```
|
||||
|
||||
Optionally, the parts can be required separately.
|
||||
|
|
@ -139,7 +139,7 @@ Routing:
|
|||
|
||||
```clj
|
||||
(app {:request-method :get, :uri "/api/admin/users"})
|
||||
; {:status 200, :body "ok", :wrap (:api :admin}
|
||||
; {:status 200, :body "ok", :wrap (:api :admin)}
|
||||
|
||||
(app {:request-method :put, :uri "/api/admin/users"})
|
||||
; nil
|
||||
|
|
|
|||
|
|
@ -143,10 +143,10 @@ As the `Match` contains all the route data, we can create a new matching functio
|
|||
(require '[clojure.string :as str])
|
||||
|
||||
(defn recursive-match-by-path [router path]
|
||||
(if-let [match (r/match-by-path router path)]
|
||||
(when-let [match (r/match-by-path router path)]
|
||||
(if-let [subrouter (-> match :data :router)]
|
||||
(let [subpath (subs path (str/last-index-of (:template match) "/"))]
|
||||
(if-let [submatch (recursive-match-by-path subrouter subpath)]
|
||||
(when-let [submatch (recursive-match-by-path subrouter subpath)]
|
||||
(cons match submatch)))
|
||||
(list match))))
|
||||
```
|
||||
|
|
@ -206,10 +206,10 @@ First, we need to modify our matching function to support router references:
|
|||
(deref x) x))
|
||||
|
||||
(defn recursive-match-by-path [router path]
|
||||
(if-let [match (r/match-by-path (<< router) path)]
|
||||
(when-let [match (r/match-by-path (<< router) path)]
|
||||
(if-let [subrouter (-> match :data :router <<)]
|
||||
(let [subpath (subs path (str/last-index-of (:template match) "/"))]
|
||||
(if-let [submatch (recursive-match-by-path subrouter subpath)]
|
||||
(when-let [submatch (recursive-match-by-path subrouter subpath)]
|
||||
(cons match submatch)))
|
||||
(list match))))
|
||||
```
|
||||
|
|
|
|||
|
|
@ -4,15 +4,16 @@ Routers can be configured via options. The following options are available for t
|
|||
|
||||
| key | description
|
||||
|--------------|-------------
|
||||
| `:path` | Base-path for routes
|
||||
| `:routes` | Initial resolved routes (default `[]`)
|
||||
| `:data` | Initial route data (default `{}`)
|
||||
| `:spec` | clojure.spec definition for a route data, see `reitit.spec` on how to use this
|
||||
| `:syntax` | Path-parameter syntax as keyword or set of keywords (default #{:bracket :colon})
|
||||
| `:expand` | Function of `arg opts => data` to expand route arg to route data (default `reitit.core/expand`)
|
||||
| `:coerce` | Function of `route opts => route` to coerce resolved route, can throw or return `nil`
|
||||
| `:compile` | Function of `route opts => result` to compile a route handler
|
||||
| `:validate` | Function of `routes opts => ()` to validate route (data) via side-effects
|
||||
| `:conflicts` | Function of `{route #{route}} => ()` to handle conflicting routes
|
||||
| `:exception` | Function of `Exception => Exception ` to handle creation time exceptions (default `reitit.exception/exception`)
|
||||
| `:router` | Function of `routes opts => router` to override the actual router implementation
|
||||
| `:path` | Base-path for routes
|
||||
| `:routes` | Initial resolved routes (default `[]`)
|
||||
| `:data` | Initial route data (default `{}`)
|
||||
| `:spec` | clojure.spec definition for a route data, see `reitit.spec` on how to use this
|
||||
| `:syntax` | Path-parameter syntax as keyword or set of keywords (default #{:bracket :colon})
|
||||
| `:expand` | Function of `arg opts => data` to expand route arg to route data (default `reitit.core/expand`)
|
||||
| `:coerce` | Function of `route opts => route` to coerce resolved route, can throw or return `nil`
|
||||
| `:meta-merge-fn` | Function which follows the signature of `meta-merge.core/meta-merge`, useful for when you want to have more control over the meta merging
|
||||
| `:compile` | Function of `route opts => result` to compile a route handler
|
||||
| `:validate` | Function of `routes opts => ()` to validate route (data) via side-effects
|
||||
| `:conflicts` | Function of `{route #{route}} => ()` to handle conflicting routes
|
||||
| `:exception` | Function of `Exception => Exception ` to handle creation time exceptions (default `reitit.exception/exception`)
|
||||
| `:router` | Function of `routes opts => router` to override the actual router implementation
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Different Routers
|
||||
|
||||
Reitit ships with several different implementations for the `Router` protocol, originally based on the [Pedestal](https://github.com/pedestal/pedestal/tree/master/route) implementation. `router` function selects the most suitable implementation by inspecting the expanded routes. The implementation can be set manually using `:router` option, see [configuring routers](advanced/configuring_routers.md).
|
||||
Reitit ships with several different implementations for the `Router` protocol, originally based on the [Pedestal](https://github.com/pedestal/pedestal/tree/master/route) implementation. `router` function selects the most suitable implementation by inspecting the expanded routes. The implementation can be set manually using `:router` option, see [configuring routers](configuring_routers.md).
|
||||
|
||||
| router | description |
|
||||
| ------------------------------|-------------|
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ The default exception formatting uses `reitit.exception/exception`. It produces
|
|||
## Pretty Errors
|
||||
|
||||
```clj
|
||||
[metosin/reitit-dev "0.5.10"]
|
||||
[metosin/reitit-dev "0.5.18"]
|
||||
```
|
||||
|
||||
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.
|
||||
|
|
@ -51,4 +51,4 @@ See the [validating route data](route_data_validation.md) page.
|
|||
|
||||
## Runtime Exception
|
||||
|
||||
See [Exception Handling with Ring](exceptions.md).
|
||||
See [Exception Handling with Ring](../ring/exceptions.md).
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ Resolved route tree:
|
|||
; :name :user/ping}]
|
||||
; ["/api/admin/users" {:interceptors [::api]
|
||||
; :roles #{:admin}
|
||||
; :name ::users} nil]
|
||||
; :name ::users}]
|
||||
; ["/api/admin/db" {:interceptors [::api ::db]
|
||||
; :roles #{:db-admin}}]]
|
||||
```
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ Routes are just data, so it's easy to create them programmatically:
|
|||
|
||||
### Explicit path-parameter syntax
|
||||
|
||||
Router options `:syntax` allows the path-parameter syntax to be explicitely defined. It takes a keyword or set of keywords as a value. Valid values are `:colon` and `:bracket`. Default value is `#{:colon :bracket}`.
|
||||
Router options `:syntax` allows the path-parameter syntax to be explicitly defined. It takes a keyword or set of keywords as a value. Valid values are `:colon` and `:bracket`. Default value is `#{:colon :bracket}`.
|
||||
|
||||
With defaults:
|
||||
|
||||
|
|
|
|||
|
|
@ -63,14 +63,6 @@ Route names:
|
|||
; [:user/ping :user/user]
|
||||
```
|
||||
|
||||
The compiled route tree:
|
||||
|
||||
```clj
|
||||
(r/routes router)
|
||||
; [["/api/ping" {:name :user/ping} nil]
|
||||
; ["/api/user/:id" {:name :user/user} nil]]
|
||||
```
|
||||
|
||||
### Composing
|
||||
|
||||
As routes are defined as plain data, it's easy to merge multiple route trees into a single router
|
||||
|
|
@ -85,9 +77,10 @@ As routes are defined as plain data, it's easy to merge multiple route trees int
|
|||
["/ping" ::ping]
|
||||
["/db" ::db]])
|
||||
|
||||
(r/router
|
||||
[admin-routes
|
||||
user-routes])
|
||||
(def router
|
||||
(r/router
|
||||
[admin-routes
|
||||
user-routes]))
|
||||
```
|
||||
|
||||
Merged route tree:
|
||||
|
|
@ -109,6 +102,6 @@ When router is created, the following steps are done:
|
|||
* route arguments are expanded (via `:expand` option)
|
||||
* routes are coerced (via `:coerce` options)
|
||||
* route tree is compiled (via `:compile` options)
|
||||
* [route conflicts](advanced/route_conflicts.md) are resolved (via `:conflicts` options)
|
||||
* [route conflicts](route_conflicts.md) are resolved (via `:conflicts` options)
|
||||
* optionally, route data is validated (via `:validate` options)
|
||||
* [router implementation](../advanced/different_routers.md) is automatically selected (or forced via `:router` options) and created
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
[Malli](https://github.com/metosin/malli) is data-driven Schema library for Clojure/Script.
|
||||
|
||||
## Default Syntax
|
||||
|
||||
By default, [Vector Syntax](https://github.com/metosin/malli#vector-syntax) is used:
|
||||
|
||||
```clj
|
||||
(require '[reitit.coercion.malli])
|
||||
(require '[reitit.coercion :as coercion])
|
||||
|
|
@ -13,7 +17,7 @@
|
|||
:coercion reitit.coercion.malli/coercion
|
||||
:parameters {:path [:map
|
||||
[:company string?]
|
||||
[:user-id int?]]}]
|
||||
[:user-id int?]]}}]
|
||||
{:compile coercion/compile-request-coercers}))
|
||||
|
||||
(defn match-by-path-and-coerce! [path]
|
||||
|
|
@ -44,18 +48,36 @@ Failing coercion:
|
|||
; => ExceptionInfo Request coercion failed...
|
||||
```
|
||||
|
||||
## Lite Syntax
|
||||
|
||||
Same using [Lite Syntax](https://github.com/metosin/malli#lite):
|
||||
|
||||
```clj
|
||||
(def router
|
||||
(r/router
|
||||
["/:company/users/:user-id" {:name ::user-view
|
||||
:coercion reitit.coercion.malli/coercion
|
||||
:parameters {:path {:company string?
|
||||
:user-id int?}}}]
|
||||
{:compile coercion/compile-request-coercers}))
|
||||
```
|
||||
|
||||
## Configuring coercion
|
||||
|
||||
Using `create` with options to create the coercion instead of `coercion`:
|
||||
|
||||
```clj
|
||||
(require '[malli.util :as mu])
|
||||
|
||||
(reitit.coercion.malli/create
|
||||
{:transformers {:body {:default default-transformer-provider
|
||||
:formats {"application/json" json-transformer-provider}}
|
||||
:string {:default string-transformer-provider}
|
||||
:response {:default default-transformer-provider}}
|
||||
{:transformers {:body {:default reitit.coercion.malli/default-transformer-provider
|
||||
:formats {"application/json" reitit.coercion.malli/json-transformer-provider}}
|
||||
:string {:default reitit.coercion.malli/string-transformer-provider}
|
||||
:response {:default reitit.coercion.malli/default-transformer-provider}}
|
||||
;; set of keys to include in error messages
|
||||
:error-keys #{:type :coercion :in :schema :value :errors :humanized #_:transformed}
|
||||
;; support lite syntax?
|
||||
:lite true
|
||||
;; schema identity function (default: close all map schemas)
|
||||
:compile mu/closed-schema
|
||||
;; validate request & response
|
||||
|
|
|
|||
|
|
@ -13,25 +13,48 @@
|
|||
./scripts/test.sh cljs
|
||||
```
|
||||
|
||||
## Formatting
|
||||
|
||||
```bash
|
||||
clojure-lsp format
|
||||
clojure-lsp clean-ns
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
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)
|
||||
|
||||
## To bump up version:
|
||||
## Making a release
|
||||
|
||||
We use [Break Versioning][breakver]. Remember our promise: patch-level bumps never include breaking changes!
|
||||
|
||||
[breakver]: https://github.com/ptaoussanis/encore/blob/master/BREAK-VERSIONING.md
|
||||
|
||||
```bash
|
||||
# Check that you're using Java 8! Making the release with a newer Java version
|
||||
# means that it is broken when used with Java 8.
|
||||
java -version
|
||||
|
||||
# new version
|
||||
./scripts/set-version "1.0.0"
|
||||
./scripts/lein-modules install
|
||||
|
||||
# create a release commit and a tag
|
||||
git add -u
|
||||
git commit -m "Release 1.0.0"
|
||||
git tag 1.0.0
|
||||
|
||||
# works
|
||||
./scripts/lein-modules install
|
||||
lein test
|
||||
|
||||
# deploy to clojars
|
||||
./scripts/lein-modules do clean, deploy clojars
|
||||
|
||||
# push the commit and the tag
|
||||
git push
|
||||
git push --tags
|
||||
```
|
||||
|
||||
* Remembor to update the changelog!
|
||||
* Announce the release at least on #reitit in Clojurians.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Default Interceptors
|
||||
|
||||
```clj
|
||||
[metosin/reitit-interceptors "0.5.10"]
|
||||
[metosin/reitit-interceptors "0.5.18"]
|
||||
```
|
||||
|
||||
Just like the [ring default middleware](../ring/default_middleware.md), but for interceptors.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ Reitit has also support for [interceptors](http://pedestal.io/reference/intercep
|
|||
## Reitit-http
|
||||
|
||||
```clj
|
||||
[metosin/reitit-http "0.5.10"]
|
||||
[metosin/reitit-http "0.5.18"]
|
||||
```
|
||||
|
||||
A module for http-routing using interceptors instead of middleware. Builds on top of the [`reitit-ring`](../ring/ring.md) module having all the same features.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
[Pedestal](http://pedestal.io/) is a backend web framework for Clojure. `reitit-pedestal` provides an alternative routing engine for Pedestal.
|
||||
|
||||
```clj
|
||||
[metosin/reitit-pedestal "0.5.10"]
|
||||
[metosin/reitit-pedestal "0.5.18"]
|
||||
```
|
||||
|
||||
Why should one use reitit instead of the Pedestal [default routing](http://pedestal.io/reference/routing-quick-reference)?
|
||||
|
|
@ -26,8 +26,8 @@ A minimalistic example on how to to swap the default-router with a reitit router
|
|||
```clj
|
||||
; [io.pedestal/pedestal.service "0.5.5"]
|
||||
; [io.pedestal/pedestal.jetty "0.5.5"]
|
||||
; [metosin/reitit-pedestal "0.5.10"]
|
||||
; [metosin/reitit "0.5.10"]
|
||||
; [metosin/reitit-pedestal "0.5.18"]
|
||||
; [metosin/reitit "0.5.18"]
|
||||
|
||||
(require '[io.pedestal.http :as server])
|
||||
(require '[reitit.pedestal :as pedestal])
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Sieppari
|
||||
|
||||
```clj
|
||||
[metosin/reitit-sieppari "0.5.10"]
|
||||
[metosin/reitit-sieppari "0.5.18"]
|
||||
```
|
||||
|
||||
[Sieppari](https://github.com/metosin/sieppari) is a new and fast interceptor implementation for Clojure, with pluggable async supporting [core.async](https://github.com/clojure/core.async), [Manifold](https://github.com/ztellman/manifold) and [Promesa](http://funcool.github.io/promesa/latest).
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ There is an extra option in http-router (actually, in the underlying interceptor
|
|||
### Printing Context Diffs
|
||||
|
||||
```clj
|
||||
[metosin/reitit-interceptors "0.5.10"]
|
||||
[metosin/reitit-interceptors "0.5.18"]
|
||||
```
|
||||
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ The routing sample taken from [bide](https://github.com/funcool/bide) README:
|
|||
(r/match-by-path routes "/workspace/1/1")))
|
||||
```
|
||||
|
||||
Based on the [perf tests](https://github.com/metosin/reitit/tree/master/perf-test/clj/reitit/perf/bide_perf_test.clj), the first (static path) lookup is 300-500x faster and the second (wildcard path) lookup is 18-110x faster that the other tested routing libs (Ataraxy, Bidi, Compojure and Pedestal).
|
||||
Based on the [perf tests](https://github.com/metosin/reitit/blob/master/perf-test/clj/reitit/bide_perf_test.clj), the first (static path) lookup is 300-500x faster and the second (wildcard path) lookup is 18-110x faster that the other tested routing libs (Ataraxy, Bidi, Compojure and Pedestal).
|
||||
|
||||
But, the example is too simple for any real benchmark. Also, some of the libraries always match on the `:request-method` too and by doing so, do more work than just match by path. Compojure does most work also by invoking the handler.
|
||||
|
||||
|
|
|
|||
|
|
@ -8,9 +8,10 @@ We can do this with middleware in reitit like this:
|
|||
```clj
|
||||
(defn- hidden-method
|
||||
[request]
|
||||
(keyword
|
||||
(or (get-in request [:form-params "_method"]) ;; look for "_method" field in :form-params
|
||||
(get-in request [:multipart-params "_method"])))) ;; or in :multipart-params
|
||||
(some-> (or (get-in request [:form-params "_method"]) ;; look for "_method" field in :form-params
|
||||
(get-in request [:multipart-params "_method"])) ;; or in :multipart-params
|
||||
clojure.string/lower-case
|
||||
keyword))
|
||||
|
||||
(def wrap-hidden-method
|
||||
{:name ::wrap-hidden-method
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ Responses are defined in route data under `:responses` key. It's value should be
|
|||
|
||||
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`.
|
||||
|
||||
Handler can access the coerced parameters can be read under `:parameters` key in the request.
|
||||
Handlers can access the coerced parameters via the `:parameters` key in the request.
|
||||
|
||||
```clj
|
||||
(require '[reitit.coercion.schema])
|
||||
|
|
@ -71,7 +71,7 @@ Defining a coercion for a route data doesn't do anything, as it's just data. We
|
|||
|
||||
### Full example
|
||||
|
||||
Here's an full example for applying coercion with Reitit, Ring and Schema:
|
||||
Here is a full example for applying coercion with Reitit, Ring and Schema:
|
||||
|
||||
```clj
|
||||
(require '[reitit.ring.coercion :as rrc])
|
||||
|
|
@ -150,7 +150,7 @@ Invalid response:
|
|||
|
||||
## Pretty printing spec errors
|
||||
|
||||
Spec problems are exposed as-is into request & response coercion errors, enabling pretty-printers like [expound](https://github.com/bhb/expound) to be used:
|
||||
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:
|
||||
|
||||
```clj
|
||||
(require '[reitit.ring :as ring])
|
||||
|
|
@ -216,7 +216,7 @@ Spec problems are exposed as-is into request & response coercion errors, enablin
|
|||
|
||||
### Optimizations
|
||||
|
||||
The coercion middleware are [compiled against a route](compiling_middleware.md). In the middleware compilation step the actual coercer implementations are constructed for the defined models. Also, the middleware doesn't mount itself if a route doesn't have `:coercion` and `:parameters` or `:responses` defined.
|
||||
The coercion middlewares are [compiled against a route](compiling_middleware.md). In the middleware compilation step the actual coercer implementations are constructed for the defined models. Also, the middleware doesn't mount itself if a route doesn't have `:coercion` and `:parameters` or `:responses` defined.
|
||||
|
||||
We can query the compiled middleware chain for the routes:
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
# Compiling Middleware
|
||||
|
||||
The [dynamic extensions](dynamic_extensions.md) is a easy way to extend the system. To enable fast lookups into route data, we can compile them into any shape (records, functions etc.) we want, enabling fast access at request-time.
|
||||
The [dynamic extensions](dynamic_extensions.md) are an easy way to extend the system. To enable fast lookup of route data, we can compile them into any shape (records, functions etc.), enabling fast access at request-time.
|
||||
|
||||
But, we can do much better. As we know the exact route that middleware/interceptor is linked to, we can pass the (compiled) route information into the middleware at creation-time. It can do local reasoning: extract and transform relevant data just for it and pass the optimized data into the actual request-handler via a closure - yielding much faster runtime processing. Middleware can also decide not to mount itself by returning `nil`. Why mount a `wrap-enforce-roles` middleware for a route if there are no roles required for it?
|
||||
But, we can do much better. As we know the exact route that a middleware/interceptor is linked to, we can pass the (compiled) route information into the middleware at creation-time. It can do local reasoning: Extract and transform relevant data just for it and pass the optimized data into the actual request-handler via a closure - yielding much faster runtime processing. A middleware can also decide not to mount itself by returning `nil`. (E.g. Why mount a `wrap-enforce-roles` middleware for a route if there are no roles required for it?)
|
||||
|
||||
To enable this we use [middleware records](data_driven_middleware.md) `:compile` key instead of the normal `:wrap`. `:compile` expects a function of `route-data router-opts => ?IntoMiddleware`.
|
||||
|
||||
To demonstrate the two approaches, below are response coercion middleware written as normal ring middleware function and as middleware record with `:compile`.
|
||||
To demonstrate the two approaches, below is the response coercion middleware written as normal ring middleware function and as middleware record with `:compile`.
|
||||
|
||||
## Normal Middleware
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# Content Negotiation
|
||||
|
||||
Wrapper for [Muuntaja](https://github.com/metosin/muuntaja) middleware for content-negotiation, request decoding and response encoding. Takes explicit configuration via `:muuntaja` key in route data. Emit's [swagger](swagger.md) `:produces` and `:consumes` definitions automatically based on the Muuntaja configuration.
|
||||
Wrapper for [Muuntaja](https://github.com/metosin/muuntaja) middleware for content negotiation, request decoding and response encoding. Takes explicit configuration via `:muuntaja` key in route data. Emits [swagger](swagger.md) `:produces` and `:consumes` definitions automatically based on the Muuntaja configuration.
|
||||
|
||||
Negotiates a request body based on `Content-Type` header and response body based on `Accept`, `Accept-Charset` headers. Publishes the negotiation results as `:muuntaja/request` and `:muuntaja/response` keys into the request.
|
||||
Negotiates a request body based on `Content-Type` header and response body based on `Accept` and `Accept-Charset` headers. Publishes the negotiation results as `:muuntaja/request` and `:muuntaja/response` keys into the request.
|
||||
|
||||
Decodes the request body into `:body-params` using the `:muuntaja/request` key in request if the `:body-params` doesn't already exist.
|
||||
|
||||
|
|
@ -87,7 +87,7 @@ Server: Jetty(9.2.21.v20170120)
|
|||
|
||||
## Changing default parameters
|
||||
|
||||
The current JSON formatter used by `reitit` already have the option to parse keys as `keyword` which is a sane default in Clojure. However, if you would like to parse all the `double` as `bigdecimal` you'd need to change an option of the [JSON formatter](https://github.com/metosin/jsonista)
|
||||
The current JSON formatter used by `reitit` already has the option to parse keys as `keyword` which is a sane default in Clojure. However, if you would like to parse all the `double` as `bigdecimal` you'd need to change an option of the [JSON formatter](https://github.com/metosin/jsonista)
|
||||
|
||||
|
||||
```clj
|
||||
|
|
@ -102,7 +102,7 @@ The current JSON formatter used by `reitit` already have the option to parse key
|
|||
|
||||
Now you should change the `m/instance` installed in the router with the `new-muuntaja-instance`.
|
||||
|
||||
You can find more options for [JSON](https://cljdoc.org/d/metosin/jsonista/0.2.5/api/jsonista.core#object-mapper) and [EDN].
|
||||
Here you can find more options for [JSON](https://cljdoc.org/d/metosin/jsonista/0.2.5/api/jsonista.core#object-mapper) and EDN.
|
||||
|
||||
|
||||
## Adding custom encoder
|
||||
|
|
@ -125,14 +125,14 @@ The example below is from `muuntaja` explaining how to add a custom encoder to p
|
|||
|
||||
```
|
||||
|
||||
## Adding all together
|
||||
## Putting it all together
|
||||
|
||||
If you inspect `m/default-options` it's only a map, therefore you can compose your new muuntaja instance with as many options as you need it.
|
||||
If you inspect `m/default-options` you'll find it's only a map. This means you can compose your new muuntaja instance with as many options as you need.
|
||||
|
||||
```clj
|
||||
(def new-muuntaja
|
||||
(m/create
|
||||
(-> m/default-options
|
||||
(assoc-in [:formats "application/json" :decoder-opts :bigdecimals] true)
|
||||
(assoc-in [:formats "application/json" :encoder-opts :data-format] "yyyy-MM-dd"))))
|
||||
(assoc-in [:formats "application/json" :encoder-opts :date-format] "yyyy-MM-dd"))))
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
# Data-driven Middleware
|
||||
|
||||
Ring [defines middleware](https://github.com/ring-clojure/ring/wiki/Concepts#middleware) as a function of type `handler & args => request => response`. It's relatively easy to understand and enables good performance. Downside is that the middleware-chain is just a opaque function, making things like debugging and composition hard. It's too easy to apply the middleware 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.
|
||||
|
||||
Reitit defines middleware as data:
|
||||
|
||||
1. Middleware can be defined as first-class data entries
|
||||
2. Middleware can be mounted as a [duct-style](https://github.com/duct-framework/duct/wiki/Configuration) vector (of middleware)
|
||||
4. Middleware can be optimized & [compiled](compiling_middleware.md) against an endpoint
|
||||
3. Middleware chain can be transformed by the router
|
||||
1. A middleware can be defined as first-class data entries
|
||||
2. A middleware can be mounted as a [duct-style](https://github.com/duct-framework/duct/wiki/Configuration) vector (of middlewares)
|
||||
4. A middleware can be optimized & [compiled](compiling_middleware.md) against an endpoint
|
||||
3. A middleware chain can be transformed by the router
|
||||
|
||||
## Middleware as data
|
||||
|
||||
All values in the `:middleware` vector in the route data are expanded into `reitit.middleware/Middleware` Records with using the `reitit.middleware/IntoMiddleware` Protocol. By default, functions, maps and `Middleware` records are allowed.
|
||||
All values in the `:middleware` vector of route data are expanded into `reitit.middleware/Middleware` Records by using the `reitit.middleware/IntoMiddleware` Protocol. By default, functions, maps and `Middleware` records are allowed.
|
||||
|
||||
Records can have arbitrary keys, but the following keys have a special purpose:
|
||||
Records can have arbitrary keys, but the following keys have special purpose:
|
||||
|
||||
| key | description |
|
||||
| ---------------|-------------|
|
||||
|
|
@ -22,13 +22,13 @@ Records can have arbitrary keys, but the following keys have a special purpose:
|
|||
| `:wrap` | The actual middleware function of `handler & args => request => response`
|
||||
| `:compile` | Middleware compilation function, see [compiling middleware](compiling_middleware.md).
|
||||
|
||||
Middleware Records are accessible in their raw form in the compiled route results, thus available for inventories, creating api-docs etc.
|
||||
Middleware Records are accessible in their raw form in the compiled route results, and thus are available for inventories, creating api-docs, etc.
|
||||
|
||||
For the actual request processing, the Records are unwrapped into normal functions and composed into a middleware function chain, yielding zero runtime penalty.
|
||||
|
||||
### Creating Middleware
|
||||
|
||||
The following produce identical middleware runtime function.
|
||||
The following examples produce identical middleware runtime functions.
|
||||
|
||||
### Function
|
||||
|
||||
|
|
@ -77,7 +77,7 @@ The following produce identical middleware runtime function.
|
|||
:handler handler}}]])))
|
||||
```
|
||||
|
||||
All the middleware are applied correctly:
|
||||
All the middlewares are applied correctly:
|
||||
|
||||
```clj
|
||||
(app {:request-method :get, :uri "/api/ping"})
|
||||
|
|
@ -86,7 +86,7 @@ All the middleware are applied correctly:
|
|||
|
||||
## Compiling middleware
|
||||
|
||||
Middleware can be optimized against an endpoint using [middleware compilation](compiling_middleware.md).
|
||||
Middlewares can be optimized against an endpoint using [middleware compilation](compiling_middleware.md).
|
||||
|
||||
## Ideas for the future
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Default handler
|
||||
|
||||
By default, if no routes match, `nil` is returned, which is not valid response in Ring:
|
||||
By default, if no routes match, `nil` is returned, which is not a valid response in Ring:
|
||||
|
||||
```clj
|
||||
(require '[reitit.ring :as ring])
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
# Default Middleware
|
||||
|
||||
```clj
|
||||
[metosin/reitit-middleware "0.5.10"]
|
||||
[metosin/reitit-middleware "0.5.18"]
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
* [Parameter Handling](#parameters-handling)
|
||||
* [Exception Handling](#exception-handling)
|
||||
|
|
@ -17,7 +17,7 @@ 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
|
||||
`ring.middleware.params/wrap-params`.
|
||||
|
||||
**NOTE**: 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.
|
||||
**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
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# Dynamic Extensions
|
||||
|
||||
`ring-handler` injects the `Match` into a request and it can be extracted at runtime with `reitit.ring/get-match`. This can be used to build ad-hoc extensions to the system.
|
||||
`ring-handler` injects the `Match` into a request and it can be extracted at runtime with `reitit.ring/get-match`. This can be used to build ad hoc extensions to the system.
|
||||
|
||||
Example middleware to guard routes based on user roles:
|
||||
This example shows a middleware to guard routes based on user roles:
|
||||
|
||||
```clj
|
||||
(require '[reitit.ring :as ring])
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
# Exception Handling with Ring
|
||||
|
||||
```clj
|
||||
[metosin/reitit-middleware "0.5.10"]
|
||||
[metosin/reitit-middleware "0.5.18"]
|
||||
```
|
||||
|
||||
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 practise is a have an top-level exception handler to log and format the 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.
|
||||
|
||||
```clj
|
||||
(require '[reitit.ring.middleware.exception :as exception])
|
||||
|
|
@ -36,7 +36,7 @@ A preconfigured middleware using `exception/default-handlers`. Catches:
|
|||
|
||||
### `exception/create-exception-middleware`
|
||||
|
||||
Creates the exception-middleware with custom options. Takes a map of `identifier => exception request => response` that is used to select the exception handler for the thrown/raised exception identifier. Exception identifier is either a `Keyword` or a Exception Class.
|
||||
Creates the exception-middleware with custom options. Takes a map of `identifier => exception request => response` that is used to select the exception handler for the thrown/raised exception identifier. Exception identifier is either a `Keyword` or an Exception Class.
|
||||
|
||||
The following handlers are available by default:
|
||||
|
||||
|
|
@ -55,7 +55,7 @@ The handler is selected from the options map by exception identifier in the foll
|
|||
2) Class of exception
|
||||
3) `:type` ancestors of exception ex-data
|
||||
4) Super Classes of exception
|
||||
5) The ::default handler
|
||||
5) The `::default` handler
|
||||
|
||||
```clj
|
||||
;; type hierarchy
|
||||
|
|
@ -94,7 +94,7 @@ The handler is selected from the options map by exception identifier in the foll
|
|||
(def app
|
||||
(ring/ring-handler
|
||||
(ring/router
|
||||
["/fail" (fn [_] (throw (ex-info "fail" {:type ::failue})))]
|
||||
["/fail" (fn [_] (throw (ex-info "fail" {:type ::failure})))]
|
||||
{:data {:middleware [exception-middleware]}})))
|
||||
|
||||
(app {:request-method :get, :uri "/fail"})
|
||||
|
|
@ -102,6 +102,6 @@ The handler is selected from the options map by exception identifier in the foll
|
|||
; => {:status 500,
|
||||
; :body {:message "default"
|
||||
; :exception clojure.lang.ExceptionInfo
|
||||
; :data {:type :user/failue}
|
||||
; :data {:type :user/failure}
|
||||
; :uri "/fail"}}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@
|
|||
Read more about the [Ring Concepts](https://github.com/ring-clojure/ring/wiki/Concepts).
|
||||
|
||||
```clj
|
||||
[metosin/reitit-ring "0.5.10"]
|
||||
[metosin/reitit-ring "0.5.18"]
|
||||
```
|
||||
|
||||
## `reitit.ring/ring-router`
|
||||
|
||||
`ring-router` is a higher order router, which adds support for `:request-method` based routing, [handlers](https://github.com/ring-clojure/ring/wiki/Concepts#handlers) and [middleware](https://github.com/ring-clojure/ring/wiki/Concepts#middleware).
|
||||
|
||||
It accepts the following options:
|
||||
It accepts the following options:
|
||||
|
||||
| key | description |
|
||||
| ----------------------------------------|-------------|
|
||||
|
|
@ -53,7 +53,7 @@ Given a `ring-router`, optional default-handler & options, `ring-handler` functi
|
|||
|
||||
| key | description |
|
||||
| ------------------|-------------|
|
||||
| `:middleware` | Optional sequence of middleware that wrap the ring-handler"
|
||||
| `:middleware` | Optional sequence of middlewares that wrap the ring-handler
|
||||
| `:inject-match?` | Boolean to inject `match` into request under `:reitit.core/match` key (default true)
|
||||
| `:inject-router?` | Boolean to inject `router` into request under `:reitit.core/router` key (default true)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
# Static Resources (Clojure Only)
|
||||
|
||||
Static resources can be served using `reitit.ring/create-resource-handler`. It takes optionally an options map and returns a ring handler to serve files from Classpath.
|
||||
Static resources can be served by using the following two functions:
|
||||
|
||||
There are two options to serve the files.
|
||||
* `reitit.ring/create-resource-handler`, which returns a Ring handler that serves files from classpath, and
|
||||
* `reitit.ring/create-file-handler`, which returns a Ring handler that servers files from file system
|
||||
|
||||
There are two ways to mount the handlers.
|
||||
The examples below use `reitit.ring/create-resource-handler`, but `reitit.ring/create-file-handler` works the same way.
|
||||
|
||||
## Internal routes
|
||||
|
||||
|
|
@ -33,7 +37,9 @@ To serve static files with conflicting routes, e.g. `"/*"`, one needs to disable
|
|||
|
||||
## External routes
|
||||
|
||||
A better way to serve files from conflicting paths, e.g. `"/*"`, is to serve them from the default-handler. One can compose multiple default locations using `ring-handler`. This way, they are only served if none of the actual routes have matched.
|
||||
A better way to serve files from conflicting paths, e.g. `"/*"`, is to serve them from the default-handler.
|
||||
One can compose multiple default locations using `reitit.ring/ring-handler`.
|
||||
This way, they are only served if none of the actual routes have matched.
|
||||
|
||||
```clj
|
||||
(ring/ring-handler
|
||||
|
|
@ -46,21 +52,19 @@ A better way to serve files from conflicting paths, e.g. `"/*"`, is to serve the
|
|||
|
||||
## Configuration
|
||||
|
||||
`reitit.ring/create-resource-handler` takes 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 `:`
|
||||
| :root | optional resource root, defaults to `\"public\"`
|
||||
| :path | optional path to mount the handler to. Works only if mounted outside of 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
|
||||
| :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)
|
||||
|
||||
|
||||
|
||||
### TODO
|
||||
|
||||
* support for things like `:cache`, `:etag`, `:last-modified?`, and `:gzip`
|
||||
* support for ClojureScript
|
||||
* serve from file-system
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Swagger Support
|
||||
|
||||
```
|
||||
[metosin/reitit-swagger "0.5.10"]
|
||||
[metosin/reitit-swagger "0.5.18"]
|
||||
```
|
||||
|
||||
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.
|
||||
|
|
@ -45,10 +45,10 @@ If you need to post-process the generated spec, just wrap the handler with a cus
|
|||
[Swagger-ui](https://github.com/swagger-api/swagger-ui) is a user interface to visualize and interact with the Swagger specification. To make things easy, there is a pre-integrated version of the swagger-ui as a separate module.
|
||||
|
||||
```
|
||||
[metosin/reitit-swagger-ui "0.5.10"]
|
||||
[metosin/reitit-swagger-ui "0.5.18"]
|
||||
```
|
||||
|
||||
`reitit.swagger-ui/create-swagger-ui-hander` can be used to create a ring-handler to serve the swagger-ui. It accepts the following options:
|
||||
`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:
|
||||
|
||||
| key | description |
|
||||
| -----------------|-------------|
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ There is an extra option in ring-router (actually, in the underlying middleware-
|
|||
### Printing Request Diffs
|
||||
|
||||
```clj
|
||||
[metosin/reitit-middleware "0.5.10"]
|
||||
[metosin/reitit-middleware "0.5.18"]
|
||||
```
|
||||
|
||||
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:
|
||||
|
|
|
|||
50
examples/README.md
Normal file
50
examples/README.md
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# Examples
|
||||
|
||||
## buddy-auth
|
||||
## frontend-auth
|
||||
## frontend-controllers
|
||||
## frontend-links
|
||||
## frontend-prompt
|
||||
## frontend-re-frame
|
||||
## frontend
|
||||
## http-swagger
|
||||
|
||||
Coercion with Spec and Swagger generation.
|
||||
|
||||
Same as ring-spec-swagger?
|
||||
|
||||
Async examples as extra.
|
||||
|
||||
## http
|
||||
|
||||
Async example.
|
||||
|
||||
## just-coercion-with-ring
|
||||
|
||||
Bad name.
|
||||
|
||||
Coercion example for spec, data-spec, Schema.
|
||||
No Swagger generation or Malli!
|
||||
|
||||
Same as ring-example?
|
||||
|
||||
## pedestal-swagger
|
||||
## pedestal
|
||||
## ring-example
|
||||
|
||||
Coercion example for spec, data-spec, Schema.
|
||||
No Swagger generation or Malli!
|
||||
|
||||
## ring-integrant
|
||||
|
||||
## ring-malli-swagger
|
||||
|
||||
Coercion with Malli and Swagger generation.
|
||||
|
||||
## ring-spec-swagger
|
||||
|
||||
Coercion with Spec and Swagger generation.
|
||||
|
||||
## ring-swagger
|
||||
|
||||
Coercion with Spec and Swagger generation. Same as previous!
|
||||
|
|
@ -2,6 +2,6 @@
|
|||
:description "Reitit Buddy Auth App"
|
||||
:dependencies [[org.clojure/clojure "1.10.1"]
|
||||
[ring/ring-jetty-adapter "1.8.1"]
|
||||
[metosin/reitit "0.5.10"]
|
||||
[metosin/reitit "0.5.18"]
|
||||
[buddy "2.0.0"]]
|
||||
:repl-options {:init-ns example.server})
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@
|
|||
[ring "1.7.1"]
|
||||
[hiccup "1.0.5"]
|
||||
[org.clojure/clojurescript "1.10.439"]
|
||||
[metosin/reitit "0.5.10"]
|
||||
[metosin/reitit-schema "0.5.10"]
|
||||
[metosin/reitit-frontend "0.5.10"]
|
||||
[metosin/reitit "0.5.18"]
|
||||
[metosin/reitit-schema "0.5.18"]
|
||||
[metosin/reitit-frontend "0.5.18"]
|
||||
;; Just for pretty printting the match
|
||||
[fipp "0.6.14"]]
|
||||
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@
|
|||
[ring "1.7.1"]
|
||||
[hiccup "1.0.5"]
|
||||
[org.clojure/clojurescript "1.10.439"]
|
||||
[metosin/reitit "0.5.10"]
|
||||
[metosin/reitit-schema "0.5.10"]
|
||||
[metosin/reitit-frontend "0.5.10"]
|
||||
[metosin/reitit "0.5.18"]
|
||||
[metosin/reitit-schema "0.5.18"]
|
||||
[metosin/reitit-frontend "0.5.18"]
|
||||
;; Just for pretty printting the match
|
||||
[fipp "0.6.14"]]
|
||||
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@
|
|||
[ring "1.7.1"]
|
||||
[hiccup "1.0.5"]
|
||||
[org.clojure/clojurescript "1.10.520"]
|
||||
[metosin/reitit "0.5.10"]
|
||||
[metosin/reitit-spec "0.5.10"]
|
||||
[metosin/reitit-frontend "0.5.10"]
|
||||
[metosin/reitit "0.5.18"]
|
||||
[metosin/reitit-spec "0.5.18"]
|
||||
[metosin/reitit-frontend "0.5.18"]
|
||||
;; Just for pretty printting the match
|
||||
[fipp "0.6.14"]]
|
||||
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@
|
|||
[ring "1.7.1"]
|
||||
[hiccup "1.0.5"]
|
||||
[org.clojure/clojurescript "1.10.520"]
|
||||
[metosin/reitit "0.5.10"]
|
||||
[metosin/reitit-spec "0.5.10"]
|
||||
[metosin/reitit-frontend "0.5.10"]
|
||||
[metosin/reitit "0.5.18"]
|
||||
[metosin/reitit-spec "0.5.18"]
|
||||
[metosin/reitit-frontend "0.5.18"]
|
||||
;; Just for pretty printting the match
|
||||
[fipp "0.6.14"]]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
(defproject frontend-re-frame "0.1.0-SNAPSHOT"
|
||||
:dependencies [[org.clojure/clojure "1.10.0"]
|
||||
[org.clojure/clojurescript "1.10.520"]
|
||||
[metosin/reitit "0.5.10"]
|
||||
[metosin/reitit "0.5.18"]
|
||||
[reagent "0.8.1"]
|
||||
[re-frame "0.10.6"]]
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,14 @@
|
|||
[reitit.frontend.controllers :as rfc]
|
||||
[reitit.frontend.easy :as rfe]))
|
||||
|
||||
;;; Effects ;;;
|
||||
|
||||
;; Triggering navigation from events.
|
||||
|
||||
(re-frame/reg-fx :push-state
|
||||
(fn [route]
|
||||
(apply rfe/push-state route)))
|
||||
|
||||
;;; Events ;;;
|
||||
|
||||
(re-frame/reg-event-db ::initialize-db
|
||||
|
|
@ -16,7 +24,7 @@
|
|||
{:current-route nil})))
|
||||
|
||||
(re-frame/reg-event-fx ::push-state
|
||||
(fn [db [_ & route]]
|
||||
(fn [_ [_ & route]]
|
||||
{:push-state route}))
|
||||
|
||||
(re-frame/reg-event-db ::navigated
|
||||
|
|
@ -49,14 +57,6 @@
|
|||
[:div
|
||||
[:h1 "This is sub-page 2"]])
|
||||
|
||||
;;; Effects ;;;
|
||||
|
||||
;; Triggering navigation from events.
|
||||
|
||||
(re-frame/reg-fx :push-state
|
||||
(fn [route]
|
||||
(apply rfe/push-state route)))
|
||||
|
||||
;;; Routes ;;;
|
||||
|
||||
(defn href
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@
|
|||
[ring "1.8.1"]
|
||||
[hiccup "1.0.5"]
|
||||
[org.clojure/clojurescript "1.10.773"]
|
||||
[metosin/reitit "0.5.10"]
|
||||
[metosin/reitit-spec "0.5.10"]
|
||||
[metosin/reitit-frontend "0.5.10"]
|
||||
[metosin/reitit "0.5.18"]
|
||||
[metosin/reitit-spec "0.5.18"]
|
||||
[metosin/reitit-frontend "0.5.18"]
|
||||
;; Just for pretty printting the match
|
||||
[fipp "0.6.23"]]
|
||||
|
||||
|
|
|
|||
|
|
@ -3,5 +3,5 @@
|
|||
:dependencies [[org.clojure/clojure "1.10.0"]
|
||||
[ring/ring-jetty-adapter "1.7.1"]
|
||||
[aleph "0.4.7-alpha5"]
|
||||
[metosin/reitit "0.5.10"]]
|
||||
[metosin/reitit "0.5.18"]]
|
||||
:repl-options {:init-ns example.server})
|
||||
|
|
|
|||
|
|
@ -5,5 +5,5 @@
|
|||
[funcool/promesa "1.9.0"]
|
||||
[manifold "0.1.8"]
|
||||
[ring/ring-jetty-adapter "1.7.1"]
|
||||
[metosin/reitit "0.5.10"]]
|
||||
[metosin/reitit "0.5.18"]]
|
||||
:repl-options {:init-ns example.server})
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@
|
|||
:description "Reitit coercion with vanilla ring"
|
||||
:dependencies [[org.clojure/clojure "1.10.0"]
|
||||
[ring/ring-jetty-adapter "1.7.1"]
|
||||
[metosin/reitit "0.5.10"]])
|
||||
[metosin/reitit "0.5.18"]])
|
||||
|
|
|
|||
11
examples/pedestal-malli-swagger/.gitignore
vendored
Normal file
11
examples/pedestal-malli-swagger/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
/target
|
||||
/classes
|
||||
/checkouts
|
||||
pom.xml
|
||||
pom.xml.asc
|
||||
*.jar
|
||||
*.class
|
||||
/.lein-*
|
||||
/.nrepl-port
|
||||
.hgignore
|
||||
.hg/
|
||||
9
examples/pedestal-malli-swagger/project.clj
Normal file
9
examples/pedestal-malli-swagger/project.clj
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
(defproject pedestal-malli-swagger-example "0.1.0-SNAPSHOT"
|
||||
:description "Reitit-http with pedestal"
|
||||
:dependencies [[org.clojure/clojure "1.10.0"]
|
||||
[io.pedestal/pedestal.service "0.5.5"]
|
||||
[io.pedestal/pedestal.jetty "0.5.5"]
|
||||
[metosin/reitit-malli "0.5.18"]
|
||||
[metosin/reitit-pedestal "0.5.18"]
|
||||
[metosin/reitit "0.5.18"]]
|
||||
:repl-options {:init-ns server})
|
||||
164
examples/pedestal-malli-swagger/src/server.clj
Normal file
164
examples/pedestal-malli-swagger/src/server.clj
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
(ns example.server
|
||||
(:require [clojure.java.io :as io]
|
||||
[io.pedestal.http.route]
|
||||
[reitit.interceptor]
|
||||
[reitit.dev.pretty :as pretty]
|
||||
[reitit.coercion.malli]
|
||||
[io.pedestal.http]
|
||||
[reitit.ring]
|
||||
[reitit.ring.malli]
|
||||
[reitit.http]
|
||||
[reitit.pedestal]
|
||||
[reitit.swagger :as swagger]
|
||||
[reitit.swagger-ui :as swagger-ui]
|
||||
[reitit.http.coercion :as coercion]
|
||||
[reitit.http.interceptors.parameters :as parameters]
|
||||
[reitit.http.interceptors.muuntaja :as muuntaja]
|
||||
[reitit.http.interceptors.multipart :as multipart]
|
||||
[muuntaja.core]
|
||||
[malli.util :as mu]))
|
||||
|
||||
(defn reitit-routes
|
||||
[_config]
|
||||
[["/swagger.json" {:get {:no-doc true
|
||||
:swagger {:info {:title "my-api"
|
||||
:description "with [malli](https://github.com/metosin/malli) and reitit-ring"}
|
||||
:tags [{:name "files",
|
||||
:description "file api"}
|
||||
{:name "math",
|
||||
:description "math api"}]}
|
||||
:handler (swagger/create-swagger-handler)}}]
|
||||
["/files" {:swagger {:tags ["files"]}}
|
||||
["/upload"
|
||||
{:post {:summary "upload a file"
|
||||
:parameters {:multipart [:map [:file reitit.ring.malli/temp-file-part]]}
|
||||
:responses {200 {:body [:map
|
||||
[:name string?]
|
||||
[:size int?]]}}
|
||||
:handler (fn [{{{{:keys [filename
|
||||
size]} :file}
|
||||
:multipart}
|
||||
:parameters}]
|
||||
{:status 200
|
||||
:body {:name filename
|
||||
:size size}})}}]
|
||||
["/download" {:get {:summary "downloads a file"
|
||||
:swagger {:produces ["image/png"]}
|
||||
:handler (fn [_]
|
||||
{:status 200
|
||||
:headers {"Content-Type" "image/png"}
|
||||
:body (-> "reitit.png"
|
||||
(io/resource)
|
||||
(io/input-stream))})}}]]
|
||||
["/math" {:swagger {:tags ["math"]}}
|
||||
["/plus"
|
||||
{:get {:summary "plus with malli query parameters"
|
||||
:parameters {:query [:map
|
||||
[:x
|
||||
{:title "X parameter"
|
||||
:description "Description for X parameter"
|
||||
:json-schema/default 42}
|
||||
int?]
|
||||
[:y int?]]}
|
||||
:responses {200 {:body [:map [:total int?]]}}
|
||||
:handler (fn [{{{:keys [x
|
||||
y]}
|
||||
:query}
|
||||
:parameters}]
|
||||
{:status 200
|
||||
:body {:total (+ x y)}})}
|
||||
:post {:summary "plus with malli body parameters"
|
||||
:parameters {:body [:map
|
||||
[:x
|
||||
{:title "X parameter"
|
||||
:description "Description for X parameter"
|
||||
:json-schema/default 42}
|
||||
int?]
|
||||
[:y int?]]}
|
||||
:responses {200 {:body [:map [:total int?]]}}
|
||||
:handler (fn [{{{:keys [x
|
||||
y]}
|
||||
:body}
|
||||
:parameters}]
|
||||
{:status 200
|
||||
:body {:total (+ x y)}})}}]]])
|
||||
|
||||
(defn reitit-ring-routes
|
||||
[_config]
|
||||
[(swagger-ui/create-swagger-ui-handler
|
||||
{:path "/"
|
||||
:config {:validatorUrl nil
|
||||
:operationsSorter "alpha"}})
|
||||
(reitit.ring/create-resource-handler)
|
||||
(reitit.ring/create-default-handler)])
|
||||
|
||||
|
||||
(defn reitit-router-config
|
||||
[_config]
|
||||
{:exception pretty/exception
|
||||
:data {:coercion (reitit.coercion.malli/create
|
||||
{:error-keys #{:coercion
|
||||
:in
|
||||
:schema
|
||||
:value
|
||||
:errors
|
||||
:humanized}
|
||||
:compile mu/closed-schema
|
||||
:strip-extra-keys true
|
||||
:default-values true
|
||||
:options nil})
|
||||
:muuntaja muuntaja.core/instance
|
||||
:interceptors [swagger/swagger-feature
|
||||
(parameters/parameters-interceptor)
|
||||
(muuntaja/format-negotiate-interceptor)
|
||||
(muuntaja/format-response-interceptor)
|
||||
(muuntaja/format-request-interceptor)
|
||||
(coercion/coerce-response-interceptor)
|
||||
(coercion/coerce-request-interceptor)
|
||||
(multipart/multipart-interceptor)]}})
|
||||
|
||||
(def config
|
||||
{:env :dev
|
||||
:io.pedestal.http/routes []
|
||||
:io.pedestal.http/type :jetty
|
||||
:io.pedestal.http/port 3000
|
||||
:io.pedestal.http/join? false
|
||||
:io.pedestal.http/secure-headers {:content-security-policy-settings
|
||||
{:default-src "'self'"
|
||||
:style-src "'self' 'unsafe-inline'"
|
||||
:script-src "'self' 'unsafe-inline'"}}
|
||||
::reitit-routes reitit-routes
|
||||
::reitit-ring-routes reitit-ring-routes
|
||||
::reitit-router-config reitit-router-config})
|
||||
|
||||
(defn reitit-http-router
|
||||
[{::keys [reitit-routes
|
||||
reitit-ring-routes
|
||||
reitit-router-config]
|
||||
:as config}]
|
||||
(reitit.pedestal/routing-interceptor
|
||||
(reitit.http/router
|
||||
(reitit-routes config)
|
||||
(reitit-router-config config))
|
||||
(->> config
|
||||
reitit-ring-routes
|
||||
(apply reitit.ring/routes))))
|
||||
|
||||
(defonce server (atom nil))
|
||||
|
||||
(defn start
|
||||
[server
|
||||
config]
|
||||
(when @server
|
||||
(io.pedestal.http/stop @server)
|
||||
(println "server stopped"))
|
||||
(-> config
|
||||
io.pedestal.http/default-interceptors
|
||||
(reitit.pedestal/replace-last-interceptor (reitit-http-router config))
|
||||
io.pedestal.http/dev-interceptors
|
||||
io.pedestal.http/create-server
|
||||
io.pedestal.http/start
|
||||
(->> (reset! server)))
|
||||
(println "server running in port 3000"))
|
||||
|
||||
#_(start server config)
|
||||
|
|
@ -3,6 +3,6 @@
|
|||
:dependencies [[org.clojure/clojure "1.10.0"]
|
||||
[io.pedestal/pedestal.service "0.5.5"]
|
||||
[io.pedestal/pedestal.jetty "0.5.5"]
|
||||
[metosin/reitit-pedestal "0.5.10"]
|
||||
[metosin/reitit "0.5.10"]]
|
||||
[metosin/reitit-pedestal "0.5.18"]
|
||||
[metosin/reitit "0.5.18"]]
|
||||
:repl-options {:init-ns example.server})
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
:dependencies [[org.clojure/clojure "1.10.0"]
|
||||
[io.pedestal/pedestal.service "0.5.5"]
|
||||
[io.pedestal/pedestal.jetty "0.5.5"]
|
||||
[metosin/reitit-pedestal "0.5.10"]
|
||||
[metosin/reitit "0.5.10"]]
|
||||
[metosin/reitit-pedestal "0.5.18"]
|
||||
[metosin/reitit "0.5.18"]]
|
||||
:repl-options {:init-ns example.server})
|
||||
|
|
|
|||
|
|
@ -2,5 +2,5 @@
|
|||
:description "Reitit Ring App"
|
||||
:dependencies [[org.clojure/clojure "1.10.0"]
|
||||
[ring/ring-jetty-adapter "1.7.1"]
|
||||
[metosin/reitit "0.5.10"]]
|
||||
[metosin/reitit "0.5.18"]]
|
||||
:repl-options {:init-ns example.server})
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
:description "Reitit Ring App with Integrant"
|
||||
:dependencies [[org.clojure/clojure "1.10.1"]
|
||||
[ring/ring-jetty-adapter "1.7.1"]
|
||||
[metosin/reitit "0.5.10"]
|
||||
[metosin/reitit "0.5.18"]
|
||||
[integrant "0.7.0"]]
|
||||
:main example.server
|
||||
:repl-options {:init-ns user}
|
||||
|
|
|
|||
11
examples/ring-malli-lite-swagger/.gitignore
vendored
Normal file
11
examples/ring-malli-lite-swagger/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
/target
|
||||
/classes
|
||||
/checkouts
|
||||
pom.xml
|
||||
pom.xml.asc
|
||||
*.jar
|
||||
*.class
|
||||
/.lein-*
|
||||
/.nrepl-port
|
||||
.hgignore
|
||||
.hg/
|
||||
23
examples/ring-malli-lite-swagger/README.md
Normal file
23
examples/ring-malli-lite-swagger/README.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# reitit-ring, malli, swagger
|
||||
|
||||
## Usage
|
||||
|
||||
```clj
|
||||
> lein repl
|
||||
(start)
|
||||
```
|
||||
|
||||
To test the endpoints using [httpie](https://httpie.org/):
|
||||
|
||||
```bash
|
||||
http GET :3000/math/plus x==1 y==20
|
||||
http POST :3000/math/plus x:=1 y:=20
|
||||
|
||||
http GET :3000/swagger.json
|
||||
```
|
||||
|
||||
<img src="https://raw.githubusercontent.com/metosin/reitit/master/examples/ring-spec-swagger/swagger.png" />
|
||||
|
||||
## License
|
||||
|
||||
Copyright © 2017-2019 Metosin Oy
|
||||
8
examples/ring-malli-lite-swagger/project.clj
Normal file
8
examples/ring-malli-lite-swagger/project.clj
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
(defproject ring-example "0.1.0-SNAPSHOT"
|
||||
:description "Reitit Ring App with Swagger"
|
||||
:dependencies [[org.clojure/clojure "1.10.0"]
|
||||
[metosin/jsonista "0.2.6"]
|
||||
[ring/ring-jetty-adapter "1.7.1"]
|
||||
[metosin/reitit "0.5.18"]]
|
||||
:repl-options {:init-ns example.server}
|
||||
:profiles {:dev {:dependencies [[ring/ring-mock "0.3.2"]]}})
|
||||
BIN
examples/ring-malli-lite-swagger/resources/reitit.png
Normal file
BIN
examples/ring-malli-lite-swagger/resources/reitit.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 494 KiB |
123
examples/ring-malli-lite-swagger/src/example/server.clj
Normal file
123
examples/ring-malli-lite-swagger/src/example/server.clj
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
(ns example.server
|
||||
(:require [reitit.ring :as ring]
|
||||
[reitit.coercion.malli]
|
||||
[reitit.ring.malli]
|
||||
[reitit.swagger :as swagger]
|
||||
[reitit.swagger-ui :as swagger-ui]
|
||||
[reitit.ring.coercion :as coercion]
|
||||
[reitit.dev.pretty :as pretty]
|
||||
[reitit.ring.middleware.muuntaja :as muuntaja]
|
||||
[reitit.ring.middleware.exception :as exception]
|
||||
[reitit.ring.middleware.multipart :as multipart]
|
||||
[reitit.ring.middleware.parameters :as parameters]
|
||||
; [reitit.ring.middleware.dev :as dev]
|
||||
; [reitit.ring.spec :as spec]
|
||||
; [spec-tools.spell :as spell]
|
||||
[ring.adapter.jetty :as jetty]
|
||||
[muuntaja.core :as m]
|
||||
[clojure.java.io :as io]
|
||||
[malli.util :as mu]))
|
||||
|
||||
(def app
|
||||
(ring/ring-handler
|
||||
(ring/router
|
||||
[["/swagger.json"
|
||||
{:get {:no-doc true
|
||||
:swagger {:info {:title "my-api"
|
||||
:description "with [malli](https://github.com/metosin/malli) and reitit-ring"}
|
||||
:tags [{:name "files", :description "file api"}
|
||||
{:name "math", :description "math api"}]}
|
||||
:handler (swagger/create-swagger-handler)}}]
|
||||
|
||||
["/files"
|
||||
{:swagger {:tags ["files"]}}
|
||||
|
||||
["/upload"
|
||||
{:post {:summary "upload a file"
|
||||
:parameters {:multipart {:file reitit.ring.malli/temp-file-part}}
|
||||
:responses {200 {:body {:name :string, :size :int}}}
|
||||
:handler (fn [{{{:keys [file]} :multipart} :parameters}]
|
||||
{:status 200
|
||||
:body {:name (:filename file)
|
||||
:size (:size file)}})}}]
|
||||
|
||||
["/download"
|
||||
{:get {:summary "downloads a file"
|
||||
:swagger {:produces ["image/png"]}
|
||||
:handler (fn [_]
|
||||
{:status 200
|
||||
:headers {"Content-Type" "image/png"}
|
||||
:body (-> "reitit.png"
|
||||
(io/resource)
|
||||
(io/input-stream))})}}]]
|
||||
|
||||
["/math"
|
||||
{:swagger {:tags ["math"]}}
|
||||
|
||||
["/plus"
|
||||
{:get {:summary "plus with malli query parameters"
|
||||
:parameters {:query {:x [:int {:title "X parameter"
|
||||
:description "Description for X parameter"
|
||||
:json-schema/default 42}]
|
||||
:y :int}}
|
||||
:responses {200 {:body {:total :int}}}
|
||||
:handler (fn [{{{:keys [x y]} :query} :parameters}]
|
||||
{:status 200
|
||||
:body {:total (+ x y)}})}
|
||||
:post {:summary "plus with malli body parameters"
|
||||
:parameters {:body {:x [:int {:title "X parameter"
|
||||
:description "Description for X parameter"
|
||||
:json-schema/default 42}]
|
||||
:y :int}}
|
||||
:responses {200 {:body {:total :int}}}
|
||||
:handler (fn [{{{:keys [x y]} :body} :parameters}]
|
||||
{:status 200
|
||||
:body {:total (+ x y)}})}}]]]
|
||||
|
||||
{;;:reitit.middleware/transform dev/print-request-diffs ;; pretty diffs
|
||||
;;:validate spec/validate ;; enable spec validation for route data
|
||||
;;:reitit.spec/wrap spell/closed ;; strict top-level validation
|
||||
:exception pretty/exception
|
||||
:data {:coercion (reitit.coercion.malli/create
|
||||
{;; set of keys to include in error messages
|
||||
:error-keys #{#_:type :coercion :in :schema :value :errors :humanized #_:transformed}
|
||||
;; schema identity function (default: close all map schemas)
|
||||
:compile mu/closed-schema
|
||||
;; strip-extra-keys (effects only predefined transformers)
|
||||
:strip-extra-keys true
|
||||
;; add/set default values
|
||||
:default-values true
|
||||
;; malli options
|
||||
:options nil})
|
||||
:muuntaja m/instance
|
||||
:middleware [;; swagger feature
|
||||
swagger/swagger-feature
|
||||
;; query-params & form-params
|
||||
parameters/parameters-middleware
|
||||
;; content-negotiation
|
||||
muuntaja/format-negotiate-middleware
|
||||
;; encoding response body
|
||||
muuntaja/format-response-middleware
|
||||
;; exception handling
|
||||
exception/exception-middleware
|
||||
;; decoding request body
|
||||
muuntaja/format-request-middleware
|
||||
;; coercing response bodys
|
||||
coercion/coerce-response-middleware
|
||||
;; coercing request parameters
|
||||
coercion/coerce-request-middleware
|
||||
;; multipart
|
||||
multipart/multipart-middleware]}})
|
||||
(ring/routes
|
||||
(swagger-ui/create-swagger-ui-handler
|
||||
{:path "/"
|
||||
:config {:validatorUrl nil
|
||||
:operationsSorter "alpha"}})
|
||||
(ring/create-default-handler))))
|
||||
|
||||
(defn start []
|
||||
(jetty/run-jetty #'app {:port 3000, :join? false})
|
||||
(println "server running in port 3000"))
|
||||
|
||||
(comment
|
||||
(start))
|
||||
BIN
examples/ring-malli-lite-swagger/swagger.png
Normal file
BIN
examples/ring-malli-lite-swagger/swagger.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 228 KiB |
|
|
@ -0,0 +1,38 @@
|
|||
(ns example.server-test
|
||||
(:require [clojure.test :refer [deftest testing is]]
|
||||
[example.server :refer [app]]
|
||||
[ring.mock.request :refer [request json-body]]))
|
||||
|
||||
(deftest example-server
|
||||
|
||||
(testing "GET"
|
||||
(is (= (-> (request :get "/math/plus?x=20&y=3")
|
||||
app :body slurp)
|
||||
(-> {:request-method :get :uri "/math/plus" :query-string "x=20&y=3"}
|
||||
app :body slurp)
|
||||
(-> {:request-method :get :uri "/math/plus" :query-params {:x 20 :y 3}}
|
||||
app :body slurp)
|
||||
"{\"total\":23}")))
|
||||
|
||||
(testing "POST"
|
||||
(is (= (-> (request :post "/math/plus") (json-body {:x 40 :y 2})
|
||||
app :body slurp)
|
||||
(-> {:request-method :post :uri "/math/plus" :body-params {:x 40 :y 2}}
|
||||
app :body slurp)
|
||||
"{\"total\":42}")))
|
||||
|
||||
(testing "Download"
|
||||
(is (= (-> {:request-method :get :uri "/files/download"}
|
||||
app :body (#(slurp % :encoding "ascii")) count) ;; binary
|
||||
(.length (clojure.java.io/file "resources/reitit.png"))
|
||||
506325)))
|
||||
|
||||
(testing "Upload"
|
||||
(let [file (clojure.java.io/file "resources/reitit.png")
|
||||
multipart-temp-file-part {:tempfile file
|
||||
:size (.length file)
|
||||
:filename (.getName file)
|
||||
:content-type "image/png;"}]
|
||||
(is (= (-> {:request-method :post :uri "/files/upload" :multipart-params {:file multipart-temp-file-part}}
|
||||
app :body slurp)
|
||||
"{\"name\":\"reitit.png\",\"size\":506325}")))))
|
||||
|
|
@ -3,6 +3,6 @@
|
|||
:dependencies [[org.clojure/clojure "1.10.0"]
|
||||
[metosin/jsonista "0.2.6"]
|
||||
[ring/ring-jetty-adapter "1.7.1"]
|
||||
[metosin/reitit "0.5.10"]]
|
||||
[metosin/reitit "0.5.18"]]
|
||||
:repl-options {:init-ns example.server}
|
||||
:profiles {:dev {:dependencies [[ring/ring-mock "0.3.2"]]}})
|
||||
|
|
|
|||
|
|
@ -56,13 +56,25 @@
|
|||
|
||||
["/plus"
|
||||
{:get {:summary "plus with malli query parameters"
|
||||
:parameters {:query [:map [:x int?] [:y int?]]}
|
||||
:parameters {:query [:map
|
||||
[:x
|
||||
{:title "X parameter"
|
||||
:description "Description for X parameter"
|
||||
:json-schema/default 42}
|
||||
int?]
|
||||
[:y int?]]}
|
||||
:responses {200 {:body [:map [:total int?]]}}
|
||||
:handler (fn [{{{:keys [x y]} :query} :parameters}]
|
||||
{:status 200
|
||||
:body {:total (+ x y)}})}
|
||||
:post {:summary "plus with malli body parameters"
|
||||
:parameters {:body [:map [:x int?] [:y int?]]}
|
||||
:parameters {:body [:map
|
||||
[:x
|
||||
{:title "X parameter"
|
||||
:description "Description for X parameter"
|
||||
:json-schema/default 42}
|
||||
int?]
|
||||
[:y int?]]}
|
||||
:responses {200 {:body [:map [:total int?]]}}
|
||||
:handler (fn [{{{:keys [x y]} :body} :parameters}]
|
||||
{:status 200
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@
|
|||
:description "Reitit Ring App with Swagger"
|
||||
:dependencies [[org.clojure/clojure "1.10.0"]
|
||||
[ring/ring-jetty-adapter "1.7.1"]
|
||||
[metosin/reitit "0.5.10"]]
|
||||
[metosin/reitit "0.5.18"]]
|
||||
:repl-options {:init-ns example.server}
|
||||
:profiles {:dev {:dependencies [[ring/ring-mock "0.3.2"]]}})
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
; [reitit.ring.middleware.dev :as dev]
|
||||
; [reitit.ring.spec :as spec]
|
||||
; [spec-tools.spell :as spell]
|
||||
[spec-tools.core :as st]
|
||||
[ring.adapter.jetty :as jetty]
|
||||
[muuntaja.core :as m]
|
||||
[clojure.spec.alpha :as s]
|
||||
|
|
@ -25,7 +26,12 @@
|
|||
(s/def ::size int?)
|
||||
(s/def ::file-response (s/keys :req-un [::name ::size]))
|
||||
|
||||
(s/def ::x int?)
|
||||
;; Use data-specs to provide extra JSON-Schema properties:
|
||||
;; https://github.com/metosin/spec-tools/blob/master/docs/04_json_schema.md#annotated-specs
|
||||
(s/def ::x (st/spec {:spec int?
|
||||
:name "X parameter"
|
||||
:description "Description for X parameter"
|
||||
:json-schema/default 42}))
|
||||
(s/def ::y int?)
|
||||
(s/def ::total int?)
|
||||
(s/def ::math-request (s/keys :req-un [::x ::y]))
|
||||
|
|
|
|||
|
|
@ -2,5 +2,5 @@
|
|||
:description "Reitit Ring App with Swagger"
|
||||
:dependencies [[org.clojure/clojure "1.10.0"]
|
||||
[ring/ring-jetty-adapter "1.7.1"]
|
||||
[metosin/reitit "0.5.10"]]
|
||||
[metosin/reitit "0.5.18"]]
|
||||
:repl-options {:init-ns example.server})
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@
|
|||
[reitit.ring.middleware.parameters :as parameters]
|
||||
;; Uncomment to use
|
||||
; [reitit.ring.middleware.dev :as dev]
|
||||
; [reitit.ring.spec :as spec]
|
||||
; [spec-tools.spell :as spell]
|
||||
[ring.adapter.jetty :as jetty]
|
||||
[muuntaja.core :as m]
|
||||
[clojure.java.io :as io]))
|
||||
|
|
@ -53,13 +51,15 @@
|
|||
|
||||
["/plus"
|
||||
{:get {:summary "plus with spec query parameters"
|
||||
:parameters {:query {:x int?, :y int?}}
|
||||
:parameters {:query {:x int?
|
||||
:y int?}}
|
||||
:responses {200 {:body {:total int?}}}
|
||||
:handler (fn [{{{:keys [x y]} :query} :parameters}]
|
||||
{:status 200
|
||||
:body {:total (+ x y)}})}
|
||||
:post {:summary "plus with spec body parameters"
|
||||
:parameters {:body {:x int?, :y int?}}
|
||||
:parameters {:body {:x int?
|
||||
:y int?}}
|
||||
:responses {200 {:body {:total int?}}}
|
||||
:handler (fn [{{{:keys [x y]} :body} :parameters}]
|
||||
{:status 200
|
||||
|
|
@ -80,7 +80,8 @@
|
|||
;; encoding response body
|
||||
muuntaja/format-response-middleware
|
||||
;; exception handling
|
||||
exception/exception-middleware
|
||||
(exception/create-exception-middleware
|
||||
{::exception/default (partial exception/wrap-log-to-console exception/default-handler)})
|
||||
;; decoding request body
|
||||
muuntaja/format-request-middleware
|
||||
;; coercing response bodys
|
||||
|
|
|
|||
1
modules/reitit-core/.clj-kondo/config.edn
Symbolic link
1
modules/reitit-core/.clj-kondo/config.edn
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../.clj-kondo/module_config.edn
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
(defproject metosin/reitit-core "0.5.10"
|
||||
(defproject metosin/reitit-core "0.5.18"
|
||||
:description "Snappy data-driven router for Clojure(Script)"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
|
|||
|
|
@ -41,36 +41,44 @@
|
|||
:header (->ParameterCoercion :headers :string true true)
|
||||
:path (->ParameterCoercion :path-params :string true true)})
|
||||
|
||||
(defn ^:no-doc request-coercion-failed! [result coercion value in request]
|
||||
(defn ^:no-doc request-coercion-failed! [result coercion value in request serialize-failed-result]
|
||||
(throw
|
||||
(ex-info
|
||||
(ex-info
|
||||
(if serialize-failed-result
|
||||
(str "Request coercion failed: " (pr-str result))
|
||||
(merge
|
||||
(into {} result)
|
||||
{:type ::request-coercion
|
||||
:coercion coercion
|
||||
:value value
|
||||
:in [:request in]
|
||||
:request request}))))
|
||||
"Request coercion failed")
|
||||
(-> {}
|
||||
transient
|
||||
(as-> $ (reduce conj! $ result))
|
||||
(assoc! :type ::request-coercion)
|
||||
(assoc! :coercion coercion)
|
||||
(assoc! :value value)
|
||||
(assoc! :in [:request in])
|
||||
(assoc! :request request)
|
||||
persistent!))))
|
||||
|
||||
(defn ^:no-doc response-coercion-failed! [result coercion value request response]
|
||||
(defn ^:no-doc response-coercion-failed! [result coercion value request response serialize-failed-result]
|
||||
(throw
|
||||
(ex-info
|
||||
(ex-info
|
||||
(if serialize-failed-result
|
||||
(str "Response coercion failed: " (pr-str result))
|
||||
(merge
|
||||
(into {} result)
|
||||
{:type ::response-coercion
|
||||
:coercion coercion
|
||||
:value value
|
||||
:in [:response :body]
|
||||
:request request
|
||||
:response response}))))
|
||||
"Response coercion failed")
|
||||
(-> {}
|
||||
transient
|
||||
(as-> $ (reduce conj! $ result))
|
||||
(assoc! :type ::response-coercion)
|
||||
(assoc! :coercion coercion)
|
||||
(assoc! :value value)
|
||||
(assoc! :in [:response :body])
|
||||
(assoc! :request request)
|
||||
(assoc! :response response)
|
||||
persistent!))))
|
||||
|
||||
(defn extract-request-format-default [request]
|
||||
(-> request :muuntaja/request :format))
|
||||
|
||||
;; TODO: support faster key walking, walk/keywordize-keys is quite slow...
|
||||
(defn request-coercer [coercion type model {::keys [extract-request-format parameter-coercion]
|
||||
(defn request-coercer [coercion type model {::keys [extract-request-format parameter-coercion serialize-failed-result]
|
||||
:or {extract-request-format extract-request-format-default
|
||||
parameter-coercion default-parameter-coercion}}]
|
||||
(if coercion
|
||||
|
|
@ -83,13 +91,13 @@
|
|||
format (extract-request-format request)
|
||||
result (coercer value format)]
|
||||
(if (error? result)
|
||||
(request-coercion-failed! result coercion value in request)
|
||||
(request-coercion-failed! result coercion value in request serialize-failed-result)
|
||||
result))))))))
|
||||
|
||||
(defn extract-response-format-default [request _]
|
||||
(-> request :muuntaja/response :format))
|
||||
|
||||
(defn response-coercer [coercion body {:keys [extract-response-format]
|
||||
(defn response-coercer [coercion body {:keys [extract-response-format serialize-failed-result]
|
||||
:or {extract-response-format extract-response-format-default}}]
|
||||
(if coercion
|
||||
(if-let [coercer (-response-coercer coercion body)]
|
||||
|
|
@ -98,7 +106,7 @@
|
|||
value (:body response)
|
||||
result (coercer value format)]
|
||||
(if (error? result)
|
||||
(response-coercion-failed! result coercion value request response)
|
||||
(response-coercion-failed! result coercion value request response serialize-failed-result)
|
||||
result))))))
|
||||
|
||||
(defn encode-error [data]
|
||||
|
|
@ -109,9 +117,9 @@
|
|||
|
||||
(defn coerce-request [coercers request]
|
||||
(reduce-kv
|
||||
(fn [acc k coercer]
|
||||
(impl/fast-assoc acc k (coercer request)))
|
||||
{} coercers))
|
||||
(fn [acc k coercer]
|
||||
(impl/fast-assoc acc k (coercer request)))
|
||||
{} coercers))
|
||||
|
||||
(defn coerce-response [coercers request response]
|
||||
(if response
|
||||
|
|
@ -147,13 +155,13 @@
|
|||
:multipart :formData}]
|
||||
(case specification
|
||||
:swagger (->> (update
|
||||
data
|
||||
:parameters
|
||||
(fn [parameters]
|
||||
(->> parameters
|
||||
(map (fn [[k v]] [(swagger-parameter k) v]))
|
||||
(filter first)
|
||||
(into {}))))
|
||||
data
|
||||
:parameters
|
||||
(fn [parameters]
|
||||
(->> parameters
|
||||
(map (fn [[k v]] [(swagger-parameter k) v]))
|
||||
(filter first)
|
||||
(into {}))))
|
||||
(-get-apidocs coercion specification)))))
|
||||
|
||||
;;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
(ns reitit.core
|
||||
(:require [reitit.impl :as impl]
|
||||
[reitit.exception :as exception]
|
||||
(:require [reitit.exception :as exception]
|
||||
[reitit.impl :as impl]
|
||||
[reitit.trie :as trie]))
|
||||
|
||||
;;
|
||||
|
|
@ -61,7 +61,7 @@
|
|||
(if-not (partial-match? match)
|
||||
match
|
||||
(impl/throw-on-missing-path-params
|
||||
(:template match) (:required match) path-params)))))
|
||||
(:template match) (:required match) path-params)))))
|
||||
|
||||
(defn match->path
|
||||
([match]
|
||||
|
|
@ -87,15 +87,15 @@
|
|||
(let [compiler (::trie/trie-compiler opts (trie/compiler))
|
||||
names (impl/find-names compiled-routes opts)
|
||||
[pl nl] (reduce
|
||||
(fn [[pl nl] [p {:keys [name] :as data} result]]
|
||||
(let [{:keys [path-params] :as route} (impl/parse p opts)
|
||||
f #(if-let [path (impl/path-for route %)]
|
||||
(->Match p data result (impl/url-decode-coll %) path)
|
||||
(->PartialMatch p data result (impl/url-decode-coll %) path-params))]
|
||||
[(conj pl (-> (trie/insert nil p (->Match p data result nil nil) opts) (trie/compile)))
|
||||
(if name (assoc nl name f) nl)]))
|
||||
[[] {}]
|
||||
compiled-routes)
|
||||
(fn [[pl nl] [p {:keys [name] :as data} result]]
|
||||
(let [{:keys [path-params] :as route} (impl/parse p opts)
|
||||
f #(if-let [path (impl/path-for route %)]
|
||||
(->Match p data result (impl/url-decode-coll %) path)
|
||||
(->PartialMatch p data result (impl/url-decode-coll %) path-params))]
|
||||
[(conj pl (-> (trie/insert nil p (->Match p data result nil nil) opts) (trie/compile)))
|
||||
(if name (assoc nl name f) nl)]))
|
||||
[[] {}]
|
||||
compiled-routes)
|
||||
lookup (impl/fast-map nl)
|
||||
matcher (trie/linear-matcher compiler pl true)
|
||||
match-by-path (trie/path-matcher matcher compiler)
|
||||
|
|
@ -103,16 +103,11 @@
|
|||
^{:type ::router}
|
||||
(reify
|
||||
Router
|
||||
(router-name [_]
|
||||
:linear-router)
|
||||
(routes [_]
|
||||
routes)
|
||||
(compiled-routes [_]
|
||||
compiled-routes)
|
||||
(options [_]
|
||||
opts)
|
||||
(route-names [_]
|
||||
names)
|
||||
(router-name [_] :linear-router)
|
||||
(routes [_] routes)
|
||||
(compiled-routes [_] compiled-routes)
|
||||
(options [_] opts)
|
||||
(route-names [_] names)
|
||||
(match-by-path [_ path]
|
||||
(if-let [match (match-by-path path)]
|
||||
(-> (:data match)
|
||||
|
|
@ -133,33 +128,28 @@
|
|||
([compiled-routes opts]
|
||||
(when-let [wilds (seq (filter (impl/->wild-route? opts) compiled-routes))]
|
||||
(exception/fail!
|
||||
(str "can't create :lookup-router with wildcard routes: " wilds)
|
||||
{:wilds wilds
|
||||
:routes compiled-routes}))
|
||||
(str "can't create :lookup-router with wildcard routes: " wilds)
|
||||
{:wilds wilds
|
||||
:routes compiled-routes}))
|
||||
(let [names (impl/find-names compiled-routes opts)
|
||||
[pl nl] (reduce
|
||||
(fn [[pl nl] [p {:keys [name] :as data} result]]
|
||||
[(assoc pl p (->Match p data result {} p))
|
||||
(if name
|
||||
(assoc nl name #(->Match p data result % p))
|
||||
nl)])
|
||||
[{} {}]
|
||||
compiled-routes)
|
||||
(fn [[pl nl] [p {:keys [name] :as data} result]]
|
||||
[(assoc pl p (->Match p data result {} p))
|
||||
(if name
|
||||
(assoc nl name #(->Match p data result % p))
|
||||
nl)])
|
||||
[{} {}]
|
||||
compiled-routes)
|
||||
data (impl/fast-map pl)
|
||||
lookup (impl/fast-map nl)
|
||||
routes (impl/uncompile-routes compiled-routes)]
|
||||
^{:type ::router}
|
||||
(reify Router
|
||||
(router-name [_]
|
||||
:lookup-router)
|
||||
(routes [_]
|
||||
routes)
|
||||
(compiled-routes [_]
|
||||
compiled-routes)
|
||||
(options [_]
|
||||
opts)
|
||||
(route-names [_]
|
||||
names)
|
||||
(router-name [_] :lookup-router)
|
||||
(routes [_] routes)
|
||||
(compiled-routes [_] compiled-routes)
|
||||
(options [_] opts)
|
||||
(route-names [_] names)
|
||||
(match-by-path [_ path]
|
||||
(impl/fast-get data path))
|
||||
(match-by-name [_ name]
|
||||
|
|
@ -183,34 +173,29 @@
|
|||
(let [compiler (::trie/trie-compiler opts (trie/compiler))
|
||||
names (impl/find-names compiled-routes opts)
|
||||
[pl nl] (reduce
|
||||
(fn [[pl nl] [p {:keys [name] :as data} result]]
|
||||
(let [{:keys [path-params] :as route} (impl/parse p opts)
|
||||
f #(if-let [path (impl/path-for route %)]
|
||||
(->Match p data result (impl/url-decode-coll %) path)
|
||||
(->PartialMatch p data result (impl/url-decode-coll %) path-params))]
|
||||
[(trie/insert pl p (->Match p data result nil nil) opts)
|
||||
(if name (assoc nl name f) nl)]))
|
||||
[nil {}]
|
||||
compiled-routes)
|
||||
(fn [[pl nl] [p {:keys [name] :as data} result]]
|
||||
(let [{:keys [path-params] :as route} (impl/parse p opts)
|
||||
f #(if-let [path (impl/path-for route %)]
|
||||
(->Match p data result (impl/url-decode-coll %) path)
|
||||
(->PartialMatch p data result (impl/url-decode-coll %) path-params))]
|
||||
[(trie/insert pl p (->Match p data result nil nil) opts)
|
||||
(if name (assoc nl name f) nl)]))
|
||||
[nil {}]
|
||||
compiled-routes)
|
||||
matcher (trie/compile pl compiler)
|
||||
match-by-path (trie/path-matcher matcher compiler)
|
||||
match-by-path (if matcher (trie/path-matcher matcher compiler))
|
||||
lookup (impl/fast-map nl)
|
||||
routes (impl/uncompile-routes compiled-routes)]
|
||||
^{:type ::router}
|
||||
(reify
|
||||
Router
|
||||
(router-name [_]
|
||||
:trie-router)
|
||||
(routes [_]
|
||||
routes)
|
||||
(compiled-routes [_]
|
||||
compiled-routes)
|
||||
(options [_]
|
||||
opts)
|
||||
(route-names [_]
|
||||
names)
|
||||
(router-name [_] :trie-router)
|
||||
(routes [_] routes)
|
||||
(compiled-routes [_] compiled-routes)
|
||||
(options [_] opts)
|
||||
(route-names [_] names)
|
||||
(match-by-path [_ path]
|
||||
(if-let [match (match-by-path path)]
|
||||
(if-let [match (and match-by-path (match-by-path path))]
|
||||
(-> (:data match)
|
||||
(assoc :path-params (:params match))
|
||||
(assoc :path path))))
|
||||
|
|
@ -229,8 +214,8 @@
|
|||
([compiled-routes opts]
|
||||
(when (or (not= (count compiled-routes) 1) (some (impl/->wild-route? opts) compiled-routes))
|
||||
(exception/fail!
|
||||
(str ":single-static-path-router requires exactly 1 static route: " compiled-routes)
|
||||
{:routes compiled-routes}))
|
||||
(str ":single-static-path-router requires exactly 1 static route: " compiled-routes)
|
||||
{:routes compiled-routes}))
|
||||
(let [[n :as names] (impl/find-names compiled-routes opts)
|
||||
[[p data result]] compiled-routes
|
||||
p #?(:clj (.intern ^String p) :cljs p)
|
||||
|
|
@ -238,25 +223,17 @@
|
|||
routes (impl/uncompile-routes compiled-routes)]
|
||||
^{:type ::router}
|
||||
(reify Router
|
||||
(router-name [_]
|
||||
:single-static-path-router)
|
||||
(routes [_]
|
||||
routes)
|
||||
(compiled-routes [_]
|
||||
compiled-routes)
|
||||
(options [_]
|
||||
opts)
|
||||
(route-names [_]
|
||||
names)
|
||||
(router-name [_] :single-static-path-router)
|
||||
(routes [_] routes)
|
||||
(compiled-routes [_] compiled-routes)
|
||||
(options [_] opts)
|
||||
(route-names [_] names)
|
||||
(match-by-path [_ path]
|
||||
(if (#?(:clj .equals :cljs =) p path)
|
||||
match))
|
||||
(if (#?(:clj .equals :cljs =) p path) match))
|
||||
(match-by-name [_ name]
|
||||
(if (= n name)
|
||||
match))
|
||||
(if (= n name) match))
|
||||
(match-by-name [_ name path-params]
|
||||
(if (= n name)
|
||||
(impl/fast-assoc match :path-params (impl/path-params path-params))))))))
|
||||
(if (= n name) (impl/fast-assoc match :path-params (impl/path-params path-params))))))))
|
||||
|
||||
(defn mixed-router
|
||||
"Creates two routers: [[lookup-router]] or [[single-static-path-router]] for
|
||||
|
|
@ -274,16 +251,11 @@
|
|||
routes (impl/uncompile-routes compiled-routes)]
|
||||
^{:type ::router}
|
||||
(reify Router
|
||||
(router-name [_]
|
||||
:mixed-router)
|
||||
(routes [_]
|
||||
routes)
|
||||
(compiled-routes [_]
|
||||
compiled-routes)
|
||||
(options [_]
|
||||
opts)
|
||||
(route-names [_]
|
||||
names)
|
||||
(router-name [_] :mixed-router)
|
||||
(routes [_] routes)
|
||||
(compiled-routes [_] compiled-routes)
|
||||
(options [_] opts)
|
||||
(route-names [_] names)
|
||||
(match-by-path [_ path]
|
||||
(or (match-by-path static-router path)
|
||||
(match-by-path wildcard-router path)))
|
||||
|
|
@ -310,16 +282,11 @@
|
|||
routes (impl/uncompile-routes compiled-routes)]
|
||||
^{:type ::router}
|
||||
(reify Router
|
||||
(router-name [_]
|
||||
:quarantine-router)
|
||||
(routes [_]
|
||||
routes)
|
||||
(compiled-routes [_]
|
||||
compiled-routes)
|
||||
(options [_]
|
||||
opts)
|
||||
(route-names [_]
|
||||
names)
|
||||
(router-name [_] :quarantine-router)
|
||||
(routes [_] routes)
|
||||
(compiled-routes [_] compiled-routes)
|
||||
(options [_] opts)
|
||||
(route-names [_] names)
|
||||
(match-by-path [_ path]
|
||||
(or (match-by-path mixed-router path)
|
||||
(match-by-path linear-router path)))
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@
|
|||
(map (fn [provide]
|
||||
(when (contains? acc provide)
|
||||
(exception/fail!
|
||||
(str "multiple providers for: " provide)
|
||||
{::multiple-providers provide}))
|
||||
(str "multiple providers for: " provide)
|
||||
{::multiple-providers provide}))
|
||||
[provide dependent]))
|
||||
(get-provides dependent)))
|
||||
{} nodes))
|
||||
|
|
@ -22,8 +22,8 @@
|
|||
(if (contains? providers k)
|
||||
(get providers k)
|
||||
(exception/fail!
|
||||
(str "provider missing for dependency: " k)
|
||||
{::missing-provider k})))
|
||||
(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.
|
||||
|
|
|
|||
|
|
@ -31,20 +31,20 @@
|
|||
path " " (not-empty (select-keys route-data [:conflicting]))))]
|
||||
(apply str "Router contains conflicting route paths:\n\n"
|
||||
(mapv
|
||||
(fn [[[path route-data] vals]]
|
||||
(str (resolve-str path route-data)
|
||||
"\n"
|
||||
(str/join "\n" (mapv (fn [[path route-data]]
|
||||
(resolve-str path route-data)) vals))
|
||||
"\n\n"))
|
||||
conflicts))))
|
||||
(fn [[[path route-data] vals]]
|
||||
(str (resolve-str path route-data)
|
||||
"\n"
|
||||
(str/join "\n" (mapv (fn [[path route-data]]
|
||||
(resolve-str path route-data)) vals))
|
||||
"\n\n"))
|
||||
conflicts))))
|
||||
|
||||
(defmethod format-exception :name-conflicts [_ _ conflicts]
|
||||
(apply str "Router contains conflicting route names:\n\n"
|
||||
(mapv
|
||||
(fn [[name vals]]
|
||||
(str name "\n-> " (str/join "\n-> " (mapv first vals)) "\n"))
|
||||
conflicts)))
|
||||
(fn [[name vals]]
|
||||
(str name "\n-> " (str/join "\n-> " (mapv first vals)) "\n"))
|
||||
conflicts)))
|
||||
|
||||
(defmethod format-exception :reitit.impl/merge-data [_ _ data]
|
||||
(str "Error merging route-data\n\n" (pr-str data)))
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
(ns ^:no-doc reitit.impl
|
||||
#?(:cljs (:require-macros [reitit.impl]))
|
||||
(:require [clojure.string :as str]
|
||||
[clojure.set :as set]
|
||||
(:require [clojure.set :as set]
|
||||
[clojure.string :as str]
|
||||
[meta-merge.core :as mm]
|
||||
[reitit.trie :as trie]
|
||||
[reitit.exception :as ex])
|
||||
[reitit.exception :as ex]
|
||||
[reitit.trie :as trie])
|
||||
#?(:clj
|
||||
(:import (java.util HashMap Map)
|
||||
(java.net URLEncoder URLDecoder))))
|
||||
(:import (java.net URLEncoder URLDecoder)
|
||||
(java.util HashMap Map))))
|
||||
|
||||
(defn parse [path opts]
|
||||
(let [path #?(:clj (.intern ^String (trie/normalize path opts)) :cljs (trie/normalize path opts))
|
||||
|
|
@ -28,59 +28,59 @@
|
|||
Also works on vectors. Maintains key for maps, order for vectors."
|
||||
[f coll]
|
||||
(reduce-kv
|
||||
(fn [coll k v]
|
||||
(if-some [v' (f v)]
|
||||
(assoc coll k v')
|
||||
coll))
|
||||
coll
|
||||
coll))
|
||||
(fn [coll k v]
|
||||
(if-some [v' (f v)]
|
||||
(assoc coll k v')
|
||||
coll))
|
||||
coll
|
||||
coll))
|
||||
|
||||
(defn walk [raw-routes {:keys [path data routes expand]
|
||||
:or {data [], routes []}
|
||||
:as opts}]
|
||||
(letfn
|
||||
[(walk-many [p m r]
|
||||
(reduce #(into %1 (walk-one p m %2)) [] r))
|
||||
(walk-one [pacc macc routes]
|
||||
(if (vector? (first routes))
|
||||
(walk-many pacc macc routes)
|
||||
(when (string? (first routes))
|
||||
(let [[path & [maybe-arg :as args]] routes
|
||||
[data childs] (if (or (vector? maybe-arg)
|
||||
(and (sequential? maybe-arg)
|
||||
(sequential? (first maybe-arg)))
|
||||
(nil? maybe-arg))
|
||||
[{} args]
|
||||
[maybe-arg (rest args)])
|
||||
macc (into macc (expand data opts))
|
||||
child-routes (walk-many (str pacc path) macc (keep identity childs))]
|
||||
(if (seq childs) (seq child-routes) [[(str pacc path) macc]])))))]
|
||||
[(walk-many [p m r]
|
||||
(reduce #(into %1 (walk-one p m %2)) [] r))
|
||||
(walk-one [pacc macc routes]
|
||||
(if (vector? (first routes))
|
||||
(walk-many pacc macc routes)
|
||||
(when (string? (first routes))
|
||||
(let [[path & [maybe-arg :as args]] routes
|
||||
[data childs] (if (or (vector? maybe-arg)
|
||||
(and (sequential? maybe-arg)
|
||||
(sequential? (first maybe-arg)))
|
||||
(nil? maybe-arg))
|
||||
[{} args]
|
||||
[maybe-arg (rest args)])
|
||||
macc (into macc (expand data opts))
|
||||
child-routes (walk-many (str pacc path) macc (keep identity childs))]
|
||||
(if (seq childs) (seq child-routes) [[(str pacc path) macc]])))))]
|
||||
(walk-one path (mapv identity data) raw-routes)))
|
||||
|
||||
(defn map-data [f routes]
|
||||
(mapv (fn [[p ds]] [p (f p ds)]) routes))
|
||||
|
||||
(defn merge-data [p x]
|
||||
(defn merge-data [{:keys [meta-merge-fn] :as g} p x]
|
||||
(reduce
|
||||
(fn [acc [k v]]
|
||||
(try
|
||||
(mm/meta-merge acc {k v})
|
||||
(catch #?(:clj Exception, :cljs js/Error) e
|
||||
(ex/fail! ::merge-data {:path p, :left acc, :right {k v}, :exception e}))))
|
||||
{} x))
|
||||
(fn [acc [k v]]
|
||||
(try
|
||||
((or meta-merge-fn mm/meta-merge) acc {k v})
|
||||
(catch #?(:clj Exception, :cljs js/Error) e
|
||||
(ex/fail! ::merge-data {:path p, :left acc, :right {k v}, :exception e}))))
|
||||
{} x))
|
||||
|
||||
(defn resolve-routes [raw-routes {:keys [coerce] :as opts}]
|
||||
(cond->> (->> (walk raw-routes opts) (map-data merge-data))
|
||||
coerce (into [] (keep #(coerce % opts)))))
|
||||
(cond->> (->> (walk raw-routes opts) (map-data #(merge-data opts %1 %2)))
|
||||
coerce (into [] (keep #(coerce % opts)))))
|
||||
|
||||
(defn path-conflicting-routes [routes opts]
|
||||
(let [parts-and-routes (mapv (fn [[s :as r]] [(trie/split-path s opts) r]) routes)]
|
||||
(-> (into {} (comp (map-indexed (fn [index [p r]]
|
||||
[r (reduce
|
||||
(fn [acc [p' r']]
|
||||
(if (trie/conflicting-parts? p p')
|
||||
(conj acc r') acc))
|
||||
#{} (subvec parts-and-routes (inc index)))]))
|
||||
(fn [acc [p' r']]
|
||||
(if (trie/conflicting-parts? p p')
|
||||
(conj acc r') acc))
|
||||
#{} (subvec parts-and-routes (inc index)))]))
|
||||
(filter (comp seq second))) parts-and-routes)
|
||||
(not-empty))))
|
||||
|
||||
|
|
@ -123,13 +123,13 @@
|
|||
(defn path-for [route path-params]
|
||||
(if (:path-params route)
|
||||
(if-let [parts (reduce
|
||||
(fn [acc part]
|
||||
(if (string? part)
|
||||
(conj acc part)
|
||||
(if-let [p (get path-params (:value part))]
|
||||
(conj acc p)
|
||||
(reduced nil))))
|
||||
[] (:path-parts route))]
|
||||
(fn [acc part]
|
||||
(if (string? part)
|
||||
(conj acc part)
|
||||
(if-let [p (get path-params (:value part))]
|
||||
(conj acc p)
|
||||
(reduced nil))))
|
||||
[] (:path-parts route))]
|
||||
(apply str parts))
|
||||
(:path route)))
|
||||
|
||||
|
|
@ -138,8 +138,8 @@
|
|||
(let [defined (-> path-params keys set)
|
||||
missing (set/difference required defined)]
|
||||
(ex/fail!
|
||||
(str "missing path-params for route " template " -> " missing)
|
||||
{:path-params path-params, :required required}))))
|
||||
(str "missing path-params for route " template " -> " missing)
|
||||
{:path-params path-params, :required required}))))
|
||||
|
||||
(defn fast-assoc
|
||||
#?@(:clj [[^clojure.lang.Associative a k v] (.assoc a k v)]
|
||||
|
|
@ -178,10 +178,10 @@
|
|||
(if s
|
||||
#?(:clj (if (.contains ^String s "%")
|
||||
(URLDecoder/decode
|
||||
(if (.contains ^String s "+")
|
||||
(.replace ^String s "+" "%2B")
|
||||
^String s)
|
||||
"UTF-8"))
|
||||
(if (.contains ^String s "+")
|
||||
(.replace ^String s "+" "%2B")
|
||||
^String s)
|
||||
"UTF-8"))
|
||||
:cljs (js/decodeURIComponent s))))
|
||||
|
||||
(defn url-decode [s]
|
||||
|
|
@ -249,18 +249,10 @@
|
|||
(->> params
|
||||
(map (fn [[k v]]
|
||||
(if (or (sequential? v) (set? v))
|
||||
(str/join "&" (map query-parameter (repeat k) v))
|
||||
(if (seq v)
|
||||
(str/join "&" (map query-parameter (repeat k) v))
|
||||
;; Empty seq results in single & character in the query string.
|
||||
;; Handle as empty string to behave similarly as when the value is nil.
|
||||
(query-parameter k ""))
|
||||
(query-parameter k v))))
|
||||
(str/join "&")))
|
||||
|
||||
(defmacro goog-extend [type base-type ctor & methods]
|
||||
`(do
|
||||
(def ~type (fn ~@ctor))
|
||||
|
||||
(goog/inherits ~type ~base-type)
|
||||
|
||||
~@(map
|
||||
(fn [method]
|
||||
`(set! (.. ~type -prototype ~(symbol (str "-" (first method))))
|
||||
(fn ~@(rest method))))
|
||||
methods)))
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
(ns reitit.interceptor
|
||||
(:require [meta-merge.core :refer [meta-merge]]
|
||||
[clojure.pprint :as pprint]
|
||||
(:require [clojure.pprint :as pprint]
|
||||
[meta-merge.core :refer [meta-merge]]
|
||||
[reitit.core :as r]
|
||||
[reitit.impl :as impl]
|
||||
[reitit.exception :as exception]))
|
||||
[reitit.exception :as exception]
|
||||
[reitit.impl :as impl]))
|
||||
|
||||
(defprotocol IntoInterceptor
|
||||
(into-interceptor [this data opts]))
|
||||
|
|
@ -37,36 +37,36 @@
|
|||
(if-let [interceptor (if registry (registry this))]
|
||||
(into-interceptor interceptor data opts)
|
||||
(throw
|
||||
(ex-info
|
||||
(str
|
||||
"Interceptor " this " not found in registry.\n\n"
|
||||
(if (seq registry)
|
||||
(str
|
||||
"Available interceptors in registry:\n"
|
||||
(with-out-str
|
||||
(pprint/print-table [:id :description] (for [[k v] registry] {:id k :description v}))))
|
||||
"see [reitit.interceptor/router] on how to add interceptor to the registry.\n") "\n")
|
||||
{:id this
|
||||
:registry registry}))))
|
||||
(ex-info
|
||||
(str
|
||||
"Interceptor " this " not found in registry.\n\n"
|
||||
(if (seq registry)
|
||||
(str
|
||||
"Available interceptors in registry:\n"
|
||||
(with-out-str
|
||||
(pprint/print-table [:id :description] (for [[k v] registry] {:id k :description v}))))
|
||||
"see [reitit.interceptor/router] on how to add interceptor to the registry.\n") "\n")
|
||||
{:id this
|
||||
:registry registry}))))
|
||||
|
||||
#?(:clj clojure.lang.APersistentVector
|
||||
:cljs cljs.core.PersistentVector)
|
||||
(into-interceptor [[f & args :as form] data opts]
|
||||
(when (and (seq args) (not (fn? f)))
|
||||
(exception/fail!
|
||||
(str "Invalid Interceptor form: " form "")
|
||||
{:form form}))
|
||||
(str "Invalid Interceptor form: " form "")
|
||||
{:form form}))
|
||||
(into-interceptor (apply f args) data opts))
|
||||
|
||||
#?(:clj clojure.lang.Fn
|
||||
:cljs function)
|
||||
(into-interceptor [this data opts]
|
||||
(into-interceptor
|
||||
{:name ::handler
|
||||
::handler this
|
||||
:enter (fn [ctx]
|
||||
(assoc ctx :response (this (:request ctx))))}
|
||||
data opts))
|
||||
{:name ::handler
|
||||
::handler this
|
||||
:enter (fn [ctx]
|
||||
(assoc ctx :response (this (:request ctx))))}
|
||||
data opts))
|
||||
|
||||
#?(:clj clojure.lang.PersistentArrayMap
|
||||
:cljs cljs.core.PersistentArrayMap)
|
||||
|
|
@ -86,13 +86,13 @@
|
|||
opts (assoc opts ::compiled (inc ^long compiled))]
|
||||
(when (>= ^long compiled ^long *max-compile-depth*)
|
||||
(exception/fail!
|
||||
(str "Too deep Interceptor compilation - " compiled)
|
||||
{:this this, :data data, :opts opts}))
|
||||
(str "Too deep Interceptor compilation - " compiled)
|
||||
{:this this, :data data, :opts opts}))
|
||||
(if-let [interceptor (into-interceptor (compile data opts) data opts)]
|
||||
(map->Interceptor
|
||||
(merge
|
||||
(dissoc this :compile)
|
||||
(impl/strip-nils interceptor)))))))
|
||||
(merge
|
||||
(dissoc this :compile)
|
||||
(impl/strip-nils interceptor)))))))
|
||||
|
||||
nil
|
||||
(into-interceptor [_ _ _]))
|
||||
|
|
@ -122,9 +122,9 @@
|
|||
([[_ {:keys [interceptors handler] :as data}] {::keys [queue] :as opts} _]
|
||||
(let [chain (chain (into (vec interceptors) [handler]) data opts)]
|
||||
(map->Endpoint
|
||||
{:interceptors chain
|
||||
:queue ((or queue identity) chain)
|
||||
:data data}))))
|
||||
{:interceptors chain
|
||||
:queue ((or queue identity) chain)
|
||||
:data data}))))
|
||||
|
||||
(defn transform-butlast
|
||||
"Returns a function to that takes a interceptor transformation function and
|
||||
|
|
@ -132,8 +132,8 @@
|
|||
[f]
|
||||
(fn [interceptors]
|
||||
(concat
|
||||
(f (butlast interceptors))
|
||||
[(last interceptors)])))
|
||||
(f (butlast interceptors))
|
||||
[(last interceptors)])))
|
||||
|
||||
(defn router
|
||||
"Creates a [[reitit.core/Router]] from raw route data and optionally an options map with
|
||||
|
|
@ -155,8 +155,8 @@
|
|||
:handler get-user}]])"
|
||||
([data]
|
||||
(router data nil))
|
||||
([data opts]
|
||||
(let [opts (meta-merge {:compile compile-result} opts)]
|
||||
([data {:keys [meta-merge-fn] :as opts}]
|
||||
(let [opts ((or meta-merge-fn meta-merge) {:compile compile-result} opts)]
|
||||
(r/router data opts))))
|
||||
|
||||
(defn interceptor-handler [router]
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
(ns reitit.middleware
|
||||
(:require [meta-merge.core :refer [meta-merge]]
|
||||
[clojure.pprint :as pprint]
|
||||
(:require [clojure.pprint :as pprint]
|
||||
[meta-merge.core :refer [meta-merge]]
|
||||
[reitit.core :as r]
|
||||
[reitit.impl :as impl]
|
||||
[reitit.exception :as exception]))
|
||||
[reitit.exception :as exception]
|
||||
[reitit.impl :as impl]))
|
||||
|
||||
(defprotocol IntoMiddleware
|
||||
(into-middleware [this data opts]))
|
||||
|
|
@ -21,17 +21,17 @@
|
|||
(if-let [middleware (if registry (registry this))]
|
||||
(into-middleware middleware data opts)
|
||||
(throw
|
||||
(ex-info
|
||||
(str
|
||||
"Middleware " this " not found in registry.\n\n"
|
||||
(if (seq registry)
|
||||
(str
|
||||
"Available middleware in registry:\n"
|
||||
(with-out-str
|
||||
(pprint/print-table [:id :description] (for [[k v] registry] {:id k :description v}))))
|
||||
"see [reitit.middleware/router] on how to add middleware to the registry.\n") "\n")
|
||||
{:id this
|
||||
:registry registry}))))
|
||||
(ex-info
|
||||
(str
|
||||
"Middleware " this " not found in registry.\n\n"
|
||||
(if (seq registry)
|
||||
(str
|
||||
"Available middleware in registry:\n"
|
||||
(with-out-str
|
||||
(pprint/print-table [:id :description] (for [[k v] registry] {:id k :description v}))))
|
||||
"see [reitit.middleware/router] on how to add middleware to the registry.\n") "\n")
|
||||
{:id this
|
||||
:registry registry}))))
|
||||
|
||||
#?(:clj clojure.lang.APersistentVector
|
||||
:cljs cljs.core.PersistentVector)
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
:cljs function)
|
||||
(into-middleware [this _ _]
|
||||
(map->Middleware
|
||||
{:wrap this}))
|
||||
{:wrap this}))
|
||||
|
||||
#?(:clj clojure.lang.PersistentArrayMap
|
||||
:cljs cljs.core.PersistentArrayMap)
|
||||
|
|
@ -63,13 +63,13 @@
|
|||
opts (assoc opts ::compiled (inc ^long compiled))]
|
||||
(when (>= ^long compiled ^long *max-compile-depth*)
|
||||
(exception/fail!
|
||||
(str "Too deep Middleware compilation - " compiled)
|
||||
{:this this, :data data, :opts opts}))
|
||||
(str "Too deep Middleware compilation - " compiled)
|
||||
{:this this, :data data, :opts opts}))
|
||||
(if-let [middeware (into-middleware (compile data opts) data opts)]
|
||||
(map->Middleware
|
||||
(merge
|
||||
(dissoc this :compile)
|
||||
(impl/strip-nils middeware)))))))
|
||||
(merge
|
||||
(dissoc this :compile)
|
||||
(impl/strip-nils middeware)))))))
|
||||
|
||||
nil
|
||||
(into-middleware [_ _ _]))
|
||||
|
|
@ -77,10 +77,10 @@
|
|||
(defn- ensure-handler! [path data scope]
|
||||
(when-not (:handler data)
|
||||
(exception/fail!
|
||||
(str "path \"" path "\" doesn't have a :handler defined"
|
||||
(if scope (str " for " scope)))
|
||||
(merge {:path path, :data data}
|
||||
(if scope {:scope scope})))))
|
||||
(str "path \"" path "\" doesn't have a :handler defined"
|
||||
(if scope (str " for " scope)))
|
||||
(merge {:path path, :data data}
|
||||
(if scope {:scope scope})))))
|
||||
|
||||
(defn- expand-and-transform
|
||||
[middleware data {::keys [transform] :or {transform identity} :as opts}]
|
||||
|
|
@ -116,9 +116,9 @@
|
|||
(ensure-handler! path data scope)
|
||||
(let [middleware (expand-and-transform middleware data opts)]
|
||||
(map->Endpoint
|
||||
{:handler (compile-handler middleware handler)
|
||||
:middleware middleware
|
||||
:data data}))))
|
||||
{:handler (compile-handler middleware handler)
|
||||
:middleware middleware
|
||||
:data data}))))
|
||||
|
||||
(defn router
|
||||
"Creates a [[reitit.core/Router]] from raw route data and optionally an options map with
|
||||
|
|
@ -138,8 +138,8 @@
|
|||
:handler get-user}]])"
|
||||
([data]
|
||||
(router data nil))
|
||||
([data opts]
|
||||
(let [opts (meta-merge {:compile compile-result} opts)]
|
||||
([data {:keys [meta-merge-fn] :as opts}]
|
||||
(let [opts ((or meta-merge-fn meta-merge) {:compile compile-result} opts)]
|
||||
(r/router data opts))))
|
||||
|
||||
(defn middleware-handler [router]
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
(ns reitit.spec
|
||||
(:require [clojure.spec.alpha :as s]
|
||||
[clojure.spec.gen.alpha :as gen]
|
||||
[reitit.exception :as exception]
|
||||
[reitit.core :as r]))
|
||||
[reitit.core :as r]
|
||||
[reitit.exception :as exception]))
|
||||
|
||||
;;
|
||||
;; routes
|
||||
|
|
@ -16,9 +16,9 @@
|
|||
|
||||
(s/def ::raw-route
|
||||
(s/nilable
|
||||
(s/cat :path ::path
|
||||
:arg (s/? ::arg)
|
||||
:childs (s/* (s/and (s/nilable ::raw-routes))))))
|
||||
(s/cat :path ::path
|
||||
:arg (s/? ::arg)
|
||||
:childs (s/* (s/and (s/nilable ::raw-routes))))))
|
||||
|
||||
(s/def ::raw-routes
|
||||
(s/or :route ::raw-route
|
||||
|
|
@ -60,19 +60,19 @@
|
|||
|
||||
(s/def ::opts
|
||||
(s/nilable
|
||||
(s/keys :opt-un [:reitit.router/path
|
||||
:reitit.router/routes
|
||||
:reitit.router/data
|
||||
:reitit.router/expand
|
||||
:reitit.router/coerce
|
||||
:reitit.router/compile
|
||||
:reitit.router/conflicts
|
||||
:reitit.router/router])))
|
||||
(s/keys :opt-un [:reitit.router/path
|
||||
:reitit.router/routes
|
||||
:reitit.router/data
|
||||
:reitit.router/expand
|
||||
:reitit.router/coerce
|
||||
:reitit.router/compile
|
||||
:reitit.router/conflicts
|
||||
:reitit.router/router])))
|
||||
|
||||
(s/fdef r/router
|
||||
:args (s/or :1arity (s/cat :data (s/spec ::raw-routes))
|
||||
:2arity (s/cat :data (s/spec ::raw-routes), :opts ::opts))
|
||||
:ret ::router)
|
||||
:args (s/or :1arity (s/cat :data (s/spec ::raw-routes))
|
||||
:2arity (s/cat :data (s/spec ::raw-routes), :opts ::opts))
|
||||
:ret ::router)
|
||||
|
||||
;;
|
||||
;; coercion
|
||||
|
|
@ -119,24 +119,25 @@
|
|||
(defrecord Problem [path scope data spec problems])
|
||||
|
||||
(defn validate-route-data [routes wrap spec]
|
||||
(let [spec (wrap spec)]
|
||||
(some->> (for [[p d _] routes]
|
||||
(when-let [problems (and spec (s/explain-data spec d))]
|
||||
(->Problem p nil d spec problems)))
|
||||
(keep identity) (seq) (vec))))
|
||||
(let [spec (wrap spec)
|
||||
spec-explain (fn [[p d _]]
|
||||
(when-let [problems (and spec (s/explain-data spec d))]
|
||||
(->Problem p nil d spec problems)))
|
||||
errors (into [] (keep spec-explain) routes)]
|
||||
(when (pos? (count errors)) errors)))
|
||||
|
||||
(defn validate [routes {:keys [spec] ::keys [wrap] :or {spec ::default-data, wrap identity}}]
|
||||
(when-let [problems (validate-route-data routes wrap spec)]
|
||||
(exception/fail!
|
||||
::invalid-route-data
|
||||
{:problems problems})))
|
||||
::invalid-route-data
|
||||
{:problems problems})))
|
||||
|
||||
(defmethod exception/format-exception :reitit.spec/invalid-route-data [_ _ {:keys [problems]}]
|
||||
(apply str "Invalid route data:\n\n"
|
||||
(mapv
|
||||
(fn [{:keys [path scope data spec]}]
|
||||
(str "-- On route -----------------------\n\n"
|
||||
(pr-str path) (if scope (str " " (pr-str scope))) "\n\n"
|
||||
(pr-str data) "\n\n"
|
||||
(s/explain-str spec data) "\n"))
|
||||
problems)))
|
||||
(fn [{:keys [path scope data spec]}]
|
||||
(str "-- On route -----------------------\n\n"
|
||||
(pr-str path) (if scope (str " " (pr-str scope))) "\n\n"
|
||||
(pr-str data) "\n\n"
|
||||
(s/explain-str spec data) "\n"))
|
||||
problems)))
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
(:refer-clojure :exclude [compile])
|
||||
(:require [clojure.string :as str]
|
||||
[reitit.exception :as ex])
|
||||
#?(:clj (:import [reitit Trie Trie$Match Trie$Matcher]
|
||||
(java.net URLDecoder))))
|
||||
#?(:clj (:import (java.net URLDecoder)
|
||||
[reitit Trie Trie$Match Trie$Matcher])))
|
||||
|
||||
(defn ^:no-doc into-set [x]
|
||||
(cond
|
||||
|
|
@ -90,12 +90,12 @@
|
|||
|
||||
(defn join-path [xs]
|
||||
(reduce
|
||||
(fn [s x]
|
||||
(str s (cond
|
||||
(string? x) x
|
||||
(instance? Wild x) (str "{" (-> x :value str (subs 1)) "}")
|
||||
(instance? CatchAll x) (str "{*" (-> x :value str (subs 1)) "}"))))
|
||||
"" xs))
|
||||
(fn [s x]
|
||||
(str s (cond
|
||||
(string? x) x
|
||||
(instance? Wild x) (str "{" (-> x :value str (subs 1)) "}")
|
||||
(instance? CatchAll x) (str "{*" (-> x :value str (subs 1)) "}"))))
|
||||
"" xs))
|
||||
|
||||
(defn normalize [s opts]
|
||||
(-> s (split-path opts) (join-path)))
|
||||
|
|
@ -167,30 +167,30 @@
|
|||
(instance? CatchAll path)
|
||||
(assoc-in node [:catch-all path] (-node {:params params, :data data}))
|
||||
|
||||
(str/blank? path)
|
||||
(empty? path)
|
||||
(-insert node ps fp params data)
|
||||
|
||||
:else
|
||||
(or
|
||||
(reduce
|
||||
(fn [_ [p n]]
|
||||
(if-let [cp (common-prefix p path)]
|
||||
(if (= cp p)
|
||||
(reduce
|
||||
(fn [_ [p n]]
|
||||
(if-let [cp (common-prefix p path)]
|
||||
(if (= cp p)
|
||||
;; insert into child node
|
||||
(let [n' (-insert n (conj ps (subs path (count p))) fp params data)]
|
||||
(reduced (assoc-in node [:children p] n')))
|
||||
(let [n' (-insert n (conj ps (subs path (count p))) fp params data)]
|
||||
(reduced (assoc-in node [:children p] n')))
|
||||
;; split child node
|
||||
(let [rp (subs p (count cp))
|
||||
rp' (subs path (count cp))
|
||||
n' (-insert (-node {}) ps fp params data)
|
||||
n'' (-insert (-node {:children {rp n, rp' n'}}) nil nil nil nil)]
|
||||
(reduced (update node :children (fn [children]
|
||||
(-> children
|
||||
(dissoc p)
|
||||
(assoc cp n'')))))))))
|
||||
nil (:children node))
|
||||
(let [rp (subs p (count cp))
|
||||
rp' (subs path (count cp))
|
||||
n' (-insert (-node {}) ps fp params data)
|
||||
n'' (-insert (-node {:children {rp n, rp' n'}}) nil nil nil nil)]
|
||||
(reduced (update node :children (fn [children]
|
||||
(-> children
|
||||
(dissoc p)
|
||||
(assoc cp n'')))))))))
|
||||
nil (:children node))
|
||||
;; new child node
|
||||
(assoc-in node [:children path] (-insert (-node {}) ps fp params data))))]
|
||||
(assoc-in node [:children path] (-insert (-node {}) ps fp params data))))]
|
||||
(if-let [child (get-in node' [:children ""])]
|
||||
;; optimize by removing empty paths
|
||||
(-> (merge-with merge (dissoc node' :data) child)
|
||||
|
|
@ -202,10 +202,10 @@
|
|||
(if percent?
|
||||
#?(:cljs (js/decodeURIComponent param)
|
||||
:clj (URLDecoder/decode
|
||||
(if (.contains ^String param "+")
|
||||
(.replace ^String param "+" "%2B")
|
||||
param)
|
||||
"UTF-8"))
|
||||
(if (.contains ^String param "+")
|
||||
(.replace ^String param "+" "%2B")
|
||||
param)
|
||||
"UTF-8"))
|
||||
param)))
|
||||
|
||||
;;
|
||||
|
|
@ -313,13 +313,13 @@
|
|||
(def record-parameters
|
||||
"Memoized function to transform parameters into runtime generated Record."
|
||||
(memoize
|
||||
(fn [keys]
|
||||
(if (some qualified-keyword? keys)
|
||||
(map-parameters keys)
|
||||
(let [sym (gensym "PathParams")
|
||||
ctor (symbol (str "map->" sym))]
|
||||
(binding [*ns* (find-ns 'user)]
|
||||
(eval `(do (defrecord ~sym ~(mapv (comp symbol name) keys)) (~ctor {}))))))))))
|
||||
(fn [keys]
|
||||
(if (some qualified-keyword? keys)
|
||||
(map-parameters keys)
|
||||
(let [sym (gensym "PathParams")
|
||||
ctor (symbol (str "map->" sym))]
|
||||
(binding [*ns* (find-ns 'user)]
|
||||
(eval `(do (defrecord ~sym ~(mapv (comp symbol name) keys)) (~ctor {}))))))))))
|
||||
|
||||
(defn insert
|
||||
"Returns a trie with routes added to it."
|
||||
|
|
@ -327,9 +327,9 @@
|
|||
(insert nil routes))
|
||||
([node routes]
|
||||
(reduce
|
||||
(fn [acc [p d]]
|
||||
(insert acc p d))
|
||||
node routes))
|
||||
(fn [acc [p d]]
|
||||
(insert acc p d))
|
||||
node routes))
|
||||
([node path data]
|
||||
(insert node path data nil))
|
||||
([node path data {::keys [parameters] :or {parameters map-parameters} :as opts}]
|
||||
|
|
@ -355,17 +355,16 @@
|
|||
(cond-> data (conj (data-matcher compiler params data)))
|
||||
(into (for [[p c] children] (static-matcher compiler p (compile c compiler (conj cp p)))))
|
||||
(into
|
||||
(for [[p c] wilds]
|
||||
(let [pv (:value p)
|
||||
ends (ends c)]
|
||||
(if (next ends)
|
||||
(ex/fail! ::multiple-terminators {:terminators ends, :path (join-path (conj cp p))})
|
||||
(wild-matcher compiler pv (ffirst ends) (compile c compiler (conj cp pv)))))))
|
||||
(for [[p c] wilds]
|
||||
(let [pv (:value p)
|
||||
ends (ends c)]
|
||||
(if (next ends)
|
||||
(ex/fail! ::multiple-terminators {:terminators ends, :path (join-path (conj cp p))})
|
||||
(wild-matcher compiler pv (ffirst ends) (compile c compiler (conj cp pv)))))))
|
||||
(into (for [[p c] catch-all] (catch-all-matcher compiler (:value p) params (:data c)))))]
|
||||
(cond
|
||||
(> (count matchers) 1) (linear-matcher compiler matchers false)
|
||||
(= (count matchers) 1) (first matchers)
|
||||
:else (data-matcher compiler {} nil)))))
|
||||
(= (count matchers) 1) (first matchers)))))
|
||||
|
||||
(defn pretty
|
||||
"Returns a simplified EDN structure of a compiled trie for printing purposes."
|
||||
|
|
@ -387,59 +386,61 @@
|
|||
|
||||
(comment
|
||||
(->
|
||||
[["/v2/whoami" 1]
|
||||
["/v2/users/:user-id/datasets" 2]
|
||||
["/v2/public/projects/:project-id/datasets" 3]
|
||||
["/v1/public/topics/:topic" 4]
|
||||
["/v1/users/:user-id/orgs/:org-id" 5]
|
||||
["/v1/search/topics/:term" 6]
|
||||
["/v1/users/:user-id/invitations" 7]
|
||||
["/v1/users/:user-id/topics" 9]
|
||||
["/v1/users/:user-id/bookmarks/followers" 10]
|
||||
["/v2/datasets/:dataset-id" 11]
|
||||
["/v1/orgs/:org-id/usage-stats" 12]
|
||||
["/v1/orgs/:org-id/devices/:client-id" 13]
|
||||
["/v1/messages/user/:user-id" 14]
|
||||
["/v1/users/:user-id/devices" 15]
|
||||
["/v1/public/users/:user-id" 16]
|
||||
["/v1/orgs/:org-id/errors" 17]
|
||||
["/v1/public/orgs/:org-id" 18]
|
||||
["/v1/orgs/:org-id/invitations" 19]
|
||||
["/v1/users/:user-id/device-errors" 22]
|
||||
["/v2/login" 23]
|
||||
["/v1/users/:user-id/usage-stats" 24]
|
||||
["/v2/users/:user-id/devices" 25]
|
||||
["/v1/users/:user-id/claim-device/:client-id" 26]
|
||||
["/v2/public/projects/:project-id" 27]
|
||||
["/v2/public/datasets/:dataset-id" 28]
|
||||
["/v2/users/:user-id/topics/bulk" 29]
|
||||
["/v1/messages/device/:client-id" 30]
|
||||
["/v1/users/:user-id/owned-orgs" 31]
|
||||
["/v1/topics/:topic" 32]
|
||||
["/v1/users/:user-id/bookmark/:topic" 33]
|
||||
["/v1/orgs/:org-id/members/:user-id" 34]
|
||||
["/v1/users/:user-id/devices/:client-id" 35]
|
||||
["/v1/users/:user-id" 36]
|
||||
["/v1/orgs/:org-id/devices" 37]
|
||||
["/v1/orgs/:org-id/members" 38]
|
||||
["/v2/orgs/:org-id/topics" 40]
|
||||
["/v1/whoami" 41]
|
||||
["/v1/orgs/:org-id" 42]
|
||||
["/v1/users/:user-id/api-key" 43]
|
||||
["/v2/schemas" 44]
|
||||
["/v2/users/:user-id/topics" 45]
|
||||
["/v1/orgs/:org-id/confirm-membership/:token" 46]
|
||||
["/v2/topics/:topic" 47]
|
||||
["/v1/messages/topic/:topic" 48]
|
||||
["/v1/users/:user-id/devices/:client-id/reset-password" 49]
|
||||
["/v2/topics" 50]
|
||||
["/v1/login" 51]
|
||||
["/v1/users/:user-id/orgs" 52]
|
||||
["/v2/public/messages/dataset/:dataset-id" 53]
|
||||
["/v1/topics" 54]
|
||||
["/v1/orgs" 55]
|
||||
["/v1/users/:user-id/bookmarks" 56]
|
||||
["/v1/orgs/:org-id/topics" 57]]
|
||||
(insert)
|
||||
(compile)
|
||||
(pretty)))
|
||||
[["/v2/whoami" 1]
|
||||
["/v2/users/:user-id/datasets" 2]
|
||||
["/v2/public/projects/:project-id/datasets" 3]
|
||||
["/v1/public/topics/:topic" 4]
|
||||
["/v1/users/:user-id/orgs/:org-id" 5]
|
||||
["/v1/search/topics/:term" 6]
|
||||
["/v1/users/:user-id/invitations" 7]
|
||||
["/v1/users/:user-id/topics" 9]
|
||||
["/v1/users/:user-id/bookmarks/followers" 10]
|
||||
["/v2/datasets/:dataset-id" 11]
|
||||
["/v1/orgs/:org-id/usage-stats" 12]
|
||||
["/v1/orgs/:org-id/devices/:client-id" 13]
|
||||
["/v1/messages/user/:user-id" 14]
|
||||
["/v1/users/:user-id/devices" 15]
|
||||
["/v1/public/users/:user-id" 16]
|
||||
["/v1/orgs/:org-id/errors" 17]
|
||||
["/v1/public/orgs/:org-id" 18]
|
||||
["/v1/orgs/:org-id/invitations" 19]
|
||||
["/v1/users/:user-id/device-errors" 22]
|
||||
["/v2/login" 23]
|
||||
["/v1/users/:user-id/usage-stats" 24]
|
||||
["/v2/users/:user-id/devices" 25]
|
||||
["/v1/users/:user-id/claim-device/:client-id" 26]
|
||||
["/v2/public/projects/:project-id" 27]
|
||||
["/v2/public/datasets/:dataset-id" 28]
|
||||
["/v2/users/:user-id/topics/bulk" 29]
|
||||
["/v1/messages/device/:client-id" 30]
|
||||
["/v1/users/:user-id/owned-orgs" 31]
|
||||
["/v1/topics/:topic" 32]
|
||||
["/v1/users/:user-id/bookmark/:topic" 33]
|
||||
["/v1/orgs/:org-id/members/:user-id" 34]
|
||||
["/v1/users/:user-id/devices/:client-id" 35]
|
||||
["/v1/users/:user-id" 36]
|
||||
["/v1/orgs/:org-id/devices" 37]
|
||||
["/v1/orgs/:org-id/members" 38]
|
||||
["/v2/orgs/:org-id/topics" 40]
|
||||
["/v1/whoami" 41]
|
||||
["/v1/orgs/:org-id" 42]
|
||||
["/v1/users/:user-id/api-key" 43]
|
||||
["/v2/schemas" 44]
|
||||
["/v2/users/:user-id/topics" 45]
|
||||
["/v1/orgs/:org-id/confirm-membership/:token" 46]
|
||||
["/v2/topics/:topic" 47]
|
||||
["/v1/messages/topic/:topic" 48]
|
||||
["/v1/users/:user-id/devices/:client-id/reset-password" 49]
|
||||
["/v2/topics" 50]
|
||||
["/v1/login" 51]
|
||||
["/v1/users/:user-id/orgs" 52]
|
||||
["/v2/public/messages/dataset/:dataset-id" 53]
|
||||
["/v1/topics" 54]
|
||||
["/v1/orgs" 55]
|
||||
["/v1/users/:user-id/bookmarks" 56]
|
||||
["/v1/orgs/:org-id/topics" 57]
|
||||
["/command1 {arg1} {arg2}" ::cmd1]
|
||||
["/command2 {arg1} {arg2} {arg3}" ::cmd2]]
|
||||
(insert)
|
||||
(compile)
|
||||
(pretty)))
|
||||
|
|
|
|||
1
modules/reitit-dev/.clj-kondo/config.edn
Symbolic link
1
modules/reitit-dev/.clj-kondo/config.edn
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../.clj-kondo/module_config.edn
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
(defproject metosin/reitit-dev "0.5.10"
|
||||
(defproject metosin/reitit-dev "0.5.18"
|
||||
:description "Snappy data-driven router for Clojure(Script)"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
|
|||
|
|
@ -1,18 +1,16 @@
|
|||
(ns reitit.dev.pretty
|
||||
(:require [clojure.string :as str]
|
||||
(:require [arrangement.core] ;; spell-spec
|
||||
[clojure.spec.alpha :as s]
|
||||
[reitit.exception :as exception]
|
||||
[arrangement.core]
|
||||
;; spell-spec
|
||||
[spell-spec.expound]
|
||||
;; expound
|
||||
[clojure.string :as str]
|
||||
[expound.alpha] ;; fipp
|
||||
[expound.ansi]
|
||||
[expound.alpha]
|
||||
;; fipp
|
||||
[fipp.visit]
|
||||
[fipp.edn]
|
||||
[fipp.ednize]
|
||||
[fipp.engine]))
|
||||
[fipp.engine]
|
||||
[fipp.visit]
|
||||
[reitit.exception :as exception]
|
||||
[spell-spec.expound] ;; expound
|
||||
))
|
||||
|
||||
;;
|
||||
;; colors
|
||||
|
|
@ -152,13 +150,13 @@
|
|||
(printer nil))
|
||||
([options]
|
||||
(map->EdnPrinter
|
||||
(merge
|
||||
{:width 80
|
||||
:symbols {}
|
||||
:print-length *print-length*
|
||||
:print-level *print-level*
|
||||
:print-meta *print-meta*}
|
||||
options))))
|
||||
(merge
|
||||
{:width 80
|
||||
:symbols {}
|
||||
:print-length *print-length*
|
||||
:print-level *print-level*
|
||||
:print-meta *print-meta*}
|
||||
options))))
|
||||
|
||||
(defn pprint
|
||||
([x] (pprint x {}))
|
||||
|
|
@ -209,13 +207,13 @@
|
|||
(defn exception-str [message source printer]
|
||||
(with-out-str
|
||||
(print-doc
|
||||
[:group
|
||||
(title "Router creation failed" source printer)
|
||||
[:break] [:break]
|
||||
message
|
||||
[:break]
|
||||
(footer printer)]
|
||||
printer)))
|
||||
[:group
|
||||
(title "Router creation failed" source printer)
|
||||
[:break] [:break]
|
||||
message
|
||||
[:break]
|
||||
(footer printer)]
|
||||
printer)))
|
||||
|
||||
(defmulti format-exception (fn [type _ _] type))
|
||||
|
||||
|
|
@ -231,11 +229,11 @@
|
|||
|
||||
(defn de-expound-colors [^String s mappings]
|
||||
(let [s' (reduce
|
||||
(fn [s [from to]]
|
||||
(.replace ^String s
|
||||
^String (expound.ansi/esc [from])
|
||||
^String (-start (colors to))))
|
||||
s mappings)]
|
||||
(fn [s [from to]]
|
||||
(.replace ^String s
|
||||
^String (expound.ansi/esc [from])
|
||||
^String (-start (colors to))))
|
||||
s mappings)]
|
||||
(.replace ^String s'
|
||||
^String (expound.ansi/esc [:none])
|
||||
(str (expound.ansi/esc [:none]) (-start (colors :text))))))
|
||||
|
|
@ -254,9 +252,9 @@
|
|||
|
||||
(def expound-printer
|
||||
(expound.alpha/custom-printer
|
||||
{:theme :figwheel-theme
|
||||
:show-valid-values? false
|
||||
:print-specs? false}))
|
||||
{:theme :figwheel-theme
|
||||
:show-valid-values? false
|
||||
:print-specs? false}))
|
||||
|
||||
;;
|
||||
;; Formatters
|
||||
|
|
@ -276,18 +274,18 @@
|
|||
" ")
|
||||
(edn (not-empty (select-keys route-data [:conflicting])))])]
|
||||
(into
|
||||
[:group]
|
||||
(mapv
|
||||
(fn [[[path route-data] vals]]
|
||||
[:group
|
||||
(path-report path route-data)
|
||||
(into
|
||||
[:group]
|
||||
(map
|
||||
(fn [[path route-data]] (path-report path route-data))
|
||||
vals))
|
||||
[:break]])
|
||||
conflicts)))
|
||||
[:group]
|
||||
(mapv
|
||||
(fn [[[path route-data] vals]]
|
||||
[:group
|
||||
(path-report path route-data)
|
||||
(into
|
||||
[:group]
|
||||
(map
|
||||
(fn [[path route-data]] (path-report path route-data))
|
||||
vals))
|
||||
[:break]])
|
||||
conflicts)))
|
||||
[:span (text "Either fix the conflicting paths or disable the conflict resolution")
|
||||
[:break] (text "by setting route data for conflicting route: ") [:break] [:break]
|
||||
(edn {:conflicting true} {:margin 3})
|
||||
|
|
@ -302,19 +300,19 @@
|
|||
(text "Router contains conflicting route names:")
|
||||
[:break] [:break]
|
||||
(into
|
||||
[:group]
|
||||
(mapv
|
||||
(fn [[name vals]]
|
||||
[:group
|
||||
[:span (text name)]
|
||||
[:break]
|
||||
(into
|
||||
[:group]
|
||||
(map
|
||||
(fn [p] [:span (color :grey "-> " p) [:break]])
|
||||
(mapv first vals)))
|
||||
[:break]])
|
||||
conflicts))
|
||||
[:group]
|
||||
(mapv
|
||||
(fn [[name vals]]
|
||||
[:group
|
||||
[:span (text name)]
|
||||
[:break]
|
||||
(into
|
||||
[:group]
|
||||
(map
|
||||
(fn [p] [:span (color :grey "-> " p) [:break]])
|
||||
(mapv first vals)))
|
||||
[:break]])
|
||||
conflicts))
|
||||
(color :white "https://cljdoc.org/d/metosin/reitit/CURRENT/doc/basics/route-conflicts")
|
||||
[:break]])
|
||||
|
||||
|
|
@ -323,22 +321,22 @@
|
|||
(text "Invalid route data:")
|
||||
[:break] [:break]
|
||||
(into
|
||||
[:group]
|
||||
(map
|
||||
(fn [{:keys [data path spec scope]}]
|
||||
[:group
|
||||
[:span (color :grey "-- On route -----------------------")]
|
||||
[:break]
|
||||
[:break]
|
||||
(text path) (if scope [:span " " (text scope)])
|
||||
[:break]
|
||||
[:break]
|
||||
(-> (s/explain-data spec data)
|
||||
(expound-printer)
|
||||
(with-out-str)
|
||||
(fippify))
|
||||
[:break]])
|
||||
problems))
|
||||
[:group]
|
||||
(map
|
||||
(fn [{:keys [data path spec scope]}]
|
||||
[:group
|
||||
[:span (color :grey "-- On route -----------------------")]
|
||||
[:break]
|
||||
[:break]
|
||||
(text path) (if scope [:span " " (text scope)])
|
||||
[:break]
|
||||
[:break]
|
||||
(-> (s/explain-data spec data)
|
||||
(expound-printer)
|
||||
(with-out-str)
|
||||
(fippify))
|
||||
[:break]])
|
||||
problems))
|
||||
(color :white "https://cljdoc.org/d/metosin/reitit/CURRENT/doc/basics/route-data-validation")
|
||||
[:break]])
|
||||
|
||||
|
|
|
|||
1
modules/reitit-frontend/.clj-kondo/config.edn
Symbolic link
1
modules/reitit-frontend/.clj-kondo/config.edn
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../.clj-kondo/module_config.edn
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
(defproject metosin/reitit-frontend "0.5.10"
|
||||
(defproject metosin/reitit-frontend "0.5.18"
|
||||
:description "Reitit: Clojurescript frontend routing core"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
|
|||
|
|
@ -22,18 +22,28 @@
|
|||
|
||||
(defn match-by-path
|
||||
"Given routing tree and current path, return match with possibly
|
||||
coerced parameters. Return nil if no match found."
|
||||
[router path]
|
||||
(let [uri (.parse Uri path)]
|
||||
(if-let [match (r/match-by-path router (.getPath uri))]
|
||||
(let [q (query-params uri)
|
||||
match (assoc match :query-params q)
|
||||
;; Return uncoerced values if coercion is not enabled - so
|
||||
;; that tha parameters are always accessible from same property.
|
||||
parameters (or (coercion/coerce! match)
|
||||
{:path (:path-params match)
|
||||
:query q})]
|
||||
(assoc match :parameters parameters)))))
|
||||
coerced parameters. Return nil if no match found.
|
||||
|
||||
:on-coercion-error - a sideeffecting fn of `match exception -> nil`"
|
||||
([router path] (match-by-path router path nil))
|
||||
([router path {:keys [on-coercion-error]}]
|
||||
(let [uri (.parse Uri path)
|
||||
coerce! (if on-coercion-error
|
||||
(fn [match]
|
||||
(try (coercion/coerce! match)
|
||||
(catch js/Error e
|
||||
(on-coercion-error match e)
|
||||
(throw e))))
|
||||
coercion/coerce!)]
|
||||
(if-let [match (r/match-by-path router (.getPath uri))]
|
||||
(let [q (query-params uri)
|
||||
match (assoc match :query-params q)
|
||||
;; Return uncoerced values if coercion is not enabled - so
|
||||
;; that tha parameters are always accessible from same property.
|
||||
parameters (or (coerce! match)
|
||||
{:path (:path-params match)
|
||||
:query q})]
|
||||
(assoc match :parameters parameters))))))
|
||||
|
||||
(defn match-by-name
|
||||
"Given a router, route name and optionally path-parameters,
|
||||
|
|
@ -64,11 +74,11 @@
|
|||
(let [defined (-> path-params keys set)
|
||||
missing (set/difference (:required match) defined)]
|
||||
(js/console.warn
|
||||
"missing path-params for route" name
|
||||
{:template (:template match)
|
||||
:missing missing
|
||||
:path-params path-params
|
||||
:required (:required match)})
|
||||
"missing path-params for route" name
|
||||
{:template (:template match)
|
||||
:missing missing
|
||||
:path-params path-params
|
||||
:required (:required match)})
|
||||
nil))
|
||||
match)
|
||||
(do (js/console.warn "missing route" name)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@
|
|||
;; Differences:
|
||||
;; This one automatically removes previous event listeners.
|
||||
|
||||
(defn start!
|
||||
(defn ^{:see-also ["reitit.frontend.history/start!"]}
|
||||
start!
|
||||
"This registers event listeners on HTML5 history and hashchange events.
|
||||
|
||||
Automatically removes previous event listeners so it is safe to call this repeatedly, for example when using
|
||||
|
|
@ -37,30 +38,66 @@
|
|||
(when (nil? @history)
|
||||
(reset! history this))
|
||||
(on-navigate m this))
|
||||
opts))
|
||||
opts))
|
||||
|
||||
(defn href
|
||||
([k]
|
||||
(rfh/href @history k nil nil))
|
||||
([k params]
|
||||
(rfh/href @history k params nil))
|
||||
([k params query]
|
||||
(rfh/href @history k params query)))
|
||||
(defn
|
||||
^{:see-also ["reitit.frontend.history/href"]}
|
||||
href
|
||||
"Generate a URL for a route defined by name, with given path-params and query-params.
|
||||
|
||||
(defn push-state
|
||||
"Sets the new route, leaving previous route in history."
|
||||
([k]
|
||||
(rfh/push-state @history k nil nil))
|
||||
([k params]
|
||||
(rfh/push-state @history k params nil))
|
||||
([k params query]
|
||||
(rfh/push-state @history k params query)))
|
||||
The URL is formatted using Reitit frontend history handler, so using it with
|
||||
anchor element href will correctly trigger route change event.
|
||||
|
||||
(defn replace-state
|
||||
"Replaces current route. I.e. current route is not left on history."
|
||||
([k]
|
||||
(rfh/replace-state @history k nil nil))
|
||||
([k params]
|
||||
(rfh/replace-state @history k params nil))
|
||||
([k params query]
|
||||
(rfh/replace-state @history k params query)))
|
||||
Note: currently collections in query-parameters are encoded as field-value
|
||||
pairs separated by &, i.e. \"?a=1&a=2\", if you want to encode them
|
||||
differently, convert the collections to strings first."
|
||||
([name]
|
||||
(rfh/href @history name nil nil))
|
||||
([name path-params]
|
||||
(rfh/href @history name path-params nil))
|
||||
([name path-params query-params]
|
||||
(rfh/href @history name path-params query-params)))
|
||||
|
||||
(defn
|
||||
^{:see-also ["reitit.frontend.history/push-state"]}
|
||||
push-state
|
||||
"Updates the browser location and pushes new entry to the history stack using
|
||||
URL built from a route defined by name, with given path-params and
|
||||
query-params.
|
||||
|
||||
Will also trigger on-navigate callback on Reitit frontend History handler.
|
||||
|
||||
Note: currently collections in query parameters are encoded as field-value
|
||||
pairs separated by &, i.e. \"?a=1&a=2\", if you want to encode them
|
||||
differently, convert the collections to strings first.
|
||||
|
||||
See also:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/History/pushState"
|
||||
([name]
|
||||
(rfh/push-state @history name nil nil))
|
||||
([name path-params]
|
||||
(rfh/push-state @history name path-params nil))
|
||||
([name path-params query-params]
|
||||
(rfh/push-state @history name path-params query-params)))
|
||||
|
||||
(defn
|
||||
^{:see-also ["reitit.frontend.history/replace-state"]}
|
||||
replace-state
|
||||
"Updates the browser location and replaces latest entry in the history stack
|
||||
using URL built from a route defined by name, with given path-params and
|
||||
query-params.
|
||||
|
||||
Will also trigger on-navigate callback on Reitit frontend History handler.
|
||||
|
||||
Note: currently collections in query-parameters are encoded as field-value
|
||||
pairs separated by &, i.e. \"?a=1&a=2\", if you want to encode them
|
||||
differently, convert the collections to strings first.
|
||||
|
||||
See also:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState"
|
||||
([name]
|
||||
(rfh/replace-state @history name nil nil))
|
||||
([name path-params]
|
||||
(rfh/replace-state @history name path-params nil))
|
||||
([name path-params query-params]
|
||||
(rfh/replace-state @history name path-params query-params)))
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
(ns reitit.frontend.history
|
||||
"Provides integration to hash-change or HTML5 History
|
||||
events."
|
||||
(:require [reitit.core :as reitit]
|
||||
[reitit.frontend :as rf]
|
||||
[goog.events :as gevents])
|
||||
(:require [goog.events :as gevents]
|
||||
[reitit.core :as reitit]
|
||||
[reitit.frontend :as rf])
|
||||
(:import goog.Uri))
|
||||
|
||||
(defprotocol History
|
||||
|
|
@ -40,7 +40,7 @@
|
|||
nil)
|
||||
(-on-navigate [this path]
|
||||
(reset! last-fragment path)
|
||||
(on-navigate (rf/match-by-path router path) this))
|
||||
(on-navigate (rf/match-by-path router path this) this))
|
||||
(-get-path [this]
|
||||
;; Remove #
|
||||
;; "" or "#" should be same as "#/"
|
||||
|
|
@ -125,7 +125,7 @@
|
|||
(-on-navigate this (-get-path this))
|
||||
this))
|
||||
(-on-navigate [this path]
|
||||
(on-navigate (rf/match-by-path router path) this))
|
||||
(on-navigate (rf/match-by-path router path this) this))
|
||||
(-stop [this]
|
||||
(gevents/unlistenByKey listen-key)
|
||||
(gevents/unlistenByKey click-listen-key)
|
||||
|
|
@ -171,40 +171,73 @@
|
|||
(map->FragmentHistory opts)
|
||||
(map->Html5History opts))))))
|
||||
|
||||
(defn stop! [history]
|
||||
(defn stop!
|
||||
"Stops the given history handler, removing the event handlers."
|
||||
[history]
|
||||
(if history
|
||||
(-stop history)))
|
||||
|
||||
(defn href
|
||||
([history k]
|
||||
(href history k nil))
|
||||
([history k params]
|
||||
(href history k params nil))
|
||||
([history k params query]
|
||||
(let [match (rf/match-by-name! (:router history) k params)]
|
||||
(-href history (reitit/match->path match query)))))
|
||||
"Generate a URL for a route defined by name, with given path-params and query-params.
|
||||
|
||||
(defn push-state
|
||||
"Sets the new route, leaving previous route in history."
|
||||
([history k]
|
||||
(push-state history k nil nil))
|
||||
([history k params]
|
||||
(push-state history k params nil))
|
||||
([history k params query]
|
||||
(let [match (rf/match-by-name! (:router history) k params)
|
||||
path (reitit/match->path match query)]
|
||||
The URL is formatted using Reitit frontend history handler, so using it with
|
||||
anchor element href will correctly trigger route change event.
|
||||
|
||||
Note: currently collections in query parameters are encoded as field-value
|
||||
pairs separated by &, i.e. \"?a=1&a=2\", if you want to encode them
|
||||
differently, convert the collections to strings first."
|
||||
([history name]
|
||||
(href history name nil))
|
||||
([history name path-params]
|
||||
(href history name path-params nil))
|
||||
([history name path-params query-params]
|
||||
(let [match (rf/match-by-name! (:router history) name path-params)]
|
||||
(-href history (reitit/match->path match query-params)))))
|
||||
|
||||
(defn
|
||||
^{:see-also ["reitit.core/match->path"]}
|
||||
push-state
|
||||
"Updates the browser URL and pushes new entry to the history stack using
|
||||
a route defined by name, with given path-params and query-params.
|
||||
|
||||
Will also trigger on-navigate callback on Reitit frontend History handler.
|
||||
|
||||
Note: currently collections in query-parameters are encoded as field-value
|
||||
pairs separated by &, i.e. \"?a=1&a=2\", if you want to encode them
|
||||
differently, convert the collections to strings first.
|
||||
|
||||
See also:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/History/pushState"
|
||||
([history name]
|
||||
(push-state history name nil nil))
|
||||
([history name path-params]
|
||||
(push-state history name path-params nil))
|
||||
([history name path-params query-params]
|
||||
(let [match (rf/match-by-name! (:router history) name path-params)
|
||||
path (reitit/match->path match query-params)]
|
||||
;; pushState and replaceState don't trigger popstate event so call on-navigate manually
|
||||
(.pushState js/window.history nil "" (-href history path))
|
||||
(-on-navigate history path))))
|
||||
|
||||
(defn replace-state
|
||||
"Replaces current route. I.e. current route is not left on history."
|
||||
([history k]
|
||||
(replace-state history k nil nil))
|
||||
([history k params]
|
||||
(replace-state history k params nil))
|
||||
([history k params query]
|
||||
(let [match (rf/match-by-name! (:router history) k params)
|
||||
path (reitit/match->path match query)]
|
||||
"Updates the browser location and replaces latest entry in the history stack
|
||||
using URL built from a route defined by name, with given path-params and
|
||||
query-params.
|
||||
|
||||
Will also trigger on-navigate callback on Reitit frontend History handler.
|
||||
|
||||
Note: currently collections in query-parameters are encoded as field-value
|
||||
pairs separated by &, i.e. \"?a=1&a=2\", if you want to encode them
|
||||
differently, convert the collections to strings first.
|
||||
|
||||
See also:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState"
|
||||
([history name]
|
||||
(replace-state history name nil nil))
|
||||
([history name path-params]
|
||||
(replace-state history name path-params nil))
|
||||
([history name path-params query-params]
|
||||
(let [match (rf/match-by-name! (:router history) name path-params)
|
||||
path (reitit/match->path match query-params)]
|
||||
(.replaceState js/window.history nil "" (-href history path))
|
||||
(-on-navigate history path))))
|
||||
|
|
|
|||
1
modules/reitit-http/.clj-kondo/config.edn
Symbolic link
1
modules/reitit-http/.clj-kondo/config.edn
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../.clj-kondo/module_config.edn
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
(defproject metosin/reitit-http "0.5.10"
|
||||
(defproject metosin/reitit-http "0.5.18"
|
||||
:description "Reitit: HTTP routing with interceptors"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
(ns reitit.http
|
||||
(:require [meta-merge.core :refer [meta-merge]]
|
||||
[reitit.interceptor :as interceptor]
|
||||
[reitit.core :as r]
|
||||
[reitit.exception :as ex]
|
||||
[reitit.ring :as ring]
|
||||
[reitit.core :as r]))
|
||||
[reitit.interceptor :as interceptor]
|
||||
[reitit.ring :as ring]))
|
||||
|
||||
(defrecord Endpoint [data interceptors queue handler path method])
|
||||
|
||||
(defn coerce-handler [[path data] {:keys [expand] :as opts}]
|
||||
[path (reduce
|
||||
(fn [acc method]
|
||||
(if (contains? acc method)
|
||||
(update acc method expand opts)
|
||||
acc)) data ring/http-methods)])
|
||||
(fn [acc method]
|
||||
(if (contains? acc method)
|
||||
(update acc method expand opts)
|
||||
acc)) data ring/http-methods)])
|
||||
|
||||
(defn compile-result [[path data] {:keys [::default-options-endpoint expand] :as opts}]
|
||||
(defn compile-result [[path data] {:keys [::default-options-endpoint expand meta-merge-fn] :as opts}]
|
||||
(let [[top childs] (ring/group-keys data)
|
||||
childs (cond-> childs
|
||||
(and (not (:options childs)) (not (:handler top)) default-options-endpoint)
|
||||
(assoc :options (expand default-options-endpoint opts)))
|
||||
(and (not (:options childs)) (not (:handler top)) default-options-endpoint)
|
||||
(assoc :options (expand default-options-endpoint opts)))
|
||||
compile (fn [[path data] opts scope]
|
||||
(interceptor/compile-result [path data] opts scope))
|
||||
->endpoint (fn [p d m s]
|
||||
|
|
@ -29,19 +29,19 @@
|
|||
(assoc :method m))))
|
||||
->methods (fn [any? data]
|
||||
(reduce
|
||||
(fn [acc method]
|
||||
(cond-> acc
|
||||
any? (assoc method (->endpoint path data method nil))))
|
||||
(ring/map->Methods {})
|
||||
ring/http-methods))]
|
||||
(fn [acc method]
|
||||
(cond-> acc
|
||||
any? (assoc method (->endpoint path data method nil))))
|
||||
(ring/map->Methods {})
|
||||
ring/http-methods))]
|
||||
(if-not (seq childs)
|
||||
(->methods true top)
|
||||
(reduce-kv
|
||||
(fn [acc method data]
|
||||
(let [data (meta-merge top data)]
|
||||
(assoc acc method (->endpoint path data method method))))
|
||||
(->methods (:handler top) data)
|
||||
childs))))
|
||||
(fn [acc method data]
|
||||
(let [data ((or meta-merge-fn meta-merge) top data)]
|
||||
(assoc acc method (->endpoint path data method method))))
|
||||
(->methods (:handler top) data)
|
||||
childs))))
|
||||
|
||||
(defn router
|
||||
"Creates a [[reitit.core/Router]] from raw route data and optionally an options map with
|
||||
|
|
@ -89,7 +89,8 @@
|
|||
default-interceptors (->> interceptors
|
||||
(map #(interceptor/into-interceptor % nil (r/options router))))
|
||||
default-queue (interceptor/queue executor default-interceptors)
|
||||
enrich-request (ring/create-enrich-request inject-match? inject-router?)]
|
||||
enrich-request (ring/create-enrich-request inject-match? inject-router?)
|
||||
enrich-default-request (ring/create-enrich-default-request inject-router?)]
|
||||
{:name ::router
|
||||
:enter (fn [{:keys [request] :as context}]
|
||||
(if-let [match (r/match-by-path router (:uri request))]
|
||||
|
|
@ -101,7 +102,9 @@
|
|||
context (assoc context :request request)
|
||||
queue (interceptor/queue executor (concat default-interceptors interceptors))]
|
||||
(interceptor/enqueue executor context queue))
|
||||
(interceptor/enqueue executor context default-queue)))
|
||||
(let [request (enrich-default-request request router)
|
||||
context (assoc context :request request)]
|
||||
(interceptor/enqueue executor context default-queue))))
|
||||
:leave (fn [context]
|
||||
(if-not (:response context)
|
||||
(assoc context :response (default-handler (:request context)))
|
||||
|
|
@ -130,7 +133,7 @@
|
|||
(assoc ::interceptor/queue (partial interceptor/queue executor))
|
||||
(dissoc :data) ; data is already merged into routes
|
||||
(cond-> (seq interceptors)
|
||||
(update-in [:data :interceptors] (partial into (vec interceptors)))))
|
||||
(update-in [:data :interceptors] (partial into (vec interceptors)))))
|
||||
router (reitit.http/router (r/routes router) router-opts) ;; will re-compile the interceptors
|
||||
enrich-request (ring/create-enrich-request inject-match? inject-router?)
|
||||
enrich-default-request (ring/create-enrich-default-request inject-router?)]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
(ns reitit.http.coercion
|
||||
(:require [reitit.coercion :as coercion]
|
||||
[reitit.spec :as rs]
|
||||
[reitit.impl :as impl]))
|
||||
[reitit.impl :as impl]
|
||||
[reitit.spec :as rs]))
|
||||
|
||||
(defn coerce-request-interceptor
|
||||
"Interceptor for pluggable request coercion.
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
(ns reitit.http.spec
|
||||
(:require [clojure.spec.alpha :as s]
|
||||
[reitit.ring.spec :as rrs]
|
||||
[reitit.interceptor :as interceptor]
|
||||
[reitit.exception :as exception]
|
||||
[reitit.interceptor :as interceptor]
|
||||
[reitit.ring.spec :as rrs]
|
||||
[reitit.spec :as rs]))
|
||||
|
||||
;;
|
||||
|
|
@ -22,5 +22,5 @@
|
|||
[routes {:keys [spec ::rs/wrap] :or {spec ::data, wrap identity}}]
|
||||
(when-let [problems (rrs/validate-route-data routes :interceptors wrap spec)]
|
||||
(exception/fail!
|
||||
::rs/invalid-route-data
|
||||
{:problems problems})))
|
||||
::rs/invalid-route-data
|
||||
{:problems problems})))
|
||||
|
|
|
|||
1
modules/reitit-interceptors/.clj-kondo/config.edn
Symbolic link
1
modules/reitit-interceptors/.clj-kondo/config.edn
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../.clj-kondo/module_config.edn
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
(defproject metosin/reitit-interceptors "0.5.10"
|
||||
(defproject metosin/reitit-interceptors "0.5.18"
|
||||
:description "Reitit, common interceptors bundled"
|
||||
:url "https://github.com/metosin/reitit"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
|
|||
|
|
@ -20,7 +20,9 @@
|
|||
|
||||
(defn- polish [ctx]
|
||||
(-> ctx
|
||||
(dissoc ::original ::previous :stack :queue)
|
||||
(dissoc ::original ::previous :stack :queue
|
||||
:io.pedestal.interceptor.chain/stack
|
||||
:io.pedestal.interceptor.chain/queue)
|
||||
(update :request dissoc ::r/match ::r/router)))
|
||||
|
||||
(defn- handle [name stage]
|
||||
|
|
@ -36,16 +38,16 @@
|
|||
[stages {:keys [enter leave error name] :as interceptor}]
|
||||
(if (->> (select-keys interceptor stages) (vals) (keep identity) (seq))
|
||||
(cond-> {:name ::diff}
|
||||
(and enter (stages :enter)) (assoc :enter (handle name :enter))
|
||||
(and leave (stages :leave)) (assoc :leave (handle name :leave))
|
||||
(and error (stages :error)) (assoc :error (handle name :error)))))
|
||||
(and enter (stages :enter)) (assoc :enter (handle name :enter))
|
||||
(and leave (stages :leave)) (assoc :leave (handle name :leave))
|
||||
(and error (stages :error)) (assoc :error (handle name :error)))))
|
||||
|
||||
(defn print-context-diffs
|
||||
"A interceptor chain transformer that adds a context diff printer between all interceptors"
|
||||
[interceptors]
|
||||
(reduce
|
||||
(fn [chain interceptor]
|
||||
(into chain (keep identity [(diff-interceptor #{:leave :error} interceptor)
|
||||
interceptor
|
||||
(diff-interceptor #{:enter} interceptor)])))
|
||||
[(diff-interceptor #{:enter :leave :error} {:enter identity})] interceptors))
|
||||
(fn [chain interceptor]
|
||||
(into chain (keep identity [(diff-interceptor #{:leave :error} interceptor)
|
||||
interceptor
|
||||
(diff-interceptor #{:enter} interceptor)])))
|
||||
[(diff-interceptor #{:enter :leave :error} {:enter identity})] interceptors))
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
(ns reitit.http.interceptors.exception
|
||||
(:require [reitit.coercion :as coercion]
|
||||
[reitit.ring :as ring]
|
||||
[clojure.spec.alpha :as s]
|
||||
[clojure.string :as str])
|
||||
(:import (java.time Instant)
|
||||
(java.io PrintWriter)))
|
||||
(:require [clojure.spec.alpha :as s]
|
||||
[clojure.string :as str]
|
||||
[reitit.coercion :as coercion]
|
||||
[reitit.ring :as ring])
|
||||
(:import (java.io PrintWriter Writer)
|
||||
(java.time Instant)))
|
||||
|
||||
(s/def ::handlers (s/map-of any? fn?))
|
||||
(s/def ::spec (s/keys :opt-un [::handlers]))
|
||||
|
|
@ -25,17 +25,17 @@
|
|||
error-handler (or (get handlers type)
|
||||
(get handlers ex-class)
|
||||
(some
|
||||
(partial get handlers)
|
||||
(descendants type))
|
||||
(partial get handlers)
|
||||
(descendants type))
|
||||
(some
|
||||
(partial get handlers)
|
||||
(super-classes ex-class))
|
||||
(partial get handlers)
|
||||
(super-classes ex-class))
|
||||
(get handlers ::default))]
|
||||
(if-let [wrap (get handlers ::wrap)]
|
||||
(wrap error-handler error request)
|
||||
(error-handler error request))))
|
||||
|
||||
(defn print! [^PrintWriter writer & more]
|
||||
(defn print! [^Writer writer & more]
|
||||
(.write writer (str (str/join " " more) "\n")))
|
||||
|
||||
;;
|
||||
|
|
@ -68,7 +68,7 @@
|
|||
|
||||
(defn wrap-log-to-console [handler ^Throwable e {:keys [uri request-method] :as req}]
|
||||
(print! *out* (Instant/now) request-method (pr-str uri) "=>" (.getMessage e))
|
||||
(.printStackTrace e ^PrintWriter *out*)
|
||||
(.printStackTrace e (PrintWriter. ^Writer *out*))
|
||||
(handler e req))
|
||||
|
||||
;;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
(ns reitit.http.interceptors.multipart
|
||||
(:require [reitit.coercion :as coercion]
|
||||
(:require [clojure.spec.alpha :as s]
|
||||
[reitit.coercion :as coercion]
|
||||
[reitit.spec]
|
||||
[ring.middleware.multipart-params :as multipart-params]
|
||||
[clojure.spec.alpha :as s]
|
||||
[spec-tools.core :as st])
|
||||
(:import (java.io File)))
|
||||
|
||||
|
|
@ -18,14 +18,14 @@
|
|||
(def temp-file-part
|
||||
"Spec for file param created by ring.middleware.multipart-params.temp-file store."
|
||||
(st/spec
|
||||
{:spec (s/keys :req-un [::filename ::content-type ::tempfile ::size])
|
||||
:swagger/type "file"}))
|
||||
{:spec (s/keys :req-un [::filename ::content-type ::tempfile ::size])
|
||||
:swagger/type "file"}))
|
||||
|
||||
(def bytes-part
|
||||
"Spec for file param created by ring.middleware.multipart-params.byte-array store."
|
||||
(st/spec
|
||||
{:spec (s/keys :req-un [::filename ::content-type ::bytes])
|
||||
:swagger/type "file"}))
|
||||
{:spec (s/keys :req-un [::filename ::content-type ::bytes])
|
||||
:swagger/type "file"}))
|
||||
|
||||
(defn- coerced-request [request coercers]
|
||||
(if-let [coerced (if coercers (coercion/coerce-request coercers request))]
|
||||
|
|
@ -49,7 +49,7 @@
|
|||
:compile (fn [{:keys [parameters coercion]} opts]
|
||||
(if-let [multipart (:multipart parameters)]
|
||||
(let [parameter-coercion {:multipart (coercion/->ParameterCoercion
|
||||
:multipart-params :string true true)}
|
||||
:multipart-params :string true true)}
|
||||
opts (assoc opts ::coercion/parameter-coercion parameter-coercion)
|
||||
coercers (if multipart (coercion/request-coercers coercion parameters opts))]
|
||||
{:data {:swagger {:consumes ^:replace #{"multipart/form-data"}}}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
(ns reitit.http.interceptors.muuntaja
|
||||
(:require [muuntaja.core :as m]
|
||||
[muuntaja.interceptor]
|
||||
[clojure.spec.alpha :as s]))
|
||||
(:require [clojure.spec.alpha :as s]
|
||||
[muuntaja.core :as m]
|
||||
[muuntaja.interceptor]))
|
||||
|
||||
(s/def ::muuntaja m/muuntaja?)
|
||||
(s/def ::spec (s/keys :opt-un [::muuntaja]))
|
||||
|
|
@ -40,10 +40,10 @@
|
|||
:compile (fn [{:keys [muuntaja parameters]} _]
|
||||
(if-let [muuntaja (or muuntaja default-muuntaja)]
|
||||
(merge
|
||||
(stripped (muuntaja.interceptor/format-interceptor muuntaja))
|
||||
(if (publish-swagger-data? parameters)
|
||||
{:data {:swagger {:produces (displace (m/encodes muuntaja))
|
||||
:consumes (displace (m/decodes muuntaja))}}}))))}))
|
||||
(stripped (muuntaja.interceptor/format-interceptor muuntaja))
|
||||
(if (publish-swagger-data? parameters)
|
||||
{:data {:swagger {:produces (displace (m/encodes muuntaja))
|
||||
:consumes (displace (m/decodes muuntaja))}}}))))}))
|
||||
|
||||
(defn format-negotiate-interceptor
|
||||
"Interceptor for content-negotiation.
|
||||
|
|
@ -87,9 +87,9 @@
|
|||
:compile (fn [{:keys [muuntaja parameters]} _]
|
||||
(if-let [muuntaja (or muuntaja default-muuntaja)]
|
||||
(merge
|
||||
(stripped (muuntaja.interceptor/format-request-interceptor muuntaja))
|
||||
(when (publish-swagger-data? parameters)
|
||||
{:data {:swagger {:consumes (displace (m/decodes muuntaja))}}}))))}))
|
||||
(stripped (muuntaja.interceptor/format-request-interceptor muuntaja))
|
||||
(when (publish-swagger-data? parameters)
|
||||
{:data {:swagger {:consumes (displace (m/decodes muuntaja))}}}))))}))
|
||||
|
||||
(defn format-response-interceptor
|
||||
"Interceptor for response formatting.
|
||||
|
|
@ -112,6 +112,6 @@
|
|||
:compile (fn [{:keys [muuntaja parameters]} _]
|
||||
(if-let [muuntaja (or muuntaja default-muuntaja)]
|
||||
(merge
|
||||
(stripped (muuntaja.interceptor/format-response-interceptor muuntaja))
|
||||
(when (publish-swagger-data? parameters)
|
||||
{:data {:swagger {:produces (displace (m/encodes muuntaja))}}}))))}))
|
||||
(stripped (muuntaja.interceptor/format-response-interceptor muuntaja))
|
||||
(when (publish-swagger-data? parameters)
|
||||
{:data {:swagger {:produces (displace (m/encodes muuntaja))}}}))))}))
|
||||
|
|
|
|||
1
modules/reitit-malli/.clj-kondo/config.edn
Symbolic link
1
modules/reitit-malli/.clj-kondo/config.edn
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../.clj-kondo/module_config.edn
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue