Merge branch 'master' into support-operationid

This commit is contained in:
Tommi Reiman 2023-01-09 17:27:00 +02:00 committed by GitHub
commit 42e988e518
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
160 changed files with 3544 additions and 2555 deletions

View file

@ -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

View file

@ -1,6 +1,6 @@
{;;:skip-comments true {;;:skip-comments true
:lint-as {potemkin/def-derived-map clojure.core/defrecord} :lint-as {potemkin/def-derived-map clojure.core/defrecord}
:linters {:if {:level :off} :linters {:missing-else-branch {:level :off}
:unused-binding {:level :off} :unused-binding {:level :off}
:unused-referred-var {:exclude {clojure.test [deftest testing is are] :unused-referred-var {:exclude {clojure.test [deftest testing is are]
cljs.test [deftest testing is are]}}}} cljs.test [deftest testing is are]}}}}

View file

@ -0,0 +1 @@
{:config-paths ["../../../.clj-kondo"]}

78
.github/workflows/testsuite.yml vendored Normal file
View 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

2
.gitignore vendored
View file

@ -13,4 +13,4 @@ pom.xml.asc
/_book /_book
figwheel_server.log figwheel_server.log
/.idea /.idea
.clj-kondo .clj-kondo

3
.lsp/config.edn Normal file
View file

@ -0,0 +1,3 @@
{:cljfmt {:indents {for-all [[:inner 0]]
are [[:inner 0]]}}
:clean {:ns-inner-blocks-indentation :same-line}}

View file

@ -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 [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) ## 0.5.10 (2020-10-22)
**[compare](https://github.com/metosin/reitit/compare/0.5.9...0.5.10)**
* updated deps: * updated deps:
```clj ```clj
@ -26,6 +156,8 @@ We use [Break Versioning][breakver]. The version numbers follow a `<major>.<mino
## 0.5.9 (2020-10-19) ## 0.5.9 (2020-10-19)
**[compare](https://github.com/metosin/reitit/compare/0.5.8...0.5.9)**
### `reitit-frontend` ### `reitit-frontend`
- `reitit.frontend.easy/start!` now correctly removes old event listeners - `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) ## 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). * Add `:conflicting` to route data spec [#444](https://github.com/metosin/reitit/pull/444).
## 0.5.7 (2020-10-18) ## 0.5.7 (2020-10-18)
**[compare](https://github.com/metosin/reitit/compare/0.5.6...0.5.7)**
* updated deps: * updated deps:
```clj ```clj
@ -55,6 +191,8 @@ when called repeatedly (e.g. with hot code reload workflow)
## 0.5.6 (2020-09-26) ## 0.5.6 (2020-09-26)
**[compare](https://github.com/metosin/reitit/compare/0.5.5...0.5.6)**
* updated deps: * updated deps:
```clj ```clj
@ -79,6 +217,8 @@ when called repeatedly (e.g. with hot code reload workflow)
## 0.5.5 (2020-07-15) ## 0.5.5 (2020-07-15)
**[compare](https://github.com/metosin/reitit/compare/0.5.4...0.5.5)**
* recompile with Java8 * recompile with Java8
```clj ```clj
@ -87,18 +227,24 @@ when called repeatedly (e.g. with hot code reload workflow)
## 0.5.4 (2020-07-13) ## 0.5.4 (2020-07-13)
**[compare](https://github.com/metosin/reitit/compare/0.5.3...0.5.4)**
```clj ```clj
[metosin/malli "0.0.1-20200713.080243-20"] is available but we use "0.0.1-20200709.163702-18" [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) ## 0.5.3 (2020-07-09)
**[compare](https://github.com/metosin/reitit/compare/0.5.2...0.5.3)**
```clj ```clj
[metosin/malli "0.0.1-20200709.163702-18"] is available but we use "0.0.1-20200525.162645-15" [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) ## 0.5.2 (2020-05-27)
**[compare](https://github.com/metosin/reitit/compare/0.5.1...0.5.2)**
```clj ```clj
[metosin/malli "0.0.1-20200525.162645-15"] is available but we use "0.0.1-20200404.091302-14" [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) ## 0.5.1 (2020-05-18)
**[compare](https://github.com/metosin/reitit/compare/0.5.0...0.5.1)**
```clj ```clj
[metosin/sieppari "0.0.0-alpha13"] is available but we use "0.0.0-alpha10" [metosin/sieppari "0.0.0-alpha13"] is available but we use "0.0.0-alpha10"
``` ```

View file

@ -1,4 +1,4 @@
# reitit [![Build Status](https://img.shields.io/circleci/project/github/metosin/reitit.svg)](https://circleci.com/gh/metosin/reitit) [![cljdoc badge](https://cljdoc.org/badge/metosin/reitit)](https://cljdoc.org/jump/release/metosin/reitit) [![Slack](https://img.shields.io/badge/clojurians-reitit-blue.svg?logo=slack)](https://clojurians.slack.com/messages/reitit/) # reitit [![Build Status](https://github.com/metosin/reitit/workflows/testsuite/badge.svg)](https://github.com/metosin/reitit/actions?query=workflow%3Atestsuite) [![cljdoc badge](https://cljdoc.org/badge/metosin/reitit)](https://cljdoc.org/jump/release/metosin/reitit) [![Slack](https://img.shields.io/badge/clojurians-reitit-blue.svg?logo=slack)](https://clojurians.slack.com/messages/reitit/)
A fast data-driven router for Clojure(Script). 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/) * [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/) * [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) ## [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. 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: All main modules bundled:
```clj ```clj
[metosin/reitit "0.5.10"] [metosin/reitit "0.5.18"]
``` ```
Optionally, the parts can be required separately. 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). A Ring routing app with input & output coercion using [data-specs](https://github.com/metosin/spec-tools/blob/master/README.md#data-specs).
```clj ```clj
(require '[muuntaja.core :as m])
(require '[reitit.ring :as ring]) (require '[reitit.ring :as ring])
(require '[reitit.coercion.spec]) (require '[reitit.coercion.spec])
(require '[reitit.ring.coercion :as rrc]) (require '[reitit.ring.coercion :as rrc])
(require '[reitit.ring.middleware.muuntaja :as muuntaja])
(require '[reitit.ring.middleware.parameters :as parameters])
(def app (def app
(ring/ring-handler (ring/ring-handler
(ring/router (ring/router
["/api" ["/api"
["/math" {:get {:parameters {:query {:x int?, :y int?}} ["/math" {:get {:parameters {:query {:x int?, :y int?}}
:responses {200 {:body {:total pos-int?}}} :responses {200 {:body {:total int?}}}
:handler (fn [{{{:keys [x y]} :query} :parameters}] :handler (fn [{{{:keys [x y]} :query} :parameters}]
{:status 200 {:status 200
:body {:total (+ x y)}})}}]] :body {:total (+ x y)}})}}]]
;; router data effecting all routes ;; router data affecting all routes
{:data {:coercion reitit.coercion.spec/coercion {:data {:coercion reitit.coercion.spec/coercion
:middleware [rrc/coerce-exceptions-middleware :muuntaja m/instance
:middleware [parameters/parameters-middleware
rrc/coerce-request-middleware rrc/coerce-request-middleware
muuntaja/format-response-middleware
rrc/coerce-response-middleware]}}))) rrc/coerce-response-middleware]}})))
``` ```
@ -146,12 +153,17 @@ All examples are in https://github.com/metosin/reitit/tree/master/examples
## External resources ## External resources
* Simple web application using Ring/Reitit and Integrant: https://github.com/PrestanceDesign/usermanager-reitit-integrant-example * 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... * A simple Clojure backend using Reitit to serve up a RESTful API: [startrek](https://github.com/dharrigan/startrek). Technologies include:
* [startrek](https://git.sr.ht/~dharrigan/startrek) * [Donut System](https://github.com/donut-party/system)
* [startrek-ui](https://git.sr.ht/~dharrigan/startrek-ui) * [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/ * https://www.learnreitit.com/
* Lipas, liikuntapalvelut: https://github.com/lipas-liikuntapaikat/lipas * 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 * 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 ## More info
@ -172,6 +184,6 @@ Roadmap is mostly written in [issues](https://github.com/metosin/reitit/issues).
## License ## 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. Distributed under the Eclipse Public License, the same as Clojure.

View file

@ -0,0 +1 @@
hello

View file

@ -6,7 +6,7 @@
* Route [conflict resolution](./basics/route_conflicts.md) * Route [conflict resolution](./basics/route_conflicts.md)
* First-class [route data](./basics/route_data.md) * First-class [route data](./basics/route_data.md)
* Bi-directional routing * 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) * 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) * Friendly [Error Messages](./basics/error_messages.md)
* Extendable * Extendable
@ -40,7 +40,7 @@ There is [#reitit](https://clojurians.slack.com/messages/reitit/) in [Clojurians
All bundled: All bundled:
```clj ```clj
[metosin/reitit "0.5.10"] [metosin/reitit "0.5.18"]
``` ```
Optionally, the parts can be required separately. Optionally, the parts can be required separately.
@ -139,7 +139,7 @@ Routing:
```clj ```clj
(app {:request-method :get, :uri "/api/admin/users"}) (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"}) (app {:request-method :put, :uri "/api/admin/users"})
; nil ; nil

View file

@ -143,10 +143,10 @@ As the `Match` contains all the route data, we can create a new matching functio
(require '[clojure.string :as str]) (require '[clojure.string :as str])
(defn recursive-match-by-path [router path] (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)] (if-let [subrouter (-> match :data :router)]
(let [subpath (subs path (str/last-index-of (:template match) "/"))] (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))) (cons match submatch)))
(list match)))) (list match))))
``` ```
@ -206,10 +206,10 @@ First, we need to modify our matching function to support router references:
(deref x) x)) (deref x) x))
(defn recursive-match-by-path [router path] (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 <<)] (if-let [subrouter (-> match :data :router <<)]
(let [subpath (subs path (str/last-index-of (:template match) "/"))] (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))) (cons match submatch)))
(list match)))) (list match))))
``` ```

View file

@ -4,15 +4,16 @@ Routers can be configured via options. The following options are available for t
| key | description | key | description
|--------------|------------- |--------------|-------------
| `:path` | Base-path for routes | `:path` | Base-path for routes
| `:routes` | Initial resolved routes (default `[]`) | `:routes` | Initial resolved routes (default `[]`)
| `:data` | Initial route data (default `{}`) | `:data` | Initial route data (default `{}`)
| `:spec` | clojure.spec definition for a route data, see `reitit.spec` on how to use this | `: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}) | `: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`) | `: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` | `: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 | `: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
| `:validate` | Function of `routes opts => ()` to validate route (data) via side-effects | `:compile` | Function of `route opts => result` to compile a route handler
| `:conflicts` | Function of `{route #{route}} => ()` to handle conflicting routes | `:validate` | Function of `routes opts => ()` to validate route (data) via side-effects
| `:exception` | Function of `Exception => Exception ` to handle creation time exceptions (default `reitit.exception/exception`) | `:conflicts` | Function of `{route #{route}} => ()` to handle conflicting routes
| `:router` | Function of `routes opts => router` to override the actual router implementation | `: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

View file

@ -1,6 +1,6 @@
# Different Routers # 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 | | router | description |
| ------------------------------|-------------| | ------------------------------|-------------|

View file

@ -22,7 +22,7 @@ The default exception formatting uses `reitit.exception/exception`. It produces
## Pretty Errors ## Pretty Errors
```clj ```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. 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 ## Runtime Exception
See [Exception Handling with Ring](exceptions.md). See [Exception Handling with Ring](../ring/exceptions.md).

View file

@ -79,7 +79,7 @@ Resolved route tree:
; :name :user/ping}] ; :name :user/ping}]
; ["/api/admin/users" {:interceptors [::api] ; ["/api/admin/users" {:interceptors [::api]
; :roles #{:admin} ; :roles #{:admin}
; :name ::users} nil] ; :name ::users}]
; ["/api/admin/db" {:interceptors [::api ::db] ; ["/api/admin/db" {:interceptors [::api ::db]
; :roles #{:db-admin}}]] ; :roles #{:db-admin}}]]
``` ```

View file

@ -132,7 +132,7 @@ Routes are just data, so it's easy to create them programmatically:
### Explicit path-parameter syntax ### 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: With defaults:

View file

@ -63,14 +63,6 @@ Route names:
; [:user/ping :user/user] ; [: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 ### Composing
As routes are defined as plain data, it's easy to merge multiple route trees into a single router 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] ["/ping" ::ping]
["/db" ::db]]) ["/db" ::db]])
(r/router (def router
[admin-routes (r/router
user-routes]) [admin-routes
user-routes]))
``` ```
Merged route tree: Merged route tree:
@ -109,6 +102,6 @@ When router is created, the following steps are done:
* route arguments are expanded (via `:expand` option) * route arguments are expanded (via `:expand` option)
* routes are coerced (via `:coerce` options) * routes are coerced (via `:coerce` options)
* route tree is compiled (via `:compile` 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) * optionally, route data is validated (via `:validate` options)
* [router implementation](../advanced/different_routers.md) is automatically selected (or forced via `:router` options) and created * [router implementation](../advanced/different_routers.md) is automatically selected (or forced via `:router` options) and created

View file

@ -2,6 +2,10 @@
[Malli](https://github.com/metosin/malli) is data-driven Schema library for Clojure/Script. [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 ```clj
(require '[reitit.coercion.malli]) (require '[reitit.coercion.malli])
(require '[reitit.coercion :as coercion]) (require '[reitit.coercion :as coercion])
@ -13,7 +17,7 @@
:coercion reitit.coercion.malli/coercion :coercion reitit.coercion.malli/coercion
:parameters {:path [:map :parameters {:path [:map
[:company string?] [:company string?]
[:user-id int?]]}] [:user-id int?]]}}]
{:compile coercion/compile-request-coercers})) {:compile coercion/compile-request-coercers}))
(defn match-by-path-and-coerce! [path] (defn match-by-path-and-coerce! [path]
@ -44,18 +48,36 @@ Failing coercion:
; => ExceptionInfo Request coercion failed... ; => 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 ## Configuring coercion
Using `create` with options to create the coercion instead of `coercion`: Using `create` with options to create the coercion instead of `coercion`:
```clj ```clj
(require '[malli.util :as mu])
(reitit.coercion.malli/create (reitit.coercion.malli/create
{:transformers {:body {:default default-transformer-provider {:transformers {:body {:default reitit.coercion.malli/default-transformer-provider
:formats {"application/json" json-transformer-provider}} :formats {"application/json" reitit.coercion.malli/json-transformer-provider}}
:string {:default string-transformer-provider} :string {:default reitit.coercion.malli/string-transformer-provider}
:response {:default default-transformer-provider}} :response {:default reitit.coercion.malli/default-transformer-provider}}
;; set of keys to include in error messages ;; set of keys to include in error messages
:error-keys #{:type :coercion :in :schema :value :errors :humanized #_:transformed} :error-keys #{:type :coercion :in :schema :value :errors :humanized #_:transformed}
;; support lite syntax?
:lite true
;; schema identity function (default: close all map schemas) ;; schema identity function (default: close all map schemas)
:compile mu/closed-schema :compile mu/closed-schema
;; validate request & response ;; validate request & response

View file

@ -13,25 +13,48 @@
./scripts/test.sh cljs ./scripts/test.sh cljs
``` ```
## Formatting
```bash
clojure-lsp format
clojure-lsp clean-ns
```
## Documentation ## Documentation
The documentation lives under `doc` and it is hosted on [cljdoc](https://cljdoc.org). See their The documentation lives under `doc` and it is hosted on [cljdoc](https://cljdoc.org). See their
documentation for [library authors](https://github.com/cljdoc/cljdoc/blob/master/doc/userguide/for-library-authors.adoc) documentation for [library authors](https://github.com/cljdoc/cljdoc/blob/master/doc/userguide/for-library-authors.adoc)
## To bump up version: ## Making a release
We use [Break Versioning][breakver]. Remember our promise: patch-level bumps never include breaking changes! We use [Break Versioning][breakver]. Remember our promise: patch-level bumps never include breaking changes!
[breakver]: https://github.com/ptaoussanis/encore/blob/master/BREAK-VERSIONING.md [breakver]: https://github.com/ptaoussanis/encore/blob/master/BREAK-VERSIONING.md
```bash ```bash
# Check that you're using Java 8! Making the release with a newer Java version
# means that it is broken when used with Java 8.
java -version
# new version # new version
./scripts/set-version "1.0.0" ./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 # works
./scripts/lein-modules install
lein test lein test
# deploy to clojars # deploy to clojars
./scripts/lein-modules do clean, deploy 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.

View file

@ -1,7 +1,7 @@
# Default Interceptors # Default Interceptors
```clj ```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. Just like the [ring default middleware](../ring/default_middleware.md), but for interceptors.

View file

@ -5,7 +5,7 @@ Reitit has also support for [interceptors](http://pedestal.io/reference/intercep
## Reitit-http ## Reitit-http
```clj ```clj
[metosin/reitit-http "0.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. 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.

View file

@ -3,7 +3,7 @@
[Pedestal](http://pedestal.io/) is a backend web framework for Clojure. `reitit-pedestal` provides an alternative routing engine for Pedestal. [Pedestal](http://pedestal.io/) is a backend web framework for Clojure. `reitit-pedestal` provides an alternative routing engine for Pedestal.
```clj ```clj
[metosin/reitit-pedestal "0.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)? Why should one use reitit instead of the Pedestal [default routing](http://pedestal.io/reference/routing-quick-reference)?
@ -26,8 +26,8 @@ A minimalistic example on how to to swap the default-router with a reitit router
```clj ```clj
; [io.pedestal/pedestal.service "0.5.5"] ; [io.pedestal/pedestal.service "0.5.5"]
; [io.pedestal/pedestal.jetty "0.5.5"] ; [io.pedestal/pedestal.jetty "0.5.5"]
; [metosin/reitit-pedestal "0.5.10"] ; [metosin/reitit-pedestal "0.5.18"]
; [metosin/reitit "0.5.10"] ; [metosin/reitit "0.5.18"]
(require '[io.pedestal.http :as server]) (require '[io.pedestal.http :as server])
(require '[reitit.pedestal :as pedestal]) (require '[reitit.pedestal :as pedestal])

View file

@ -1,7 +1,7 @@
# Sieppari # Sieppari
```clj ```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). [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).

View file

@ -65,7 +65,7 @@ There is an extra option in http-router (actually, in the underlying interceptor
### Printing Context Diffs ### Printing Context Diffs
```clj ```clj
[metosin/reitit-interceptors "0.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: 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:

View file

@ -76,7 +76,7 @@ The routing sample taken from [bide](https://github.com/funcool/bide) README:
(r/match-by-path routes "/workspace/1/1"))) (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. 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.
@ -146,6 +146,6 @@ Few things that have an effect on performance:
* Wildcard-routes are an order of magnitude slower than static routes * Wildcard-routes are an order of magnitude slower than static routes
* Conflicting routes are served with LinearRouter, which is the slowest implementation. * Conflicting routes are served with LinearRouter, which is the slowest implementation.
* It's ok to mix non-wildcard, wildcard or even conflicting routes in a same routing tree. Reitit will create an hierarchy of routers to serve all the routes with best possible implementation. * It's ok to mix non-wildcard, wildcard or even conflicting routes in a same routing tree. Reitit will create an hierarchy of routers to serve all the routes with best possible implementation.
* Move computation from request processing time into creation time, using by compiling [middleware](ring/compiling_middleware.md), [interceptors](http/interceptors.md) and [route data](advanced/configuring_routers.md). * Move computation from request processing time into creation time, using by compiling [middleware](ring/compiling_middleware.md), [interceptors](http/interceptors.md) and [route data](advanced/configuring_routers.md).
* Unmounted middleware (or interceptor) is infinitely faster than a mounted one effectively doing nothing. * Unmounted middleware (or interceptor) is infinitely faster than a mounted one effectively doing nothing.

View file

@ -1,36 +1,37 @@
# RESTful form methods # RESTful form methods
When designing RESTful applications you will be doing a lot of "PATCH" and "DELETE" request, but most browsers don't support methods other than "GET" and "POST" when it comes to submitting forms. When designing RESTful applications you will be doing a lot of "PATCH" and "DELETE" request, but most browsers don't support methods other than "GET" and "POST" when it comes to submitting forms.
There is a pattern to solve this (pioneered by Rails) using a hidden "_method" field in the form and swapping out the "POST" method for whatever is in that field. There is a pattern to solve this (pioneered by Rails) using a hidden "_method" field in the form and swapping out the "POST" method for whatever is in that field.
We can do this with middleware in reitit like this: We can do this with middleware in reitit like this:
```clj ```clj
(defn- hidden-method (defn- hidden-method
[request] [request]
(keyword (some-> (or (get-in request [:form-params "_method"]) ;; look for "_method" field in :form-params
(or (get-in request [:form-params "_method"]) ;; look for "_method" field in :form-params (get-in request [:multipart-params "_method"])) ;; or in :multipart-params
(get-in request [:multipart-params "_method"])))) ;; or in :multipart-params clojure.string/lower-case
keyword))
(def wrap-hidden-method (def wrap-hidden-method
{:name ::wrap-hidden-method {:name ::wrap-hidden-method
:wrap (fn [handler] :wrap (fn [handler]
(fn [request] (fn [request]
(if-let [fm (and (= :post (:request-method request)) ;; if this is a :post request (if-let [fm (and (= :post (:request-method request)) ;; if this is a :post request
(hidden-method request))] ;; and there is a "_method" field (hidden-method request))] ;; and there is a "_method" field
(handler (assoc request :request-method fm)) ;; replace :request-method (handler (assoc request :request-method fm)) ;; replace :request-method
(handler request))))}) (handler request))))})
``` ```
And apply the middleware like this: And apply the middleware like this:
```clj ```clj
(reitit.ring/ring-handler (reitit.ring/ring-handler
(reitit.ring/router ...) (reitit.ring/router ...)
(reitit.ring/create-default-handler) (reitit.ring/create-default-handler)
{:middleware {:middleware
[reitit.ring.middleware.parameters/parameters-middleware ;; needed to have :form-params in the request map [reitit.ring.middleware.parameters/parameters-middleware ;; needed to have :form-params in the request map
reitit.ring.middleware.multipart/multipart-middleware ;; needed to have :multipart-params in the request map reitit.ring.middleware.multipart/multipart-middleware ;; needed to have :multipart-params in the request map
wrap-hidden-method]}) ;; our hidden method wrapper wrap-hidden-method]}) ;; our hidden method wrapper
``` ```
(NOTE: This middleware must be placed here and not inside the route data given to `reitit.ring/handler`. (NOTE: This middleware must be placed here and not inside the route data given to `reitit.ring/handler`.
This is so that our middleware is applied before reitit matches the request with a specific handler using the wrong method.) This is so that our middleware is applied before reitit matches the request with a specific handler using the wrong method.)

View file

@ -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`. 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 ```clj
(require '[reitit.coercion.schema]) (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 ### 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 ```clj
(require '[reitit.ring.coercion :as rrc]) (require '[reitit.ring.coercion :as rrc])
@ -150,7 +150,7 @@ Invalid response:
## Pretty printing spec errors ## 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 ```clj
(require '[reitit.ring :as ring]) (require '[reitit.ring :as ring])
@ -216,7 +216,7 @@ Spec problems are exposed as-is into request & response coercion errors, enablin
### Optimizations ### 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: We can query the compiled middleware chain for the routes:

View file

@ -1,12 +1,12 @@
# Compiling Middleware # 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 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 ## Normal Middleware

View file

@ -1,8 +1,8 @@
# Content Negotiation # 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. 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 ## 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 ```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`. 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 ## 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 ```clj
(def new-muuntaja (def new-muuntaja
(m/create (m/create
(-> m/default-options (-> m/default-options
(assoc-in [:formats "application/json" :decoder-opts :bigdecimals] true) (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"))))
``` ```

View file

@ -1,19 +1,19 @@
# Data-driven Middleware # 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: Reitit defines middleware as data:
1. Middleware can be defined as first-class data entries 1. A 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) 2. A middleware can be mounted as a [duct-style](https://github.com/duct-framework/duct/wiki/Configuration) vector (of middlewares)
4. Middleware can be optimized & [compiled](compiling_middleware.md) against an endpoint 4. A middleware can be optimized & [compiled](compiling_middleware.md) against an endpoint
3. Middleware chain can be transformed by the router 3. A middleware chain can be transformed by the router
## Middleware as data ## 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 | | 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` | `:wrap` | The actual middleware function of `handler & args => request => response`
| `:compile` | Middleware compilation function, see [compiling middleware](compiling_middleware.md). | `: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. 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 ### Creating Middleware
The following produce identical middleware runtime function. The following examples produce identical middleware runtime functions.
### Function ### Function
@ -77,7 +77,7 @@ The following produce identical middleware runtime function.
:handler handler}}]]))) :handler handler}}]])))
``` ```
All the middleware are applied correctly: All the middlewares are applied correctly:
```clj ```clj
(app {:request-method :get, :uri "/api/ping"}) (app {:request-method :get, :uri "/api/ping"})
@ -86,7 +86,7 @@ All the middleware are applied correctly:
## Compiling middleware ## 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 ## Ideas for the future

View file

@ -1,6 +1,6 @@
# Default handler # 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 ```clj
(require '[reitit.ring :as ring]) (require '[reitit.ring :as ring])

View file

@ -1,10 +1,10 @@
# Default Middleware # Default Middleware
```clj ```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) * [Parameter Handling](#parameters-handling)
* [Exception Handling](#exception-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 `reitit.ring.middleware.parameters/parameters-middleware` to capture query- and form-params. Wraps
`ring.middleware.params/wrap-params`. `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 ## Exception Handling
@ -32,7 +32,7 @@ See [Content Negotiation](content_negotiation.md).
Wrapper for [Ring Multipart Middleware](https://github.com/ring-clojure/ring/blob/master/ring-core/src/ring/middleware/multipart_params.clj). Emits swagger `:consumes` definitions automatically. Wrapper for [Ring Multipart Middleware](https://github.com/ring-clojure/ring/blob/master/ring-core/src/ring/middleware/multipart_params.clj). Emits swagger `:consumes` definitions automatically.
Expected route data: Expected route data:
| key | description | | key | description |
| -------------|-------------| | -------------|-------------|
| `[:parameters :multipart]` | mounts only if defined for a route. | `[:parameters :multipart]` | mounts only if defined for a route.

View file

@ -1,8 +1,8 @@
# Dynamic Extensions # 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 ```clj
(require '[reitit.ring :as ring]) (require '[reitit.ring :as ring])

View file

@ -1,10 +1,10 @@
# Exception Handling with Ring # Exception Handling with Ring
```clj ```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 ```clj
(require '[reitit.ring.middleware.exception :as exception]) (require '[reitit.ring.middleware.exception :as exception])
@ -36,7 +36,7 @@ A preconfigured middleware using `exception/default-handlers`. Catches:
### `exception/create-exception-middleware` ### `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: 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 2) Class of exception
3) `:type` ancestors of exception ex-data 3) `:type` ancestors of exception ex-data
4) Super Classes of exception 4) Super Classes of exception
5) The ::default handler 5) The `::default` handler
```clj ```clj
;; type hierarchy ;; type hierarchy
@ -94,7 +94,7 @@ The handler is selected from the options map by exception identifier in the foll
(def app (def app
(ring/ring-handler (ring/ring-handler
(ring/router (ring/router
["/fail" (fn [_] (throw (ex-info "fail" {:type ::failue})))] ["/fail" (fn [_] (throw (ex-info "fail" {:type ::failure})))]
{:data {:middleware [exception-middleware]}}))) {:data {:middleware [exception-middleware]}})))
(app {:request-method :get, :uri "/fail"}) (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, ; => {:status 500,
; :body {:message "default" ; :body {:message "default"
; :exception clojure.lang.ExceptionInfo ; :exception clojure.lang.ExceptionInfo
; :data {:type :user/failue} ; :data {:type :user/failure}
; :uri "/fail"}} ; :uri "/fail"}}
``` ```

View file

@ -5,14 +5,14 @@
Read more about the [Ring Concepts](https://github.com/ring-clojure/ring/wiki/Concepts). Read more about the [Ring Concepts](https://github.com/ring-clojure/ring/wiki/Concepts).
```clj ```clj
[metosin/reitit-ring "0.5.10"] [metosin/reitit-ring "0.5.18"]
``` ```
## `reitit.ring/ring-router` ## `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). `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 | | key | description |
| ----------------------------------------|-------------| | ----------------------------------------|-------------|
@ -53,7 +53,7 @@ Given a `ring-router`, optional default-handler & options, `ring-handler` functi
| key | description | | 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-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) | `:inject-router?` | Boolean to inject `router` into request under `:reitit.core/router` key (default true)
@ -91,7 +91,7 @@ The router can be accessed via `get-router`:
# Request-method based routing # Request-method based routing
Handlers can be placed either to the top-level (all methods) or under a specific method (`:get`, `:head`, `:patch`, `:delete`, `:options`, `:post`, `:put` or `:trace`). Top-level handler is used if request-method based handler is not found. Handlers can be placed either to the top-level (all methods) or under a specific method (`:get`, `:head`, `:patch`, `:delete`, `:options`, `:post`, `:put` or `:trace`). Top-level handler is used if request-method based handler is not found.
By default, the `:options` route is generated for all paths - to enable thing like [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing). By default, the `:options` route is generated for all paths - to enable thing like [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing).
@ -196,7 +196,7 @@ Top-level middleware, applied before any routing is done:
(ring/router (ring/router
["/api" {:middleware [[mw :api]]} ["/api" {:middleware [[mw :api]]}
["/get" {:get handler}]]) ["/get" {:get handler}]])
nil nil
{:middleware [[mw :top]]})) {:middleware [[mw :top]]}))
(app {:request-method :get, :uri "/api/get"}) (app {:request-method :get, :uri "/api/get"})

View file

@ -1,8 +1,12 @@
# Static Resources (Clojure Only) # 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 ## Internal routes
@ -33,7 +37,9 @@ To serve static files with conflicting routes, e.g. `"/*"`, one needs to disable
## External routes ## 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 ```clj
(ring/ring-handler (ring/ring-handler
@ -46,21 +52,19 @@ A better way to serve files from conflicting paths, e.g. `"/*"`, is to serve the
## Configuration ## 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 `:` | :parameter | optional name of the wildcard parameter, defaults to unnamed keyword `:`
| :root | optional resource root, defaults to `\"public\"` | :root | optional resource root, defaults to `\"public\"`
| :path | 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 | :loader | optional class loader to resolve the resources
| :index-files | optional vector of index-files to look in a resource directory, defaults to `[\"index.html\"]` | :index-files | optional vector of index-files to look in a resource directory, defaults to `[\"index.html\"]`
| :not-found-handler | optional handler function to use if the requested resource is missing (404 Not Found) | :not-found-handler | optional handler function to use if the requested resource is missing (404 Not Found)
### TODO ### TODO
* support for things like `:cache`, `:etag`, `:last-modified?`, and `:gzip` * support for things like `:cache`, `:etag`, `:last-modified?`, and `:gzip`
* support for ClojureScript * support for ClojureScript
* serve from file-system

View file

@ -1,7 +1,7 @@
# Swagger Support # 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. 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. [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 | | key | description |
| -----------------|-------------| | -----------------|-------------|

View file

@ -59,7 +59,7 @@ There is an extra option in ring-router (actually, in the underlying middleware-
### Printing Request Diffs ### Printing Request Diffs
```clj ```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: 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
View 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!

View file

@ -2,6 +2,6 @@
:description "Reitit Buddy Auth App" :description "Reitit Buddy Auth App"
:dependencies [[org.clojure/clojure "1.10.1"] :dependencies [[org.clojure/clojure "1.10.1"]
[ring/ring-jetty-adapter "1.8.1"] [ring/ring-jetty-adapter "1.8.1"]
[metosin/reitit "0.5.10"] [metosin/reitit "0.5.18"]
[buddy "2.0.0"]] [buddy "2.0.0"]]
:repl-options {:init-ns example.server}) :repl-options {:init-ns example.server})

View file

@ -10,9 +10,9 @@
[ring "1.7.1"] [ring "1.7.1"]
[hiccup "1.0.5"] [hiccup "1.0.5"]
[org.clojure/clojurescript "1.10.439"] [org.clojure/clojurescript "1.10.439"]
[metosin/reitit "0.5.10"] [metosin/reitit "0.5.18"]
[metosin/reitit-schema "0.5.10"] [metosin/reitit-schema "0.5.18"]
[metosin/reitit-frontend "0.5.10"] [metosin/reitit-frontend "0.5.18"]
;; Just for pretty printting the match ;; Just for pretty printting the match
[fipp "0.6.14"]] [fipp "0.6.14"]]

View file

@ -10,9 +10,9 @@
[ring "1.7.1"] [ring "1.7.1"]
[hiccup "1.0.5"] [hiccup "1.0.5"]
[org.clojure/clojurescript "1.10.439"] [org.clojure/clojurescript "1.10.439"]
[metosin/reitit "0.5.10"] [metosin/reitit "0.5.18"]
[metosin/reitit-schema "0.5.10"] [metosin/reitit-schema "0.5.18"]
[metosin/reitit-frontend "0.5.10"] [metosin/reitit-frontend "0.5.18"]
;; Just for pretty printting the match ;; Just for pretty printting the match
[fipp "0.6.14"]] [fipp "0.6.14"]]

View file

@ -10,9 +10,9 @@
[ring "1.7.1"] [ring "1.7.1"]
[hiccup "1.0.5"] [hiccup "1.0.5"]
[org.clojure/clojurescript "1.10.520"] [org.clojure/clojurescript "1.10.520"]
[metosin/reitit "0.5.10"] [metosin/reitit "0.5.18"]
[metosin/reitit-spec "0.5.10"] [metosin/reitit-spec "0.5.18"]
[metosin/reitit-frontend "0.5.10"] [metosin/reitit-frontend "0.5.18"]
;; Just for pretty printting the match ;; Just for pretty printting the match
[fipp "0.6.14"]] [fipp "0.6.14"]]

View file

@ -10,9 +10,9 @@
[ring "1.7.1"] [ring "1.7.1"]
[hiccup "1.0.5"] [hiccup "1.0.5"]
[org.clojure/clojurescript "1.10.520"] [org.clojure/clojurescript "1.10.520"]
[metosin/reitit "0.5.10"] [metosin/reitit "0.5.18"]
[metosin/reitit-spec "0.5.10"] [metosin/reitit-spec "0.5.18"]
[metosin/reitit-frontend "0.5.10"] [metosin/reitit-frontend "0.5.18"]
;; Just for pretty printting the match ;; Just for pretty printting the match
[fipp "0.6.14"]] [fipp "0.6.14"]]

View file

@ -1,7 +1,7 @@
(defproject frontend-re-frame "0.1.0-SNAPSHOT" (defproject frontend-re-frame "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.10.0"] :dependencies [[org.clojure/clojure "1.10.0"]
[org.clojure/clojurescript "1.10.520"] [org.clojure/clojurescript "1.10.520"]
[metosin/reitit "0.5.10"] [metosin/reitit "0.5.18"]
[reagent "0.8.1"] [reagent "0.8.1"]
[re-frame "0.10.6"]] [re-frame "0.10.6"]]

View file

@ -7,6 +7,14 @@
[reitit.frontend.controllers :as rfc] [reitit.frontend.controllers :as rfc]
[reitit.frontend.easy :as rfe])) [reitit.frontend.easy :as rfe]))
;;; Effects ;;;
;; Triggering navigation from events.
(re-frame/reg-fx :push-state
(fn [route]
(apply rfe/push-state route)))
;;; Events ;;; ;;; Events ;;;
(re-frame/reg-event-db ::initialize-db (re-frame/reg-event-db ::initialize-db
@ -16,7 +24,7 @@
{:current-route nil}))) {:current-route nil})))
(re-frame/reg-event-fx ::push-state (re-frame/reg-event-fx ::push-state
(fn [db [_ & route]] (fn [_ [_ & route]]
{:push-state route})) {:push-state route}))
(re-frame/reg-event-db ::navigated (re-frame/reg-event-db ::navigated
@ -49,14 +57,6 @@
[:div [:div
[:h1 "This is sub-page 2"]]) [: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 ;;; ;;; Routes ;;;
(defn href (defn href

View file

@ -10,9 +10,9 @@
[ring "1.8.1"] [ring "1.8.1"]
[hiccup "1.0.5"] [hiccup "1.0.5"]
[org.clojure/clojurescript "1.10.773"] [org.clojure/clojurescript "1.10.773"]
[metosin/reitit "0.5.10"] [metosin/reitit "0.5.18"]
[metosin/reitit-spec "0.5.10"] [metosin/reitit-spec "0.5.18"]
[metosin/reitit-frontend "0.5.10"] [metosin/reitit-frontend "0.5.18"]
;; Just for pretty printting the match ;; Just for pretty printting the match
[fipp "0.6.23"]] [fipp "0.6.23"]]

View file

@ -3,5 +3,5 @@
:dependencies [[org.clojure/clojure "1.10.0"] :dependencies [[org.clojure/clojure "1.10.0"]
[ring/ring-jetty-adapter "1.7.1"] [ring/ring-jetty-adapter "1.7.1"]
[aleph "0.4.7-alpha5"] [aleph "0.4.7-alpha5"]
[metosin/reitit "0.5.10"]] [metosin/reitit "0.5.18"]]
:repl-options {:init-ns example.server}) :repl-options {:init-ns example.server})

View file

@ -5,5 +5,5 @@
[funcool/promesa "1.9.0"] [funcool/promesa "1.9.0"]
[manifold "0.1.8"] [manifold "0.1.8"]
[ring/ring-jetty-adapter "1.7.1"] [ring/ring-jetty-adapter "1.7.1"]
[metosin/reitit "0.5.10"]] [metosin/reitit "0.5.18"]]
:repl-options {:init-ns example.server}) :repl-options {:init-ns example.server})

View file

@ -2,4 +2,4 @@
:description "Reitit coercion with vanilla ring" :description "Reitit coercion with vanilla ring"
:dependencies [[org.clojure/clojure "1.10.0"] :dependencies [[org.clojure/clojure "1.10.0"]
[ring/ring-jetty-adapter "1.7.1"] [ring/ring-jetty-adapter "1.7.1"]
[metosin/reitit "0.5.10"]]) [metosin/reitit "0.5.18"]])

View file

@ -0,0 +1,11 @@
/target
/classes
/checkouts
pom.xml
pom.xml.asc
*.jar
*.class
/.lein-*
/.nrepl-port
.hgignore
.hg/

View 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})

View 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)

View file

@ -3,6 +3,6 @@
:dependencies [[org.clojure/clojure "1.10.0"] :dependencies [[org.clojure/clojure "1.10.0"]
[io.pedestal/pedestal.service "0.5.5"] [io.pedestal/pedestal.service "0.5.5"]
[io.pedestal/pedestal.jetty "0.5.5"] [io.pedestal/pedestal.jetty "0.5.5"]
[metosin/reitit-pedestal "0.5.10"] [metosin/reitit-pedestal "0.5.18"]
[metosin/reitit "0.5.10"]] [metosin/reitit "0.5.18"]]
:repl-options {:init-ns example.server}) :repl-options {:init-ns example.server})

View file

@ -3,6 +3,6 @@
:dependencies [[org.clojure/clojure "1.10.0"] :dependencies [[org.clojure/clojure "1.10.0"]
[io.pedestal/pedestal.service "0.5.5"] [io.pedestal/pedestal.service "0.5.5"]
[io.pedestal/pedestal.jetty "0.5.5"] [io.pedestal/pedestal.jetty "0.5.5"]
[metosin/reitit-pedestal "0.5.10"] [metosin/reitit-pedestal "0.5.18"]
[metosin/reitit "0.5.10"]] [metosin/reitit "0.5.18"]]
:repl-options {:init-ns example.server}) :repl-options {:init-ns example.server})

View file

@ -2,5 +2,5 @@
:description "Reitit Ring App" :description "Reitit Ring App"
:dependencies [[org.clojure/clojure "1.10.0"] :dependencies [[org.clojure/clojure "1.10.0"]
[ring/ring-jetty-adapter "1.7.1"] [ring/ring-jetty-adapter "1.7.1"]
[metosin/reitit "0.5.10"]] [metosin/reitit "0.5.18"]]
:repl-options {:init-ns example.server}) :repl-options {:init-ns example.server})

View file

@ -2,7 +2,7 @@
:description "Reitit Ring App with Integrant" :description "Reitit Ring App with Integrant"
:dependencies [[org.clojure/clojure "1.10.1"] :dependencies [[org.clojure/clojure "1.10.1"]
[ring/ring-jetty-adapter "1.7.1"] [ring/ring-jetty-adapter "1.7.1"]
[metosin/reitit "0.5.10"] [metosin/reitit "0.5.18"]
[integrant "0.7.0"]] [integrant "0.7.0"]]
:main example.server :main example.server
:repl-options {:init-ns user} :repl-options {:init-ns user}

View file

@ -0,0 +1,11 @@
/target
/classes
/checkouts
pom.xml
pom.xml.asc
*.jar
*.class
/.lein-*
/.nrepl-port
.hgignore
.hg/

View 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

View 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"]]}})

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 KiB

View 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))

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

View file

@ -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}")))))

View file

@ -3,6 +3,6 @@
:dependencies [[org.clojure/clojure "1.10.0"] :dependencies [[org.clojure/clojure "1.10.0"]
[metosin/jsonista "0.2.6"] [metosin/jsonista "0.2.6"]
[ring/ring-jetty-adapter "1.7.1"] [ring/ring-jetty-adapter "1.7.1"]
[metosin/reitit "0.5.10"]] [metosin/reitit "0.5.18"]]
:repl-options {:init-ns example.server} :repl-options {:init-ns example.server}
:profiles {:dev {:dependencies [[ring/ring-mock "0.3.2"]]}}) :profiles {:dev {:dependencies [[ring/ring-mock "0.3.2"]]}})

View file

@ -56,13 +56,25 @@
["/plus" ["/plus"
{:get {:summary "plus with malli query parameters" {: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?]]}} :responses {200 {:body [:map [:total int?]]}}
:handler (fn [{{{:keys [x y]} :query} :parameters}] :handler (fn [{{{:keys [x y]} :query} :parameters}]
{:status 200 {:status 200
:body {:total (+ x y)}})} :body {:total (+ x y)}})}
:post {:summary "plus with malli body parameters" :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?]]}} :responses {200 {:body [:map [:total int?]]}}
:handler (fn [{{{:keys [x y]} :body} :parameters}] :handler (fn [{{{:keys [x y]} :body} :parameters}]
{:status 200 {:status 200

View file

@ -2,6 +2,6 @@
:description "Reitit Ring App with Swagger" :description "Reitit Ring App with Swagger"
:dependencies [[org.clojure/clojure "1.10.0"] :dependencies [[org.clojure/clojure "1.10.0"]
[ring/ring-jetty-adapter "1.7.1"] [ring/ring-jetty-adapter "1.7.1"]
[metosin/reitit "0.5.10"]] [metosin/reitit "0.5.18"]]
:repl-options {:init-ns example.server} :repl-options {:init-ns example.server}
:profiles {:dev {:dependencies [[ring/ring-mock "0.3.2"]]}}) :profiles {:dev {:dependencies [[ring/ring-mock "0.3.2"]]}})

View file

@ -13,6 +13,7 @@
; [reitit.ring.middleware.dev :as dev] ; [reitit.ring.middleware.dev :as dev]
; [reitit.ring.spec :as spec] ; [reitit.ring.spec :as spec]
; [spec-tools.spell :as spell] ; [spec-tools.spell :as spell]
[spec-tools.core :as st]
[ring.adapter.jetty :as jetty] [ring.adapter.jetty :as jetty]
[muuntaja.core :as m] [muuntaja.core :as m]
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
@ -25,7 +26,12 @@
(s/def ::size int?) (s/def ::size int?)
(s/def ::file-response (s/keys :req-un [::name ::size])) (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 ::y int?)
(s/def ::total int?) (s/def ::total int?)
(s/def ::math-request (s/keys :req-un [::x ::y])) (s/def ::math-request (s/keys :req-un [::x ::y]))

View file

@ -2,5 +2,5 @@
:description "Reitit Ring App with Swagger" :description "Reitit Ring App with Swagger"
:dependencies [[org.clojure/clojure "1.10.0"] :dependencies [[org.clojure/clojure "1.10.0"]
[ring/ring-jetty-adapter "1.7.1"] [ring/ring-jetty-adapter "1.7.1"]
[metosin/reitit "0.5.10"]] [metosin/reitit "0.5.18"]]
:repl-options {:init-ns example.server}) :repl-options {:init-ns example.server})

View file

@ -11,8 +11,6 @@
[reitit.ring.middleware.parameters :as parameters] [reitit.ring.middleware.parameters :as parameters]
;; Uncomment to use ;; Uncomment to use
; [reitit.ring.middleware.dev :as dev] ; [reitit.ring.middleware.dev :as dev]
; [reitit.ring.spec :as spec]
; [spec-tools.spell :as spell]
[ring.adapter.jetty :as jetty] [ring.adapter.jetty :as jetty]
[muuntaja.core :as m] [muuntaja.core :as m]
[clojure.java.io :as io])) [clojure.java.io :as io]))
@ -53,13 +51,15 @@
["/plus" ["/plus"
{:get {:summary "plus with spec query parameters" {:get {:summary "plus with spec query parameters"
:parameters {:query {:x int?, :y int?}} :parameters {:query {:x int?
:y int?}}
:responses {200 {:body {:total int?}}} :responses {200 {:body {:total int?}}}
:handler (fn [{{{:keys [x y]} :query} :parameters}] :handler (fn [{{{:keys [x y]} :query} :parameters}]
{:status 200 {:status 200
:body {:total (+ x y)}})} :body {:total (+ x y)}})}
:post {:summary "plus with spec body parameters" :post {:summary "plus with spec body parameters"
:parameters {:body {:x int?, :y int?}} :parameters {:body {:x int?
:y int?}}
:responses {200 {:body {:total int?}}} :responses {200 {:body {:total int?}}}
:handler (fn [{{{:keys [x y]} :body} :parameters}] :handler (fn [{{{:keys [x y]} :body} :parameters}]
{:status 200 {:status 200
@ -80,7 +80,8 @@
;; encoding response body ;; encoding response body
muuntaja/format-response-middleware muuntaja/format-response-middleware
;; exception handling ;; exception handling
exception/exception-middleware (exception/create-exception-middleware
{::exception/default (partial exception/wrap-log-to-console exception/default-handler)})
;; decoding request body ;; decoding request body
muuntaja/format-request-middleware muuntaja/format-request-middleware
;; coercing response bodys ;; coercing response bodys

View file

@ -0,0 +1 @@
../../../.clj-kondo/module_config.edn

View file

@ -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)" :description "Snappy data-driven router for Clojure(Script)"
:url "https://github.com/metosin/reitit" :url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License" :license {:name "Eclipse Public License"

View file

@ -41,36 +41,44 @@
:header (->ParameterCoercion :headers :string true true) :header (->ParameterCoercion :headers :string true true)
:path (->ParameterCoercion :path-params :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 (throw
(ex-info (ex-info
(if serialize-failed-result
(str "Request coercion failed: " (pr-str result)) (str "Request coercion failed: " (pr-str result))
(merge "Request coercion failed")
(into {} result) (-> {}
{:type ::request-coercion transient
:coercion coercion (as-> $ (reduce conj! $ result))
:value value (assoc! :type ::request-coercion)
:in [:request in] (assoc! :coercion coercion)
:request request})))) (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 (throw
(ex-info (ex-info
(if serialize-failed-result
(str "Response coercion failed: " (pr-str result)) (str "Response coercion failed: " (pr-str result))
(merge "Response coercion failed")
(into {} result) (-> {}
{:type ::response-coercion transient
:coercion coercion (as-> $ (reduce conj! $ result))
:value value (assoc! :type ::response-coercion)
:in [:response :body] (assoc! :coercion coercion)
:request request (assoc! :value value)
:response response})))) (assoc! :in [:response :body])
(assoc! :request request)
(assoc! :response response)
persistent!))))
(defn extract-request-format-default [request] (defn extract-request-format-default [request]
(-> request :muuntaja/request :format)) (-> request :muuntaja/request :format))
;; TODO: support faster key walking, walk/keywordize-keys is quite slow... ;; 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 :or {extract-request-format extract-request-format-default
parameter-coercion default-parameter-coercion}}] parameter-coercion default-parameter-coercion}}]
(if coercion (if coercion
@ -83,13 +91,13 @@
format (extract-request-format request) format (extract-request-format request)
result (coercer value format)] result (coercer value format)]
(if (error? result) (if (error? result)
(request-coercion-failed! result coercion value in request) (request-coercion-failed! result coercion value in request serialize-failed-result)
result)))))))) result))))))))
(defn extract-response-format-default [request _] (defn extract-response-format-default [request _]
(-> request :muuntaja/response :format)) (-> 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}}] :or {extract-response-format extract-response-format-default}}]
(if coercion (if coercion
(if-let [coercer (-response-coercer coercion body)] (if-let [coercer (-response-coercer coercion body)]
@ -98,7 +106,7 @@
value (:body response) value (:body response)
result (coercer value format)] result (coercer value format)]
(if (error? result) (if (error? result)
(response-coercion-failed! result coercion value request response) (response-coercion-failed! result coercion value request response serialize-failed-result)
result)))))) result))))))
(defn encode-error [data] (defn encode-error [data]
@ -109,9 +117,9 @@
(defn coerce-request [coercers request] (defn coerce-request [coercers request]
(reduce-kv (reduce-kv
(fn [acc k coercer] (fn [acc k coercer]
(impl/fast-assoc acc k (coercer request))) (impl/fast-assoc acc k (coercer request)))
{} coercers)) {} coercers))
(defn coerce-response [coercers request response] (defn coerce-response [coercers request response]
(if response (if response
@ -147,13 +155,13 @@
:multipart :formData}] :multipart :formData}]
(case specification (case specification
:swagger (->> (update :swagger (->> (update
data data
:parameters :parameters
(fn [parameters] (fn [parameters]
(->> parameters (->> parameters
(map (fn [[k v]] [(swagger-parameter k) v])) (map (fn [[k v]] [(swagger-parameter k) v]))
(filter first) (filter first)
(into {})))) (into {}))))
(-get-apidocs coercion specification))))) (-get-apidocs coercion specification)))))
;; ;;

View file

@ -1,6 +1,6 @@
(ns reitit.core (ns reitit.core
(:require [reitit.impl :as impl] (:require [reitit.exception :as exception]
[reitit.exception :as exception] [reitit.impl :as impl]
[reitit.trie :as trie])) [reitit.trie :as trie]))
;; ;;
@ -61,7 +61,7 @@
(if-not (partial-match? match) (if-not (partial-match? match)
match match
(impl/throw-on-missing-path-params (impl/throw-on-missing-path-params
(:template match) (:required match) path-params))))) (:template match) (:required match) path-params)))))
(defn match->path (defn match->path
([match] ([match]
@ -87,15 +87,15 @@
(let [compiler (::trie/trie-compiler opts (trie/compiler)) (let [compiler (::trie/trie-compiler opts (trie/compiler))
names (impl/find-names compiled-routes opts) names (impl/find-names compiled-routes opts)
[pl nl] (reduce [pl nl] (reduce
(fn [[pl nl] [p {:keys [name] :as data} result]] (fn [[pl nl] [p {:keys [name] :as data} result]]
(let [{:keys [path-params] :as route} (impl/parse p opts) (let [{:keys [path-params] :as route} (impl/parse p opts)
f #(if-let [path (impl/path-for route %)] f #(if-let [path (impl/path-for route %)]
(->Match p data result (impl/url-decode-coll %) path) (->Match p data result (impl/url-decode-coll %) path)
(->PartialMatch p data result (impl/url-decode-coll %) path-params))] (->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))) [(conj pl (-> (trie/insert nil p (->Match p data result nil nil) opts) (trie/compile)))
(if name (assoc nl name f) nl)])) (if name (assoc nl name f) nl)]))
[[] {}] [[] {}]
compiled-routes) compiled-routes)
lookup (impl/fast-map nl) lookup (impl/fast-map nl)
matcher (trie/linear-matcher compiler pl true) matcher (trie/linear-matcher compiler pl true)
match-by-path (trie/path-matcher matcher compiler) match-by-path (trie/path-matcher matcher compiler)
@ -103,16 +103,11 @@
^{:type ::router} ^{:type ::router}
(reify (reify
Router Router
(router-name [_] (router-name [_] :linear-router)
:linear-router) (routes [_] routes)
(routes [_] (compiled-routes [_] compiled-routes)
routes) (options [_] opts)
(compiled-routes [_] (route-names [_] names)
compiled-routes)
(options [_]
opts)
(route-names [_]
names)
(match-by-path [_ path] (match-by-path [_ path]
(if-let [match (match-by-path path)] (if-let [match (match-by-path path)]
(-> (:data match) (-> (:data match)
@ -133,33 +128,28 @@
([compiled-routes opts] ([compiled-routes opts]
(when-let [wilds (seq (filter (impl/->wild-route? opts) compiled-routes))] (when-let [wilds (seq (filter (impl/->wild-route? opts) compiled-routes))]
(exception/fail! (exception/fail!
(str "can't create :lookup-router with wildcard routes: " wilds) (str "can't create :lookup-router with wildcard routes: " wilds)
{:wilds wilds {:wilds wilds
:routes compiled-routes})) :routes compiled-routes}))
(let [names (impl/find-names compiled-routes opts) (let [names (impl/find-names compiled-routes opts)
[pl nl] (reduce [pl nl] (reduce
(fn [[pl nl] [p {:keys [name] :as data} result]] (fn [[pl nl] [p {:keys [name] :as data} result]]
[(assoc pl p (->Match p data result {} p)) [(assoc pl p (->Match p data result {} p))
(if name (if name
(assoc nl name #(->Match p data result % p)) (assoc nl name #(->Match p data result % p))
nl)]) nl)])
[{} {}] [{} {}]
compiled-routes) compiled-routes)
data (impl/fast-map pl) data (impl/fast-map pl)
lookup (impl/fast-map nl) lookup (impl/fast-map nl)
routes (impl/uncompile-routes compiled-routes)] routes (impl/uncompile-routes compiled-routes)]
^{:type ::router} ^{:type ::router}
(reify Router (reify Router
(router-name [_] (router-name [_] :lookup-router)
:lookup-router) (routes [_] routes)
(routes [_] (compiled-routes [_] compiled-routes)
routes) (options [_] opts)
(compiled-routes [_] (route-names [_] names)
compiled-routes)
(options [_]
opts)
(route-names [_]
names)
(match-by-path [_ path] (match-by-path [_ path]
(impl/fast-get data path)) (impl/fast-get data path))
(match-by-name [_ name] (match-by-name [_ name]
@ -183,34 +173,29 @@
(let [compiler (::trie/trie-compiler opts (trie/compiler)) (let [compiler (::trie/trie-compiler opts (trie/compiler))
names (impl/find-names compiled-routes opts) names (impl/find-names compiled-routes opts)
[pl nl] (reduce [pl nl] (reduce
(fn [[pl nl] [p {:keys [name] :as data} result]] (fn [[pl nl] [p {:keys [name] :as data} result]]
(let [{:keys [path-params] :as route} (impl/parse p opts) (let [{:keys [path-params] :as route} (impl/parse p opts)
f #(if-let [path (impl/path-for route %)] f #(if-let [path (impl/path-for route %)]
(->Match p data result (impl/url-decode-coll %) path) (->Match p data result (impl/url-decode-coll %) path)
(->PartialMatch p data result (impl/url-decode-coll %) path-params))] (->PartialMatch p data result (impl/url-decode-coll %) path-params))]
[(trie/insert pl p (->Match p data result nil nil) opts) [(trie/insert pl p (->Match p data result nil nil) opts)
(if name (assoc nl name f) nl)])) (if name (assoc nl name f) nl)]))
[nil {}] [nil {}]
compiled-routes) compiled-routes)
matcher (trie/compile pl compiler) 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) lookup (impl/fast-map nl)
routes (impl/uncompile-routes compiled-routes)] routes (impl/uncompile-routes compiled-routes)]
^{:type ::router} ^{:type ::router}
(reify (reify
Router Router
(router-name [_] (router-name [_] :trie-router)
:trie-router) (routes [_] routes)
(routes [_] (compiled-routes [_] compiled-routes)
routes) (options [_] opts)
(compiled-routes [_] (route-names [_] names)
compiled-routes)
(options [_]
opts)
(route-names [_]
names)
(match-by-path [_ path] (match-by-path [_ path]
(if-let [match (match-by-path path)] (if-let [match (and match-by-path (match-by-path path))]
(-> (:data match) (-> (:data match)
(assoc :path-params (:params match)) (assoc :path-params (:params match))
(assoc :path path)))) (assoc :path path))))
@ -229,8 +214,8 @@
([compiled-routes opts] ([compiled-routes opts]
(when (or (not= (count compiled-routes) 1) (some (impl/->wild-route? opts) compiled-routes)) (when (or (not= (count compiled-routes) 1) (some (impl/->wild-route? opts) compiled-routes))
(exception/fail! (exception/fail!
(str ":single-static-path-router requires exactly 1 static route: " compiled-routes) (str ":single-static-path-router requires exactly 1 static route: " compiled-routes)
{:routes compiled-routes})) {:routes compiled-routes}))
(let [[n :as names] (impl/find-names compiled-routes opts) (let [[n :as names] (impl/find-names compiled-routes opts)
[[p data result]] compiled-routes [[p data result]] compiled-routes
p #?(:clj (.intern ^String p) :cljs p) p #?(:clj (.intern ^String p) :cljs p)
@ -238,25 +223,17 @@
routes (impl/uncompile-routes compiled-routes)] routes (impl/uncompile-routes compiled-routes)]
^{:type ::router} ^{:type ::router}
(reify Router (reify Router
(router-name [_] (router-name [_] :single-static-path-router)
:single-static-path-router) (routes [_] routes)
(routes [_] (compiled-routes [_] compiled-routes)
routes) (options [_] opts)
(compiled-routes [_] (route-names [_] names)
compiled-routes)
(options [_]
opts)
(route-names [_]
names)
(match-by-path [_ path] (match-by-path [_ path]
(if (#?(:clj .equals :cljs =) p path) (if (#?(:clj .equals :cljs =) p path) match))
match))
(match-by-name [_ name] (match-by-name [_ name]
(if (= n name) (if (= n name) match))
match))
(match-by-name [_ name path-params] (match-by-name [_ name path-params]
(if (= n name) (if (= n name) (impl/fast-assoc match :path-params (impl/path-params path-params))))))))
(impl/fast-assoc match :path-params (impl/path-params path-params))))))))
(defn mixed-router (defn mixed-router
"Creates two routers: [[lookup-router]] or [[single-static-path-router]] for "Creates two routers: [[lookup-router]] or [[single-static-path-router]] for
@ -274,16 +251,11 @@
routes (impl/uncompile-routes compiled-routes)] routes (impl/uncompile-routes compiled-routes)]
^{:type ::router} ^{:type ::router}
(reify Router (reify Router
(router-name [_] (router-name [_] :mixed-router)
:mixed-router) (routes [_] routes)
(routes [_] (compiled-routes [_] compiled-routes)
routes) (options [_] opts)
(compiled-routes [_] (route-names [_] names)
compiled-routes)
(options [_]
opts)
(route-names [_]
names)
(match-by-path [_ path] (match-by-path [_ path]
(or (match-by-path static-router path) (or (match-by-path static-router path)
(match-by-path wildcard-router path))) (match-by-path wildcard-router path)))
@ -310,16 +282,11 @@
routes (impl/uncompile-routes compiled-routes)] routes (impl/uncompile-routes compiled-routes)]
^{:type ::router} ^{:type ::router}
(reify Router (reify Router
(router-name [_] (router-name [_] :quarantine-router)
:quarantine-router) (routes [_] routes)
(routes [_] (compiled-routes [_] compiled-routes)
routes) (options [_] opts)
(compiled-routes [_] (route-names [_] names)
compiled-routes)
(options [_]
opts)
(route-names [_]
names)
(match-by-path [_ path] (match-by-path [_ path]
(or (match-by-path mixed-router path) (or (match-by-path mixed-router path)
(match-by-path linear-router path))) (match-by-path linear-router path)))

View file

@ -10,8 +10,8 @@
(map (fn [provide] (map (fn [provide]
(when (contains? acc provide) (when (contains? acc provide)
(exception/fail! (exception/fail!
(str "multiple providers for: " provide) (str "multiple providers for: " provide)
{::multiple-providers provide})) {::multiple-providers provide}))
[provide dependent])) [provide dependent]))
(get-provides dependent))) (get-provides dependent)))
{} nodes)) {} nodes))
@ -22,8 +22,8 @@
(if (contains? providers k) (if (contains? providers k)
(get providers k) (get providers k)
(exception/fail! (exception/fail!
(str "provider missing for dependency: " k) (str "provider missing for dependency: " k)
{::missing-provider k}))) {::missing-provider k})))
(defn post-order (defn post-order
"Put `nodes` in post-order. Can also be described as a reverse topological sort. "Put `nodes` in post-order. Can also be described as a reverse topological sort.

View file

@ -31,20 +31,20 @@
path " " (not-empty (select-keys route-data [:conflicting]))))] path " " (not-empty (select-keys route-data [:conflicting]))))]
(apply str "Router contains conflicting route paths:\n\n" (apply str "Router contains conflicting route paths:\n\n"
(mapv (mapv
(fn [[[path route-data] vals]] (fn [[[path route-data] vals]]
(str (resolve-str path route-data) (str (resolve-str path route-data)
"\n" "\n"
(str/join "\n" (mapv (fn [[path route-data]] (str/join "\n" (mapv (fn [[path route-data]]
(resolve-str path route-data)) vals)) (resolve-str path route-data)) vals))
"\n\n")) "\n\n"))
conflicts)))) conflicts))))
(defmethod format-exception :name-conflicts [_ _ conflicts] (defmethod format-exception :name-conflicts [_ _ conflicts]
(apply str "Router contains conflicting route names:\n\n" (apply str "Router contains conflicting route names:\n\n"
(mapv (mapv
(fn [[name vals]] (fn [[name vals]]
(str name "\n-> " (str/join "\n-> " (mapv first vals)) "\n")) (str name "\n-> " (str/join "\n-> " (mapv first vals)) "\n"))
conflicts))) conflicts)))
(defmethod format-exception :reitit.impl/merge-data [_ _ data] (defmethod format-exception :reitit.impl/merge-data [_ _ data]
(str "Error merging route-data\n\n" (pr-str data))) (str "Error merging route-data\n\n" (pr-str data)))

View file

@ -1,13 +1,13 @@
(ns ^:no-doc reitit.impl (ns ^:no-doc reitit.impl
#?(:cljs (:require-macros [reitit.impl])) #?(:cljs (:require-macros [reitit.impl]))
(:require [clojure.string :as str] (:require [clojure.set :as set]
[clojure.set :as set] [clojure.string :as str]
[meta-merge.core :as mm] [meta-merge.core :as mm]
[reitit.trie :as trie] [reitit.exception :as ex]
[reitit.exception :as ex]) [reitit.trie :as trie])
#?(:clj #?(:clj
(:import (java.util HashMap Map) (:import (java.net URLEncoder URLDecoder)
(java.net URLEncoder URLDecoder)))) (java.util HashMap Map))))
(defn parse [path opts] (defn parse [path opts]
(let [path #?(:clj (.intern ^String (trie/normalize path opts)) :cljs (trie/normalize 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." Also works on vectors. Maintains key for maps, order for vectors."
[f coll] [f coll]
(reduce-kv (reduce-kv
(fn [coll k v] (fn [coll k v]
(if-some [v' (f v)] (if-some [v' (f v)]
(assoc coll k v') (assoc coll k v')
coll)) coll))
coll coll
coll)) coll))
(defn walk [raw-routes {:keys [path data routes expand] (defn walk [raw-routes {:keys [path data routes expand]
:or {data [], routes []} :or {data [], routes []}
:as opts}] :as opts}]
(letfn (letfn
[(walk-many [p m r] [(walk-many [p m r]
(reduce #(into %1 (walk-one p m %2)) [] r)) (reduce #(into %1 (walk-one p m %2)) [] r))
(walk-one [pacc macc routes] (walk-one [pacc macc routes]
(if (vector? (first routes)) (if (vector? (first routes))
(walk-many pacc macc routes) (walk-many pacc macc routes)
(when (string? (first routes)) (when (string? (first routes))
(let [[path & [maybe-arg :as args]] routes (let [[path & [maybe-arg :as args]] routes
[data childs] (if (or (vector? maybe-arg) [data childs] (if (or (vector? maybe-arg)
(and (sequential? maybe-arg) (and (sequential? maybe-arg)
(sequential? (first maybe-arg))) (sequential? (first maybe-arg)))
(nil? maybe-arg)) (nil? maybe-arg))
[{} args] [{} args]
[maybe-arg (rest args)]) [maybe-arg (rest args)])
macc (into macc (expand data opts)) macc (into macc (expand data opts))
child-routes (walk-many (str pacc path) macc (keep identity childs))] child-routes (walk-many (str pacc path) macc (keep identity childs))]
(if (seq childs) (seq child-routes) [[(str pacc path) macc]])))))] (if (seq childs) (seq child-routes) [[(str pacc path) macc]])))))]
(walk-one path (mapv identity data) raw-routes))) (walk-one path (mapv identity data) raw-routes)))
(defn map-data [f routes] (defn map-data [f routes]
(mapv (fn [[p ds]] [p (f p ds)]) 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 (reduce
(fn [acc [k v]] (fn [acc [k v]]
(try (try
(mm/meta-merge acc {k v}) ((or meta-merge-fn mm/meta-merge) acc {k v})
(catch #?(:clj Exception, :cljs js/Error) e (catch #?(:clj Exception, :cljs js/Error) e
(ex/fail! ::merge-data {:path p, :left acc, :right {k v}, :exception e})))) (ex/fail! ::merge-data {:path p, :left acc, :right {k v}, :exception e}))))
{} x)) {} x))
(defn resolve-routes [raw-routes {:keys [coerce] :as opts}] (defn resolve-routes [raw-routes {:keys [coerce] :as opts}]
(cond->> (->> (walk raw-routes opts) (map-data merge-data)) (cond->> (->> (walk raw-routes opts) (map-data #(merge-data opts %1 %2)))
coerce (into [] (keep #(coerce % opts))))) coerce (into [] (keep #(coerce % opts)))))
(defn path-conflicting-routes [routes opts] (defn path-conflicting-routes [routes opts]
(let [parts-and-routes (mapv (fn [[s :as r]] [(trie/split-path s opts) r]) routes)] (let [parts-and-routes (mapv (fn [[s :as r]] [(trie/split-path s opts) r]) routes)]
(-> (into {} (comp (map-indexed (fn [index [p r]] (-> (into {} (comp (map-indexed (fn [index [p r]]
[r (reduce [r (reduce
(fn [acc [p' r']] (fn [acc [p' r']]
(if (trie/conflicting-parts? p p') (if (trie/conflicting-parts? p p')
(conj acc r') acc)) (conj acc r') acc))
#{} (subvec parts-and-routes (inc index)))])) #{} (subvec parts-and-routes (inc index)))]))
(filter (comp seq second))) parts-and-routes) (filter (comp seq second))) parts-and-routes)
(not-empty)))) (not-empty))))
@ -123,13 +123,13 @@
(defn path-for [route path-params] (defn path-for [route path-params]
(if (:path-params route) (if (:path-params route)
(if-let [parts (reduce (if-let [parts (reduce
(fn [acc part] (fn [acc part]
(if (string? part) (if (string? part)
(conj acc part) (conj acc part)
(if-let [p (get path-params (:value part))] (if-let [p (get path-params (:value part))]
(conj acc p) (conj acc p)
(reduced nil)))) (reduced nil))))
[] (:path-parts route))] [] (:path-parts route))]
(apply str parts)) (apply str parts))
(:path route))) (:path route)))
@ -138,8 +138,8 @@
(let [defined (-> path-params keys set) (let [defined (-> path-params keys set)
missing (set/difference required defined)] missing (set/difference required defined)]
(ex/fail! (ex/fail!
(str "missing path-params for route " template " -> " missing) (str "missing path-params for route " template " -> " missing)
{:path-params path-params, :required required})))) {:path-params path-params, :required required}))))
(defn fast-assoc (defn fast-assoc
#?@(:clj [[^clojure.lang.Associative a k v] (.assoc a k v)] #?@(:clj [[^clojure.lang.Associative a k v] (.assoc a k v)]
@ -178,10 +178,10 @@
(if s (if s
#?(:clj (if (.contains ^String s "%") #?(:clj (if (.contains ^String s "%")
(URLDecoder/decode (URLDecoder/decode
(if (.contains ^String s "+") (if (.contains ^String s "+")
(.replace ^String s "+" "%2B") (.replace ^String s "+" "%2B")
^String s) ^String s)
"UTF-8")) "UTF-8"))
:cljs (js/decodeURIComponent s)))) :cljs (js/decodeURIComponent s))))
(defn url-decode [s] (defn url-decode [s]
@ -249,18 +249,10 @@
(->> params (->> params
(map (fn [[k v]] (map (fn [[k v]]
(if (or (sequential? v) (set? 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)))) (query-parameter k v))))
(str/join "&"))) (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)))

View file

@ -1,9 +1,9 @@
(ns reitit.interceptor (ns reitit.interceptor
(:require [meta-merge.core :refer [meta-merge]] (:require [clojure.pprint :as pprint]
[clojure.pprint :as pprint] [meta-merge.core :refer [meta-merge]]
[reitit.core :as r] [reitit.core :as r]
[reitit.impl :as impl] [reitit.exception :as exception]
[reitit.exception :as exception])) [reitit.impl :as impl]))
(defprotocol IntoInterceptor (defprotocol IntoInterceptor
(into-interceptor [this data opts])) (into-interceptor [this data opts]))
@ -37,36 +37,36 @@
(if-let [interceptor (if registry (registry this))] (if-let [interceptor (if registry (registry this))]
(into-interceptor interceptor data opts) (into-interceptor interceptor data opts)
(throw (throw
(ex-info (ex-info
(str (str
"Interceptor " this " not found in registry.\n\n" "Interceptor " this " not found in registry.\n\n"
(if (seq registry) (if (seq registry)
(str (str
"Available interceptors in registry:\n" "Available interceptors in registry:\n"
(with-out-str (with-out-str
(pprint/print-table [:id :description] (for [[k v] registry] {:id k :description v})))) (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") "see [reitit.interceptor/router] on how to add interceptor to the registry.\n") "\n")
{:id this {:id this
:registry registry})))) :registry registry}))))
#?(:clj clojure.lang.APersistentVector #?(:clj clojure.lang.APersistentVector
:cljs cljs.core.PersistentVector) :cljs cljs.core.PersistentVector)
(into-interceptor [[f & args :as form] data opts] (into-interceptor [[f & args :as form] data opts]
(when (and (seq args) (not (fn? f))) (when (and (seq args) (not (fn? f)))
(exception/fail! (exception/fail!
(str "Invalid Interceptor form: " form "") (str "Invalid Interceptor form: " form "")
{:form form})) {:form form}))
(into-interceptor (apply f args) data opts)) (into-interceptor (apply f args) data opts))
#?(:clj clojure.lang.Fn #?(:clj clojure.lang.Fn
:cljs function) :cljs function)
(into-interceptor [this data opts] (into-interceptor [this data opts]
(into-interceptor (into-interceptor
{:name ::handler {:name ::handler
::handler this ::handler this
:enter (fn [ctx] :enter (fn [ctx]
(assoc ctx :response (this (:request ctx))))} (assoc ctx :response (this (:request ctx))))}
data opts)) data opts))
#?(:clj clojure.lang.PersistentArrayMap #?(:clj clojure.lang.PersistentArrayMap
:cljs cljs.core.PersistentArrayMap) :cljs cljs.core.PersistentArrayMap)
@ -86,13 +86,13 @@
opts (assoc opts ::compiled (inc ^long compiled))] opts (assoc opts ::compiled (inc ^long compiled))]
(when (>= ^long compiled ^long *max-compile-depth*) (when (>= ^long compiled ^long *max-compile-depth*)
(exception/fail! (exception/fail!
(str "Too deep Interceptor compilation - " compiled) (str "Too deep Interceptor compilation - " compiled)
{:this this, :data data, :opts opts})) {:this this, :data data, :opts opts}))
(if-let [interceptor (into-interceptor (compile data opts) data opts)] (if-let [interceptor (into-interceptor (compile data opts) data opts)]
(map->Interceptor (map->Interceptor
(merge (merge
(dissoc this :compile) (dissoc this :compile)
(impl/strip-nils interceptor))))))) (impl/strip-nils interceptor)))))))
nil nil
(into-interceptor [_ _ _])) (into-interceptor [_ _ _]))
@ -122,9 +122,9 @@
([[_ {:keys [interceptors handler] :as data}] {::keys [queue] :as opts} _] ([[_ {:keys [interceptors handler] :as data}] {::keys [queue] :as opts} _]
(let [chain (chain (into (vec interceptors) [handler]) data opts)] (let [chain (chain (into (vec interceptors) [handler]) data opts)]
(map->Endpoint (map->Endpoint
{:interceptors chain {:interceptors chain
:queue ((or queue identity) chain) :queue ((or queue identity) chain)
:data data})))) :data data}))))
(defn transform-butlast (defn transform-butlast
"Returns a function to that takes a interceptor transformation function and "Returns a function to that takes a interceptor transformation function and
@ -132,8 +132,8 @@
[f] [f]
(fn [interceptors] (fn [interceptors]
(concat (concat
(f (butlast interceptors)) (f (butlast interceptors))
[(last interceptors)]))) [(last interceptors)])))
(defn router (defn router
"Creates a [[reitit.core/Router]] from raw route data and optionally an options map with "Creates a [[reitit.core/Router]] from raw route data and optionally an options map with
@ -155,8 +155,8 @@
:handler get-user}]])" :handler get-user}]])"
([data] ([data]
(router data nil)) (router data nil))
([data opts] ([data {:keys [meta-merge-fn] :as opts}]
(let [opts (meta-merge {:compile compile-result} opts)] (let [opts ((or meta-merge-fn meta-merge) {:compile compile-result} opts)]
(r/router data opts)))) (r/router data opts))))
(defn interceptor-handler [router] (defn interceptor-handler [router]

View file

@ -1,9 +1,9 @@
(ns reitit.middleware (ns reitit.middleware
(:require [meta-merge.core :refer [meta-merge]] (:require [clojure.pprint :as pprint]
[clojure.pprint :as pprint] [meta-merge.core :refer [meta-merge]]
[reitit.core :as r] [reitit.core :as r]
[reitit.impl :as impl] [reitit.exception :as exception]
[reitit.exception :as exception])) [reitit.impl :as impl]))
(defprotocol IntoMiddleware (defprotocol IntoMiddleware
(into-middleware [this data opts])) (into-middleware [this data opts]))
@ -21,17 +21,17 @@
(if-let [middleware (if registry (registry this))] (if-let [middleware (if registry (registry this))]
(into-middleware middleware data opts) (into-middleware middleware data opts)
(throw (throw
(ex-info (ex-info
(str (str
"Middleware " this " not found in registry.\n\n" "Middleware " this " not found in registry.\n\n"
(if (seq registry) (if (seq registry)
(str (str
"Available middleware in registry:\n" "Available middleware in registry:\n"
(with-out-str (with-out-str
(pprint/print-table [:id :description] (for [[k v] registry] {:id k :description v})))) (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") "see [reitit.middleware/router] on how to add middleware to the registry.\n") "\n")
{:id this {:id this
:registry registry})))) :registry registry}))))
#?(:clj clojure.lang.APersistentVector #?(:clj clojure.lang.APersistentVector
:cljs cljs.core.PersistentVector) :cljs cljs.core.PersistentVector)
@ -43,7 +43,7 @@
:cljs function) :cljs function)
(into-middleware [this _ _] (into-middleware [this _ _]
(map->Middleware (map->Middleware
{:wrap this})) {:wrap this}))
#?(:clj clojure.lang.PersistentArrayMap #?(:clj clojure.lang.PersistentArrayMap
:cljs cljs.core.PersistentArrayMap) :cljs cljs.core.PersistentArrayMap)
@ -63,13 +63,13 @@
opts (assoc opts ::compiled (inc ^long compiled))] opts (assoc opts ::compiled (inc ^long compiled))]
(when (>= ^long compiled ^long *max-compile-depth*) (when (>= ^long compiled ^long *max-compile-depth*)
(exception/fail! (exception/fail!
(str "Too deep Middleware compilation - " compiled) (str "Too deep Middleware compilation - " compiled)
{:this this, :data data, :opts opts})) {:this this, :data data, :opts opts}))
(if-let [middeware (into-middleware (compile data opts) data opts)] (if-let [middeware (into-middleware (compile data opts) data opts)]
(map->Middleware (map->Middleware
(merge (merge
(dissoc this :compile) (dissoc this :compile)
(impl/strip-nils middeware))))))) (impl/strip-nils middeware)))))))
nil nil
(into-middleware [_ _ _])) (into-middleware [_ _ _]))
@ -77,10 +77,10 @@
(defn- ensure-handler! [path data scope] (defn- ensure-handler! [path data scope]
(when-not (:handler data) (when-not (:handler data)
(exception/fail! (exception/fail!
(str "path \"" path "\" doesn't have a :handler defined" (str "path \"" path "\" doesn't have a :handler defined"
(if scope (str " for " scope))) (if scope (str " for " scope)))
(merge {:path path, :data data} (merge {:path path, :data data}
(if scope {:scope scope}))))) (if scope {:scope scope})))))
(defn- expand-and-transform (defn- expand-and-transform
[middleware data {::keys [transform] :or {transform identity} :as opts}] [middleware data {::keys [transform] :or {transform identity} :as opts}]
@ -116,9 +116,9 @@
(ensure-handler! path data scope) (ensure-handler! path data scope)
(let [middleware (expand-and-transform middleware data opts)] (let [middleware (expand-and-transform middleware data opts)]
(map->Endpoint (map->Endpoint
{:handler (compile-handler middleware handler) {:handler (compile-handler middleware handler)
:middleware middleware :middleware middleware
:data data})))) :data data}))))
(defn router (defn router
"Creates a [[reitit.core/Router]] from raw route data and optionally an options map with "Creates a [[reitit.core/Router]] from raw route data and optionally an options map with
@ -138,8 +138,8 @@
:handler get-user}]])" :handler get-user}]])"
([data] ([data]
(router data nil)) (router data nil))
([data opts] ([data {:keys [meta-merge-fn] :as opts}]
(let [opts (meta-merge {:compile compile-result} opts)] (let [opts ((or meta-merge-fn meta-merge) {:compile compile-result} opts)]
(r/router data opts)))) (r/router data opts))))
(defn middleware-handler [router] (defn middleware-handler [router]

View file

@ -1,8 +1,8 @@
(ns reitit.spec (ns reitit.spec
(:require [clojure.spec.alpha :as s] (:require [clojure.spec.alpha :as s]
[clojure.spec.gen.alpha :as gen] [clojure.spec.gen.alpha :as gen]
[reitit.exception :as exception] [reitit.core :as r]
[reitit.core :as r])) [reitit.exception :as exception]))
;; ;;
;; routes ;; routes
@ -16,9 +16,9 @@
(s/def ::raw-route (s/def ::raw-route
(s/nilable (s/nilable
(s/cat :path ::path (s/cat :path ::path
:arg (s/? ::arg) :arg (s/? ::arg)
:childs (s/* (s/and (s/nilable ::raw-routes)))))) :childs (s/* (s/and (s/nilable ::raw-routes))))))
(s/def ::raw-routes (s/def ::raw-routes
(s/or :route ::raw-route (s/or :route ::raw-route
@ -60,19 +60,19 @@
(s/def ::opts (s/def ::opts
(s/nilable (s/nilable
(s/keys :opt-un [:reitit.router/path (s/keys :opt-un [:reitit.router/path
:reitit.router/routes :reitit.router/routes
:reitit.router/data :reitit.router/data
:reitit.router/expand :reitit.router/expand
:reitit.router/coerce :reitit.router/coerce
:reitit.router/compile :reitit.router/compile
:reitit.router/conflicts :reitit.router/conflicts
:reitit.router/router]))) :reitit.router/router])))
(s/fdef r/router (s/fdef r/router
:args (s/or :1arity (s/cat :data (s/spec ::raw-routes)) :args (s/or :1arity (s/cat :data (s/spec ::raw-routes))
:2arity (s/cat :data (s/spec ::raw-routes), :opts ::opts)) :2arity (s/cat :data (s/spec ::raw-routes), :opts ::opts))
:ret ::router) :ret ::router)
;; ;;
;; coercion ;; coercion
@ -119,24 +119,25 @@
(defrecord Problem [path scope data spec problems]) (defrecord Problem [path scope data spec problems])
(defn validate-route-data [routes wrap spec] (defn validate-route-data [routes wrap spec]
(let [spec (wrap spec)] (let [spec (wrap spec)
(some->> (for [[p d _] routes] spec-explain (fn [[p d _]]
(when-let [problems (and spec (s/explain-data spec d))] (when-let [problems (and spec (s/explain-data spec d))]
(->Problem p nil d spec problems))) (->Problem p nil d spec problems)))
(keep identity) (seq) (vec)))) errors (into [] (keep spec-explain) routes)]
(when (pos? (count errors)) errors)))
(defn validate [routes {:keys [spec] ::keys [wrap] :or {spec ::default-data, wrap identity}}] (defn validate [routes {:keys [spec] ::keys [wrap] :or {spec ::default-data, wrap identity}}]
(when-let [problems (validate-route-data routes wrap spec)] (when-let [problems (validate-route-data routes wrap spec)]
(exception/fail! (exception/fail!
::invalid-route-data ::invalid-route-data
{:problems problems}))) {:problems problems})))
(defmethod exception/format-exception :reitit.spec/invalid-route-data [_ _ {:keys [problems]}] (defmethod exception/format-exception :reitit.spec/invalid-route-data [_ _ {:keys [problems]}]
(apply str "Invalid route data:\n\n" (apply str "Invalid route data:\n\n"
(mapv (mapv
(fn [{:keys [path scope data spec]}] (fn [{:keys [path scope data spec]}]
(str "-- On route -----------------------\n\n" (str "-- On route -----------------------\n\n"
(pr-str path) (if scope (str " " (pr-str scope))) "\n\n" (pr-str path) (if scope (str " " (pr-str scope))) "\n\n"
(pr-str data) "\n\n" (pr-str data) "\n\n"
(s/explain-str spec data) "\n")) (s/explain-str spec data) "\n"))
problems))) problems)))

View file

@ -2,8 +2,8 @@
(:refer-clojure :exclude [compile]) (:refer-clojure :exclude [compile])
(:require [clojure.string :as str] (:require [clojure.string :as str]
[reitit.exception :as ex]) [reitit.exception :as ex])
#?(:clj (:import [reitit Trie Trie$Match Trie$Matcher] #?(:clj (:import (java.net URLDecoder)
(java.net URLDecoder)))) [reitit Trie Trie$Match Trie$Matcher])))
(defn ^:no-doc into-set [x] (defn ^:no-doc into-set [x]
(cond (cond
@ -90,12 +90,12 @@
(defn join-path [xs] (defn join-path [xs]
(reduce (reduce
(fn [s x] (fn [s x]
(str s (cond (str s (cond
(string? x) x (string? x) x
(instance? Wild x) (str "{" (-> x :value str (subs 1)) "}") (instance? Wild x) (str "{" (-> x :value str (subs 1)) "}")
(instance? CatchAll x) (str "{*" (-> x :value str (subs 1)) "}")))) (instance? CatchAll x) (str "{*" (-> x :value str (subs 1)) "}"))))
"" xs)) "" xs))
(defn normalize [s opts] (defn normalize [s opts]
(-> s (split-path opts) (join-path))) (-> s (split-path opts) (join-path)))
@ -167,30 +167,30 @@
(instance? CatchAll path) (instance? CatchAll path)
(assoc-in node [:catch-all path] (-node {:params params, :data data})) (assoc-in node [:catch-all path] (-node {:params params, :data data}))
(str/blank? path) (empty? path)
(-insert node ps fp params data) (-insert node ps fp params data)
:else :else
(or (or
(reduce (reduce
(fn [_ [p n]] (fn [_ [p n]]
(if-let [cp (common-prefix p path)] (if-let [cp (common-prefix p path)]
(if (= cp p) (if (= cp p)
;; insert into child node ;; insert into child node
(let [n' (-insert n (conj ps (subs path (count p))) fp params data)] (let [n' (-insert n (conj ps (subs path (count p))) fp params data)]
(reduced (assoc-in node [:children p] n'))) (reduced (assoc-in node [:children p] n')))
;; split child node ;; split child node
(let [rp (subs p (count cp)) (let [rp (subs p (count cp))
rp' (subs path (count cp)) rp' (subs path (count cp))
n' (-insert (-node {}) ps fp params data) n' (-insert (-node {}) ps fp params data)
n'' (-insert (-node {:children {rp n, rp' n'}}) nil nil nil nil)] n'' (-insert (-node {:children {rp n, rp' n'}}) nil nil nil nil)]
(reduced (update node :children (fn [children] (reduced (update node :children (fn [children]
(-> children (-> children
(dissoc p) (dissoc p)
(assoc cp n''))))))))) (assoc cp n'')))))))))
nil (:children node)) nil (:children node))
;; new child 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 ""])] (if-let [child (get-in node' [:children ""])]
;; optimize by removing empty paths ;; optimize by removing empty paths
(-> (merge-with merge (dissoc node' :data) child) (-> (merge-with merge (dissoc node' :data) child)
@ -202,10 +202,10 @@
(if percent? (if percent?
#?(:cljs (js/decodeURIComponent param) #?(:cljs (js/decodeURIComponent param)
:clj (URLDecoder/decode :clj (URLDecoder/decode
(if (.contains ^String param "+") (if (.contains ^String param "+")
(.replace ^String param "+" "%2B") (.replace ^String param "+" "%2B")
param) param)
"UTF-8")) "UTF-8"))
param))) param)))
;; ;;
@ -313,13 +313,13 @@
(def record-parameters (def record-parameters
"Memoized function to transform parameters into runtime generated Record." "Memoized function to transform parameters into runtime generated Record."
(memoize (memoize
(fn [keys] (fn [keys]
(if (some qualified-keyword? keys) (if (some qualified-keyword? keys)
(map-parameters keys) (map-parameters keys)
(let [sym (gensym "PathParams") (let [sym (gensym "PathParams")
ctor (symbol (str "map->" sym))] ctor (symbol (str "map->" sym))]
(binding [*ns* (find-ns 'user)] (binding [*ns* (find-ns 'user)]
(eval `(do (defrecord ~sym ~(mapv (comp symbol name) keys)) (~ctor {})))))))))) (eval `(do (defrecord ~sym ~(mapv (comp symbol name) keys)) (~ctor {}))))))))))
(defn insert (defn insert
"Returns a trie with routes added to it." "Returns a trie with routes added to it."
@ -327,9 +327,9 @@
(insert nil routes)) (insert nil routes))
([node routes] ([node routes]
(reduce (reduce
(fn [acc [p d]] (fn [acc [p d]]
(insert acc p d)) (insert acc p d))
node routes)) node routes))
([node path data] ([node path data]
(insert node path data nil)) (insert node path data nil))
([node path data {::keys [parameters] :or {parameters map-parameters} :as opts}] ([node path data {::keys [parameters] :or {parameters map-parameters} :as opts}]
@ -355,17 +355,16 @@
(cond-> data (conj (data-matcher compiler params data))) (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] children] (static-matcher compiler p (compile c compiler (conj cp p)))))
(into (into
(for [[p c] wilds] (for [[p c] wilds]
(let [pv (:value p) (let [pv (:value p)
ends (ends c)] ends (ends c)]
(if (next ends) (if (next ends)
(ex/fail! ::multiple-terminators {:terminators ends, :path (join-path (conj cp p))}) (ex/fail! ::multiple-terminators {:terminators ends, :path (join-path (conj cp p))})
(wild-matcher compiler pv (ffirst ends) (compile c compiler (conj cp pv))))))) (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)))))] (into (for [[p c] catch-all] (catch-all-matcher compiler (:value p) params (:data c)))))]
(cond (cond
(> (count matchers) 1) (linear-matcher compiler matchers false) (> (count matchers) 1) (linear-matcher compiler matchers false)
(= (count matchers) 1) (first matchers) (= (count matchers) 1) (first matchers)))))
:else (data-matcher compiler {} nil)))))
(defn pretty (defn pretty
"Returns a simplified EDN structure of a compiled trie for printing purposes." "Returns a simplified EDN structure of a compiled trie for printing purposes."
@ -387,59 +386,61 @@
(comment (comment
(-> (->
[["/v2/whoami" 1] [["/v2/whoami" 1]
["/v2/users/:user-id/datasets" 2] ["/v2/users/:user-id/datasets" 2]
["/v2/public/projects/:project-id/datasets" 3] ["/v2/public/projects/:project-id/datasets" 3]
["/v1/public/topics/:topic" 4] ["/v1/public/topics/:topic" 4]
["/v1/users/:user-id/orgs/:org-id" 5] ["/v1/users/:user-id/orgs/:org-id" 5]
["/v1/search/topics/:term" 6] ["/v1/search/topics/:term" 6]
["/v1/users/:user-id/invitations" 7] ["/v1/users/:user-id/invitations" 7]
["/v1/users/:user-id/topics" 9] ["/v1/users/:user-id/topics" 9]
["/v1/users/:user-id/bookmarks/followers" 10] ["/v1/users/:user-id/bookmarks/followers" 10]
["/v2/datasets/:dataset-id" 11] ["/v2/datasets/:dataset-id" 11]
["/v1/orgs/:org-id/usage-stats" 12] ["/v1/orgs/:org-id/usage-stats" 12]
["/v1/orgs/:org-id/devices/:client-id" 13] ["/v1/orgs/:org-id/devices/:client-id" 13]
["/v1/messages/user/:user-id" 14] ["/v1/messages/user/:user-id" 14]
["/v1/users/:user-id/devices" 15] ["/v1/users/:user-id/devices" 15]
["/v1/public/users/:user-id" 16] ["/v1/public/users/:user-id" 16]
["/v1/orgs/:org-id/errors" 17] ["/v1/orgs/:org-id/errors" 17]
["/v1/public/orgs/:org-id" 18] ["/v1/public/orgs/:org-id" 18]
["/v1/orgs/:org-id/invitations" 19] ["/v1/orgs/:org-id/invitations" 19]
["/v1/users/:user-id/device-errors" 22] ["/v1/users/:user-id/device-errors" 22]
["/v2/login" 23] ["/v2/login" 23]
["/v1/users/:user-id/usage-stats" 24] ["/v1/users/:user-id/usage-stats" 24]
["/v2/users/:user-id/devices" 25] ["/v2/users/:user-id/devices" 25]
["/v1/users/:user-id/claim-device/:client-id" 26] ["/v1/users/:user-id/claim-device/:client-id" 26]
["/v2/public/projects/:project-id" 27] ["/v2/public/projects/:project-id" 27]
["/v2/public/datasets/:dataset-id" 28] ["/v2/public/datasets/:dataset-id" 28]
["/v2/users/:user-id/topics/bulk" 29] ["/v2/users/:user-id/topics/bulk" 29]
["/v1/messages/device/:client-id" 30] ["/v1/messages/device/:client-id" 30]
["/v1/users/:user-id/owned-orgs" 31] ["/v1/users/:user-id/owned-orgs" 31]
["/v1/topics/:topic" 32] ["/v1/topics/:topic" 32]
["/v1/users/:user-id/bookmark/:topic" 33] ["/v1/users/:user-id/bookmark/:topic" 33]
["/v1/orgs/:org-id/members/:user-id" 34] ["/v1/orgs/:org-id/members/:user-id" 34]
["/v1/users/:user-id/devices/:client-id" 35] ["/v1/users/:user-id/devices/:client-id" 35]
["/v1/users/:user-id" 36] ["/v1/users/:user-id" 36]
["/v1/orgs/:org-id/devices" 37] ["/v1/orgs/:org-id/devices" 37]
["/v1/orgs/:org-id/members" 38] ["/v1/orgs/:org-id/members" 38]
["/v2/orgs/:org-id/topics" 40] ["/v2/orgs/:org-id/topics" 40]
["/v1/whoami" 41] ["/v1/whoami" 41]
["/v1/orgs/:org-id" 42] ["/v1/orgs/:org-id" 42]
["/v1/users/:user-id/api-key" 43] ["/v1/users/:user-id/api-key" 43]
["/v2/schemas" 44] ["/v2/schemas" 44]
["/v2/users/:user-id/topics" 45] ["/v2/users/:user-id/topics" 45]
["/v1/orgs/:org-id/confirm-membership/:token" 46] ["/v1/orgs/:org-id/confirm-membership/:token" 46]
["/v2/topics/:topic" 47] ["/v2/topics/:topic" 47]
["/v1/messages/topic/:topic" 48] ["/v1/messages/topic/:topic" 48]
["/v1/users/:user-id/devices/:client-id/reset-password" 49] ["/v1/users/:user-id/devices/:client-id/reset-password" 49]
["/v2/topics" 50] ["/v2/topics" 50]
["/v1/login" 51] ["/v1/login" 51]
["/v1/users/:user-id/orgs" 52] ["/v1/users/:user-id/orgs" 52]
["/v2/public/messages/dataset/:dataset-id" 53] ["/v2/public/messages/dataset/:dataset-id" 53]
["/v1/topics" 54] ["/v1/topics" 54]
["/v1/orgs" 55] ["/v1/orgs" 55]
["/v1/users/:user-id/bookmarks" 56] ["/v1/users/:user-id/bookmarks" 56]
["/v1/orgs/:org-id/topics" 57]] ["/v1/orgs/:org-id/topics" 57]
(insert) ["/command1 {arg1} {arg2}" ::cmd1]
(compile) ["/command2 {arg1} {arg2} {arg3}" ::cmd2]]
(pretty))) (insert)
(compile)
(pretty)))

View file

@ -0,0 +1 @@
../../../.clj-kondo/module_config.edn

View file

@ -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)" :description "Snappy data-driven router for Clojure(Script)"
:url "https://github.com/metosin/reitit" :url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License" :license {:name "Eclipse Public License"

View file

@ -1,18 +1,16 @@
(ns reitit.dev.pretty (ns reitit.dev.pretty
(:require [clojure.string :as str] (:require [arrangement.core] ;; spell-spec
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
[reitit.exception :as exception] [clojure.string :as str]
[arrangement.core] [expound.alpha] ;; fipp
;; spell-spec
[spell-spec.expound]
;; expound
[expound.ansi] [expound.ansi]
[expound.alpha]
;; fipp
[fipp.visit]
[fipp.edn] [fipp.edn]
[fipp.ednize] [fipp.ednize]
[fipp.engine])) [fipp.engine]
[fipp.visit]
[reitit.exception :as exception]
[spell-spec.expound] ;; expound
))
;; ;;
;; colors ;; colors
@ -152,13 +150,13 @@
(printer nil)) (printer nil))
([options] ([options]
(map->EdnPrinter (map->EdnPrinter
(merge (merge
{:width 80 {:width 80
:symbols {} :symbols {}
:print-length *print-length* :print-length *print-length*
:print-level *print-level* :print-level *print-level*
:print-meta *print-meta*} :print-meta *print-meta*}
options)))) options))))
(defn pprint (defn pprint
([x] (pprint x {})) ([x] (pprint x {}))
@ -209,13 +207,13 @@
(defn exception-str [message source printer] (defn exception-str [message source printer]
(with-out-str (with-out-str
(print-doc (print-doc
[:group [:group
(title "Router creation failed" source printer) (title "Router creation failed" source printer)
[:break] [:break] [:break] [:break]
message message
[:break] [:break]
(footer printer)] (footer printer)]
printer))) printer)))
(defmulti format-exception (fn [type _ _] type)) (defmulti format-exception (fn [type _ _] type))
@ -231,11 +229,11 @@
(defn de-expound-colors [^String s mappings] (defn de-expound-colors [^String s mappings]
(let [s' (reduce (let [s' (reduce
(fn [s [from to]] (fn [s [from to]]
(.replace ^String s (.replace ^String s
^String (expound.ansi/esc [from]) ^String (expound.ansi/esc [from])
^String (-start (colors to)))) ^String (-start (colors to))))
s mappings)] s mappings)]
(.replace ^String s' (.replace ^String s'
^String (expound.ansi/esc [:none]) ^String (expound.ansi/esc [:none])
(str (expound.ansi/esc [:none]) (-start (colors :text)))))) (str (expound.ansi/esc [:none]) (-start (colors :text))))))
@ -254,9 +252,9 @@
(def expound-printer (def expound-printer
(expound.alpha/custom-printer (expound.alpha/custom-printer
{:theme :figwheel-theme {:theme :figwheel-theme
:show-valid-values? false :show-valid-values? false
:print-specs? false})) :print-specs? false}))
;; ;;
;; Formatters ;; Formatters
@ -276,18 +274,18 @@
" ") " ")
(edn (not-empty (select-keys route-data [:conflicting])))])] (edn (not-empty (select-keys route-data [:conflicting])))])]
(into (into
[:group] [:group]
(mapv (mapv
(fn [[[path route-data] vals]] (fn [[[path route-data] vals]]
[:group [:group
(path-report path route-data) (path-report path route-data)
(into (into
[:group] [:group]
(map (map
(fn [[path route-data]] (path-report path route-data)) (fn [[path route-data]] (path-report path route-data))
vals)) vals))
[:break]]) [:break]])
conflicts))) conflicts)))
[:span (text "Either fix the conflicting paths or disable the conflict resolution") [:span (text "Either fix the conflicting paths or disable the conflict resolution")
[:break] (text "by setting route data for conflicting route: ") [:break] [:break] [:break] (text "by setting route data for conflicting route: ") [:break] [:break]
(edn {:conflicting true} {:margin 3}) (edn {:conflicting true} {:margin 3})
@ -302,19 +300,19 @@
(text "Router contains conflicting route names:") (text "Router contains conflicting route names:")
[:break] [:break] [:break] [:break]
(into (into
[:group] [:group]
(mapv (mapv
(fn [[name vals]] (fn [[name vals]]
[:group [:group
[:span (text name)] [:span (text name)]
[:break] [:break]
(into (into
[:group] [:group]
(map (map
(fn [p] [:span (color :grey "-> " p) [:break]]) (fn [p] [:span (color :grey "-> " p) [:break]])
(mapv first vals))) (mapv first vals)))
[:break]]) [:break]])
conflicts)) conflicts))
(color :white "https://cljdoc.org/d/metosin/reitit/CURRENT/doc/basics/route-conflicts") (color :white "https://cljdoc.org/d/metosin/reitit/CURRENT/doc/basics/route-conflicts")
[:break]]) [:break]])
@ -323,22 +321,22 @@
(text "Invalid route data:") (text "Invalid route data:")
[:break] [:break] [:break] [:break]
(into (into
[:group] [:group]
(map (map
(fn [{:keys [data path spec scope]}] (fn [{:keys [data path spec scope]}]
[:group [:group
[:span (color :grey "-- On route -----------------------")] [:span (color :grey "-- On route -----------------------")]
[:break] [:break]
[:break] [:break]
(text path) (if scope [:span " " (text scope)]) (text path) (if scope [:span " " (text scope)])
[:break] [:break]
[:break] [:break]
(-> (s/explain-data spec data) (-> (s/explain-data spec data)
(expound-printer) (expound-printer)
(with-out-str) (with-out-str)
(fippify)) (fippify))
[:break]]) [:break]])
problems)) problems))
(color :white "https://cljdoc.org/d/metosin/reitit/CURRENT/doc/basics/route-data-validation") (color :white "https://cljdoc.org/d/metosin/reitit/CURRENT/doc/basics/route-data-validation")
[:break]]) [:break]])

View file

@ -0,0 +1 @@
../../../.clj-kondo/module_config.edn

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-frontend "0.5.10" (defproject metosin/reitit-frontend "0.5.18"
:description "Reitit: Clojurescript frontend routing core" :description "Reitit: Clojurescript frontend routing core"
:url "https://github.com/metosin/reitit" :url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License" :license {:name "Eclipse Public License"

View file

@ -22,18 +22,28 @@
(defn match-by-path (defn match-by-path
"Given routing tree and current path, return match with possibly "Given routing tree and current path, return match with possibly
coerced parameters. Return nil if no match found." coerced parameters. Return nil if no match found.
[router path]
(let [uri (.parse Uri path)] :on-coercion-error - a sideeffecting fn of `match exception -> nil`"
(if-let [match (r/match-by-path router (.getPath uri))] ([router path] (match-by-path router path nil))
(let [q (query-params uri) ([router path {:keys [on-coercion-error]}]
match (assoc match :query-params q) (let [uri (.parse Uri path)
;; Return uncoerced values if coercion is not enabled - so coerce! (if on-coercion-error
;; that tha parameters are always accessible from same property. (fn [match]
parameters (or (coercion/coerce! match) (try (coercion/coerce! match)
{:path (:path-params match) (catch js/Error e
:query q})] (on-coercion-error match e)
(assoc match :parameters parameters))))) (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 (defn match-by-name
"Given a router, route name and optionally path-parameters, "Given a router, route name and optionally path-parameters,
@ -64,11 +74,11 @@
(let [defined (-> path-params keys set) (let [defined (-> path-params keys set)
missing (set/difference (:required match) defined)] missing (set/difference (:required match) defined)]
(js/console.warn (js/console.warn
"missing path-params for route" name "missing path-params for route" name
{:template (:template match) {:template (:template match)
:missing missing :missing missing
:path-params path-params :path-params path-params
:required (:required match)}) :required (:required match)})
nil)) nil))
match) match)
(do (js/console.warn "missing route" name) (do (js/console.warn "missing route" name)

View file

@ -10,7 +10,8 @@
;; Differences: ;; Differences:
;; This one automatically removes previous event listeners. ;; 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. "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 Automatically removes previous event listeners so it is safe to call this repeatedly, for example when using
@ -37,30 +38,66 @@
(when (nil? @history) (when (nil? @history)
(reset! history this)) (reset! history this))
(on-navigate m this)) (on-navigate m this))
opts)) opts))
(defn href (defn
([k] ^{:see-also ["reitit.frontend.history/href"]}
(rfh/href @history k nil nil)) href
([k params] "Generate a URL for a route defined by name, with given path-params and query-params.
(rfh/href @history k params nil))
([k params query]
(rfh/href @history k params query)))
(defn push-state The URL is formatted using Reitit frontend history handler, so using it with
"Sets the new route, leaving previous route in history." anchor element href will correctly trigger route change event.
([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)))
(defn replace-state Note: currently collections in query-parameters are encoded as field-value
"Replaces current route. I.e. current route is not left on history." pairs separated by &, i.e. \"?a=1&a=2\", if you want to encode them
([k] differently, convert the collections to strings first."
(rfh/replace-state @history k nil nil)) ([name]
([k params] (rfh/href @history name nil nil))
(rfh/replace-state @history k params nil)) ([name path-params]
([k params query] (rfh/href @history name path-params nil))
(rfh/replace-state @history k params query))) ([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)))

View file

@ -1,9 +1,9 @@
(ns reitit.frontend.history (ns reitit.frontend.history
"Provides integration to hash-change or HTML5 History "Provides integration to hash-change or HTML5 History
events." events."
(:require [reitit.core :as reitit] (:require [goog.events :as gevents]
[reitit.frontend :as rf] [reitit.core :as reitit]
[goog.events :as gevents]) [reitit.frontend :as rf])
(:import goog.Uri)) (:import goog.Uri))
(defprotocol History (defprotocol History
@ -40,7 +40,7 @@
nil) nil)
(-on-navigate [this path] (-on-navigate [this path]
(reset! last-fragment 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] (-get-path [this]
;; Remove # ;; Remove #
;; "" or "#" should be same as "#/" ;; "" or "#" should be same as "#/"
@ -125,7 +125,7 @@
(-on-navigate this (-get-path this)) (-on-navigate this (-get-path this))
this)) this))
(-on-navigate [this path] (-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] (-stop [this]
(gevents/unlistenByKey listen-key) (gevents/unlistenByKey listen-key)
(gevents/unlistenByKey click-listen-key) (gevents/unlistenByKey click-listen-key)
@ -171,40 +171,73 @@
(map->FragmentHistory opts) (map->FragmentHistory opts)
(map->Html5History opts)))))) (map->Html5History opts))))))
(defn stop! [history] (defn stop!
"Stops the given history handler, removing the event handlers."
[history]
(if history (if history
(-stop history))) (-stop history)))
(defn href (defn href
([history k] "Generate a URL for a route defined by name, with given path-params and query-params.
(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)))))
(defn push-state The URL is formatted using Reitit frontend history handler, so using it with
"Sets the new route, leaving previous route in history." anchor element href will correctly trigger route change event.
([history k]
(push-state history k nil nil)) Note: currently collections in query parameters are encoded as field-value
([history k params] pairs separated by &, i.e. \"?a=1&a=2\", if you want to encode them
(push-state history k params nil)) differently, convert the collections to strings first."
([history k params query] ([history name]
(let [match (rf/match-by-name! (:router history) k params) (href history name nil))
path (reitit/match->path match query)] ([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 and replaceState don't trigger popstate event so call on-navigate manually
(.pushState js/window.history nil "" (-href history path)) (.pushState js/window.history nil "" (-href history path))
(-on-navigate history path)))) (-on-navigate history path))))
(defn replace-state (defn replace-state
"Replaces current route. I.e. current route is not left on history." "Updates the browser location and replaces latest entry in the history stack
([history k] using URL built from a route defined by name, with given path-params and
(replace-state history k nil nil)) query-params.
([history k params]
(replace-state history k params nil)) Will also trigger on-navigate callback on Reitit frontend History handler.
([history k params query]
(let [match (rf/match-by-name! (:router history) k params) Note: currently collections in query-parameters are encoded as field-value
path (reitit/match->path match query)] 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)) (.replaceState js/window.history nil "" (-href history path))
(-on-navigate history path)))) (-on-navigate history path))))

View file

@ -0,0 +1 @@
../../../.clj-kondo/module_config.edn

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-http "0.5.10" (defproject metosin/reitit-http "0.5.18"
:description "Reitit: HTTP routing with interceptors" :description "Reitit: HTTP routing with interceptors"
:url "https://github.com/metosin/reitit" :url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License" :license {:name "Eclipse Public License"

View file

@ -1,24 +1,24 @@
(ns reitit.http (ns reitit.http
(:require [meta-merge.core :refer [meta-merge]] (:require [meta-merge.core :refer [meta-merge]]
[reitit.interceptor :as interceptor] [reitit.core :as r]
[reitit.exception :as ex] [reitit.exception :as ex]
[reitit.ring :as ring] [reitit.interceptor :as interceptor]
[reitit.core :as r])) [reitit.ring :as ring]))
(defrecord Endpoint [data interceptors queue handler path method]) (defrecord Endpoint [data interceptors queue handler path method])
(defn coerce-handler [[path data] {:keys [expand] :as opts}] (defn coerce-handler [[path data] {:keys [expand] :as opts}]
[path (reduce [path (reduce
(fn [acc method] (fn [acc method]
(if (contains? acc method) (if (contains? acc method)
(update acc method expand opts) (update acc method expand opts)
acc)) data ring/http-methods)]) 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) (let [[top childs] (ring/group-keys data)
childs (cond-> childs childs (cond-> childs
(and (not (:options childs)) (not (:handler top)) default-options-endpoint) (and (not (:options childs)) (not (:handler top)) default-options-endpoint)
(assoc :options (expand default-options-endpoint opts))) (assoc :options (expand default-options-endpoint opts)))
compile (fn [[path data] opts scope] compile (fn [[path data] opts scope]
(interceptor/compile-result [path data] opts scope)) (interceptor/compile-result [path data] opts scope))
->endpoint (fn [p d m s] ->endpoint (fn [p d m s]
@ -29,19 +29,19 @@
(assoc :method m)))) (assoc :method m))))
->methods (fn [any? data] ->methods (fn [any? data]
(reduce (reduce
(fn [acc method] (fn [acc method]
(cond-> acc (cond-> acc
any? (assoc method (->endpoint path data method nil)))) any? (assoc method (->endpoint path data method nil))))
(ring/map->Methods {}) (ring/map->Methods {})
ring/http-methods))] ring/http-methods))]
(if-not (seq childs) (if-not (seq childs)
(->methods true top) (->methods true top)
(reduce-kv (reduce-kv
(fn [acc method data] (fn [acc method data]
(let [data (meta-merge top data)] (let [data ((or meta-merge-fn meta-merge) top data)]
(assoc acc method (->endpoint path data method method)))) (assoc acc method (->endpoint path data method method))))
(->methods (:handler top) data) (->methods (:handler top) data)
childs)))) childs))))
(defn router (defn router
"Creates a [[reitit.core/Router]] from raw route data and optionally an options map with "Creates a [[reitit.core/Router]] from raw route data and optionally an options map with
@ -89,7 +89,8 @@
default-interceptors (->> interceptors default-interceptors (->> interceptors
(map #(interceptor/into-interceptor % nil (r/options router)))) (map #(interceptor/into-interceptor % nil (r/options router))))
default-queue (interceptor/queue executor default-interceptors) 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 {:name ::router
:enter (fn [{:keys [request] :as context}] :enter (fn [{:keys [request] :as context}]
(if-let [match (r/match-by-path router (:uri request))] (if-let [match (r/match-by-path router (:uri request))]
@ -101,7 +102,9 @@
context (assoc context :request request) context (assoc context :request request)
queue (interceptor/queue executor (concat default-interceptors interceptors))] queue (interceptor/queue executor (concat default-interceptors interceptors))]
(interceptor/enqueue executor context queue)) (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] :leave (fn [context]
(if-not (:response context) (if-not (:response context)
(assoc context :response (default-handler (:request context))) (assoc context :response (default-handler (:request context)))
@ -130,7 +133,7 @@
(assoc ::interceptor/queue (partial interceptor/queue executor)) (assoc ::interceptor/queue (partial interceptor/queue executor))
(dissoc :data) ; data is already merged into routes (dissoc :data) ; data is already merged into routes
(cond-> (seq interceptors) (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 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-request (ring/create-enrich-request inject-match? inject-router?)
enrich-default-request (ring/create-enrich-default-request inject-router?)] enrich-default-request (ring/create-enrich-default-request inject-router?)]

View file

@ -1,7 +1,7 @@
(ns reitit.http.coercion (ns reitit.http.coercion
(:require [reitit.coercion :as 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 (defn coerce-request-interceptor
"Interceptor for pluggable request coercion. "Interceptor for pluggable request coercion.

View file

@ -1,8 +1,8 @@
(ns reitit.http.spec (ns reitit.http.spec
(:require [clojure.spec.alpha :as s] (:require [clojure.spec.alpha :as s]
[reitit.ring.spec :as rrs]
[reitit.interceptor :as interceptor]
[reitit.exception :as exception] [reitit.exception :as exception]
[reitit.interceptor :as interceptor]
[reitit.ring.spec :as rrs]
[reitit.spec :as rs])) [reitit.spec :as rs]))
;; ;;
@ -22,5 +22,5 @@
[routes {:keys [spec ::rs/wrap] :or {spec ::data, wrap identity}}] [routes {:keys [spec ::rs/wrap] :or {spec ::data, wrap identity}}]
(when-let [problems (rrs/validate-route-data routes :interceptors wrap spec)] (when-let [problems (rrs/validate-route-data routes :interceptors wrap spec)]
(exception/fail! (exception/fail!
::rs/invalid-route-data ::rs/invalid-route-data
{:problems problems}))) {:problems problems})))

View file

@ -0,0 +1 @@
../../../.clj-kondo/module_config.edn

View file

@ -1,4 +1,4 @@
(defproject metosin/reitit-interceptors "0.5.10" (defproject metosin/reitit-interceptors "0.5.18"
:description "Reitit, common interceptors bundled" :description "Reitit, common interceptors bundled"
:url "https://github.com/metosin/reitit" :url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License" :license {:name "Eclipse Public License"

View file

@ -20,7 +20,9 @@
(defn- polish [ctx] (defn- polish [ctx]
(-> 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))) (update :request dissoc ::r/match ::r/router)))
(defn- handle [name stage] (defn- handle [name stage]
@ -36,16 +38,16 @@
[stages {:keys [enter leave error name] :as interceptor}] [stages {:keys [enter leave error name] :as interceptor}]
(if (->> (select-keys interceptor stages) (vals) (keep identity) (seq)) (if (->> (select-keys interceptor stages) (vals) (keep identity) (seq))
(cond-> {:name ::diff} (cond-> {:name ::diff}
(and enter (stages :enter)) (assoc :enter (handle name :enter)) (and enter (stages :enter)) (assoc :enter (handle name :enter))
(and leave (stages :leave)) (assoc :leave (handle name :leave)) (and leave (stages :leave)) (assoc :leave (handle name :leave))
(and error (stages :error)) (assoc :error (handle name :error))))) (and error (stages :error)) (assoc :error (handle name :error)))))
(defn print-context-diffs (defn print-context-diffs
"A interceptor chain transformer that adds a context diff printer between all interceptors" "A interceptor chain transformer that adds a context diff printer between all interceptors"
[interceptors] [interceptors]
(reduce (reduce
(fn [chain interceptor] (fn [chain interceptor]
(into chain (keep identity [(diff-interceptor #{:leave :error} interceptor) (into chain (keep identity [(diff-interceptor #{:leave :error} interceptor)
interceptor interceptor
(diff-interceptor #{:enter} interceptor)]))) (diff-interceptor #{:enter} interceptor)])))
[(diff-interceptor #{:enter :leave :error} {:enter identity})] interceptors)) [(diff-interceptor #{:enter :leave :error} {:enter identity})] interceptors))

View file

@ -1,10 +1,10 @@
(ns reitit.http.interceptors.exception (ns reitit.http.interceptors.exception
(:require [reitit.coercion :as coercion] (:require [clojure.spec.alpha :as s]
[reitit.ring :as ring] [clojure.string :as str]
[clojure.spec.alpha :as s] [reitit.coercion :as coercion]
[clojure.string :as str]) [reitit.ring :as ring])
(:import (java.time Instant) (:import (java.io PrintWriter Writer)
(java.io PrintWriter))) (java.time Instant)))
(s/def ::handlers (s/map-of any? fn?)) (s/def ::handlers (s/map-of any? fn?))
(s/def ::spec (s/keys :opt-un [::handlers])) (s/def ::spec (s/keys :opt-un [::handlers]))
@ -25,17 +25,17 @@
error-handler (or (get handlers type) error-handler (or (get handlers type)
(get handlers ex-class) (get handlers ex-class)
(some (some
(partial get handlers) (partial get handlers)
(descendants type)) (descendants type))
(some (some
(partial get handlers) (partial get handlers)
(super-classes ex-class)) (super-classes ex-class))
(get handlers ::default))] (get handlers ::default))]
(if-let [wrap (get handlers ::wrap)] (if-let [wrap (get handlers ::wrap)]
(wrap error-handler error request) (wrap error-handler error request)
(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"))) (.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}] (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)) (print! *out* (Instant/now) request-method (pr-str uri) "=>" (.getMessage e))
(.printStackTrace e ^PrintWriter *out*) (.printStackTrace e (PrintWriter. ^Writer *out*))
(handler e req)) (handler e req))
;; ;;

View file

@ -1,8 +1,8 @@
(ns reitit.http.interceptors.multipart (ns reitit.http.interceptors.multipart
(:require [reitit.coercion :as coercion] (:require [clojure.spec.alpha :as s]
[reitit.coercion :as coercion]
[reitit.spec] [reitit.spec]
[ring.middleware.multipart-params :as multipart-params] [ring.middleware.multipart-params :as multipart-params]
[clojure.spec.alpha :as s]
[spec-tools.core :as st]) [spec-tools.core :as st])
(:import (java.io File))) (:import (java.io File)))
@ -18,14 +18,14 @@
(def temp-file-part (def temp-file-part
"Spec for file param created by ring.middleware.multipart-params.temp-file store." "Spec for file param created by ring.middleware.multipart-params.temp-file store."
(st/spec (st/spec
{:spec (s/keys :req-un [::filename ::content-type ::tempfile ::size]) {:spec (s/keys :req-un [::filename ::content-type ::tempfile ::size])
:swagger/type "file"})) :swagger/type "file"}))
(def bytes-part (def bytes-part
"Spec for file param created by ring.middleware.multipart-params.byte-array store." "Spec for file param created by ring.middleware.multipart-params.byte-array store."
(st/spec (st/spec
{:spec (s/keys :req-un [::filename ::content-type ::bytes]) {:spec (s/keys :req-un [::filename ::content-type ::bytes])
:swagger/type "file"})) :swagger/type "file"}))
(defn- coerced-request [request coercers] (defn- coerced-request [request coercers]
(if-let [coerced (if coercers (coercion/coerce-request coercers request))] (if-let [coerced (if coercers (coercion/coerce-request coercers request))]
@ -49,7 +49,7 @@
:compile (fn [{:keys [parameters coercion]} opts] :compile (fn [{:keys [parameters coercion]} opts]
(if-let [multipart (:multipart parameters)] (if-let [multipart (:multipart parameters)]
(let [parameter-coercion {:multipart (coercion/->ParameterCoercion (let [parameter-coercion {:multipart (coercion/->ParameterCoercion
:multipart-params :string true true)} :multipart-params :string true true)}
opts (assoc opts ::coercion/parameter-coercion parameter-coercion) opts (assoc opts ::coercion/parameter-coercion parameter-coercion)
coercers (if multipart (coercion/request-coercers coercion parameters opts))] coercers (if multipart (coercion/request-coercers coercion parameters opts))]
{:data {:swagger {:consumes ^:replace #{"multipart/form-data"}}} {:data {:swagger {:consumes ^:replace #{"multipart/form-data"}}}

View file

@ -1,7 +1,7 @@
(ns reitit.http.interceptors.muuntaja (ns reitit.http.interceptors.muuntaja
(:require [muuntaja.core :as m] (:require [clojure.spec.alpha :as s]
[muuntaja.interceptor] [muuntaja.core :as m]
[clojure.spec.alpha :as s])) [muuntaja.interceptor]))
(s/def ::muuntaja m/muuntaja?) (s/def ::muuntaja m/muuntaja?)
(s/def ::spec (s/keys :opt-un [::muuntaja])) (s/def ::spec (s/keys :opt-un [::muuntaja]))
@ -40,10 +40,10 @@
:compile (fn [{:keys [muuntaja parameters]} _] :compile (fn [{:keys [muuntaja parameters]} _]
(if-let [muuntaja (or muuntaja default-muuntaja)] (if-let [muuntaja (or muuntaja default-muuntaja)]
(merge (merge
(stripped (muuntaja.interceptor/format-interceptor muuntaja)) (stripped (muuntaja.interceptor/format-interceptor muuntaja))
(if (publish-swagger-data? parameters) (if (publish-swagger-data? parameters)
{:data {:swagger {:produces (displace (m/encodes muuntaja)) {:data {:swagger {:produces (displace (m/encodes muuntaja))
:consumes (displace (m/decodes muuntaja))}}}))))})) :consumes (displace (m/decodes muuntaja))}}}))))}))
(defn format-negotiate-interceptor (defn format-negotiate-interceptor
"Interceptor for content-negotiation. "Interceptor for content-negotiation.
@ -87,9 +87,9 @@
:compile (fn [{:keys [muuntaja parameters]} _] :compile (fn [{:keys [muuntaja parameters]} _]
(if-let [muuntaja (or muuntaja default-muuntaja)] (if-let [muuntaja (or muuntaja default-muuntaja)]
(merge (merge
(stripped (muuntaja.interceptor/format-request-interceptor muuntaja)) (stripped (muuntaja.interceptor/format-request-interceptor muuntaja))
(when (publish-swagger-data? parameters) (when (publish-swagger-data? parameters)
{:data {:swagger {:consumes (displace (m/decodes muuntaja))}}}))))})) {:data {:swagger {:consumes (displace (m/decodes muuntaja))}}}))))}))
(defn format-response-interceptor (defn format-response-interceptor
"Interceptor for response formatting. "Interceptor for response formatting.
@ -112,6 +112,6 @@
:compile (fn [{:keys [muuntaja parameters]} _] :compile (fn [{:keys [muuntaja parameters]} _]
(if-let [muuntaja (or muuntaja default-muuntaja)] (if-let [muuntaja (or muuntaja default-muuntaja)]
(merge (merge
(stripped (muuntaja.interceptor/format-response-interceptor muuntaja)) (stripped (muuntaja.interceptor/format-response-interceptor muuntaja))
(when (publish-swagger-data? parameters) (when (publish-swagger-data? parameters)
{:data {:swagger {:produces (displace (m/encodes muuntaja))}}}))))})) {:data {:swagger {:produces (displace (m/encodes muuntaja))}}}))))}))

Some files were not shown because too many files have changed in this diff Show more