mirror of
https://github.com/metosin/reitit.git
synced 2025-12-16 16:01:11 +00:00
Walk the docs, fix links, maybe better texts
This commit is contained in:
parent
d44b18ad66
commit
c3820239c5
20 changed files with 265 additions and 117 deletions
41
README.md
41
README.md
|
|
@ -1,22 +1,47 @@
|
|||
# reitit [](https://travis-ci.org/metosin/reitit) [](https://jarkeeper.com/metosin/reitit)
|
||||
# reitit [](https://travis-ci.org/metosin/reitit)
|
||||
|
||||
A friendly data-driven router for Clojure(Script).
|
||||
|
||||
* Simple data-driven [route syntax](https://metosin.github.io/reitit/basics/route_syntax.md)
|
||||
* [Route conflict resolution](https://metosin.github.io/reitit/advanced/route_conflicts.md)
|
||||
* First-class [route meta-data](https://metosin.github.io/reitit/basics/route_data.md)
|
||||
* Simple data-driven [route syntax](https://metosin.github.io/reitit/basics/route_syntax.html)
|
||||
* Route [conflict resolution](https://metosin.github.io/reitit/basics/route_conflicts.html)
|
||||
* First-class [route meta-data](https://metosin.github.io/reitit/basics/route_data.html)
|
||||
* Bi-directional routing
|
||||
* [Pluggable coercion](https://metosin.github.io/reitit/ring/parameter_coercion.md) ([clojure.spec](https://clojure.org/about/spec))
|
||||
* supports both [Middleware](https://metosin.github.io/reitit/ring/compiling_middleware.md) & Interceptors
|
||||
* [Ring-router](https://metosin.github.io/reitit/ring.html) with data-driven [middleware](https://metosin.github.io/reitit/ring/compiling_middleware.html)
|
||||
* [Pluggable coercion](https://metosin.github.io/reitit/ring/parameter_coercion.html) ([clojure.spec](https://clojure.org/about/spec))
|
||||
* Extendable
|
||||
* Fast
|
||||
|
||||
Ships with example router for [Ring](#ring). See [Issues](https://github.com/metosin/reitit/issues) for roadmap.
|
||||
See [Issues](https://github.com/metosin/reitit/issues) for roadmap.
|
||||
|
||||
## Latest version
|
||||
|
||||
[](http://clojars.org/metosin/reitit)
|
||||
|
||||
## Quick start
|
||||
|
||||
```clj
|
||||
(require '[reitit.core :as r])
|
||||
|
||||
(def router
|
||||
(r/router
|
||||
[["/api/ping" ::ping]
|
||||
["/api/orders/:id" ::order-by-id]]))
|
||||
|
||||
(r/match-by-path router "/api/ping")
|
||||
; #Match{:template "/api/ping"
|
||||
; :meta {:name ::ping}
|
||||
; :result nil
|
||||
; :params {}
|
||||
; :path "/api/ping"}
|
||||
|
||||
(r/match-by-name router ::order-by-id {:id 2})
|
||||
; #Match{:template "/api/orders/:id",
|
||||
; :meta {:name ::order-by-id},
|
||||
; :result nil,
|
||||
; :params {:id 2},
|
||||
; :path "/api/orders/2"}
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
[Check out the full documentation!](https://metosin.github.io/reitit/)
|
||||
|
|
@ -41,7 +66,7 @@ gitbook install
|
|||
gitbook serve
|
||||
```
|
||||
|
||||
To raise the version:
|
||||
To bump up version:
|
||||
|
||||
```bash
|
||||
# new version
|
||||
|
|
|
|||
|
|
@ -7,14 +7,15 @@
|
|||
* [Path-based Routing](basics/path_based_routing.md)
|
||||
* [Name-based Routing](basics/name_based_routing.md)
|
||||
* [Route data](basics/route_data.md)
|
||||
* [Different Routers](basics/different_routers.md)
|
||||
* [Route conflicts](basics/route_conflicts.md)
|
||||
* [Advanced](advanced/README.md)
|
||||
* [Route conflicts](advanced/route_conflicts.md)
|
||||
* [Route Validation](advanced/route_validation.md)
|
||||
* [Different Routers](advanced/different_routers.md)
|
||||
* [Configuring routers](advanced/configuring_routers.md)
|
||||
* [Ring](ring/README.md)
|
||||
* [Ring-router](ring/ring.md)
|
||||
* [Dynamic extensions](ring/dynamic_extensions.md)
|
||||
* [Data-driven Middleware](ring/data_driven_middleware.md)
|
||||
* [Parameter coercion](ring/parameter_coercion.md)
|
||||
* [Compiling middleware](ring/compiling_middleware.md)
|
||||
* TODO: Swagger & OpenAPI
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# Advanced
|
||||
|
||||
* [Route conflicts](advanced/route_conflicts.md)
|
||||
* [Route Validation](advanced/route_validation.md)
|
||||
* [Configuring routers](advanced/configuring_routers.md)
|
||||
* [Route Validation](route_validation.md)
|
||||
* [Different Routers](different_routers.md)
|
||||
* [Configuring routers](configuring_routers.md)
|
||||
|
|
|
|||
18
doc/advanced/different_routers.md
Normal file
18
doc/advanced/different_routers.md
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# 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` 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).
|
||||
|
||||
| router | description |
|
||||
| ------------------------------|-------------|
|
||||
| `:linear-router` | Matches the routes one-by-one starting from the top until a match is found. Works with any kind of routes.
|
||||
| `:lookup-router` | Fast router, uses hash-lookup to resolve the route. Valid if no paths have path or catch-all parameters.
|
||||
| `:mixed-router` | Creates internally a `:linear-router` and a `:lookup-router` and used them to effectively get best-of-both-worlds. Valid if there are no [Route conflicts](../basics/route_conflicts.md).
|
||||
| `::single-static-path-router` | Fastest possible router: valid only if there is one static route.
|
||||
| `:prefix-tree-router` | TODO: https://github.com/julienschmidt/httprouter#how-does-it-work
|
||||
|
||||
The router name can be asked from the router
|
||||
|
||||
```clj
|
||||
(r/router-name router)
|
||||
; :mixed-router
|
||||
```
|
||||
|
|
@ -4,12 +4,11 @@ Namespace `reitit.spec` contains [clojure.spec](https://clojure.org/about/spec)
|
|||
|
||||
**NOTE:** Use of specs requires to use one of the following:
|
||||
|
||||
* `[org.clojure/clojurescript "1.9.660"]`
|
||||
* `[org.clojure/clojure "1.9.0-alpha19"]`
|
||||
* `[clojure-future-spec "1.9.0-alpha17"]` (Clojure 1.8)
|
||||
* `[org.clojure/clojurescript "1.9.660"]` (or higher)
|
||||
* `[org.clojure/clojure "1.9.0-beta2"]` (or higher)
|
||||
* `[clojure-future-spec "1.9.0-alpha17"]` (if Clojure 1.8 is used)
|
||||
|
||||
## At runtime
|
||||
If route trees are generated at runtime (e.g. from external source like the database), one can use directly the `clojure.spec` functions.
|
||||
## Example
|
||||
|
||||
```clj
|
||||
(require '[clojure.spec.alpha :as s])
|
||||
|
|
@ -36,7 +35,7 @@ If route trees are generated at runtime (e.g. from external source like the data
|
|||
First add a `:dev` dependency to:
|
||||
|
||||
```clj
|
||||
[expound "0.3.0"]
|
||||
[expound "0.3.0"] ; or higher
|
||||
```
|
||||
|
||||
Some bootstrapping:
|
||||
|
|
@ -54,8 +53,9 @@ Some bootstrapping:
|
|||
And we are ready to go:
|
||||
|
||||
```clj
|
||||
(require '[reitit.core :as r])
|
||||
|
||||
(reitit/router
|
||||
(r/router
|
||||
["/api"
|
||||
["/public"
|
||||
["/ping"]
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# Basics
|
||||
|
||||
* [Route syntax](basics/route_syntax.md)
|
||||
* [Router](basics/router.md)
|
||||
* [Path-based Routing](basics/path_based_routing.md)
|
||||
* [Name-based Routing](basics/name_based_routing.md)
|
||||
* [Route data](basics/route_data.md)
|
||||
* [Different Routers](basics/different_routers.md)
|
||||
* [Route syntax](route_syntax.md)
|
||||
* [Router](router.md)
|
||||
* [Path-based Routing](path_based_routing.md)
|
||||
* [Name-based Routing](name_based_routing.md)
|
||||
* [Route data](route_data.md)
|
||||
* [Route conflicts](route_conflicts.md)
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
# Different Routers
|
||||
|
||||
Reitit ships with several different implementations for the `Router` protocol, originally based on the awesome [Pedestal](https://github.com/pedestal/pedestal/tree/master/route) implementation. `router` selects the most suitable implementation by inspecting the expanded routes. The implementation can be set manually using `:router` ROUTER OPTION.
|
||||
|
||||
| router | description |
|
||||
| ----------------------|-------------|
|
||||
| `:linear-router` | Matches the routes one-by-one starting from the top until a match is found. Works with any kind of routes.
|
||||
| `:lookup-router` | Fastest router, uses hash-lookup to resolve the route. Valid if no paths have path or catch-all parameters.
|
||||
| `:mixed-router` | Creates internally a `:linear-router` and a `:lookup-router` and used them to effectively get best-of-both-worlds. Valid if there are no CONFLICTING ROUTES.
|
||||
| `:prefix-tree-router` | [TODO](https://github.com/julienschmidt/httprouter#how-does-it-work)
|
||||
|
||||
The router name can be asked from the router
|
||||
|
||||
```clj
|
||||
(r/router-name router)
|
||||
; :mixed-router
|
||||
```
|
||||
|
|
@ -1,6 +1,18 @@
|
|||
## Name-based routing
|
||||
## Name-based (reverse) routing
|
||||
|
||||
All routes which `:name` route data defined, can be matched by name.
|
||||
All routes which have `:name` route data defined, can also be matched by name.
|
||||
|
||||
Given a router:
|
||||
|
||||
```clj
|
||||
(require '[reitit.core :as r])
|
||||
|
||||
(def router
|
||||
(r/router
|
||||
[["/api"
|
||||
["/ping" ::ping]
|
||||
["/user/:id" ::user]]]))
|
||||
```
|
||||
|
||||
Listing all route names:
|
||||
|
||||
|
|
@ -9,7 +21,25 @@ Listing all route names:
|
|||
; [:user/ping :user/user]
|
||||
```
|
||||
|
||||
Matching by name:
|
||||
No match returns `nil`:
|
||||
|
||||
```clj
|
||||
(r/match-by-name router ::kikka)
|
||||
nil
|
||||
```
|
||||
|
||||
Matching a route:
|
||||
|
||||
```clj
|
||||
(r/match-by-name router ::ping)
|
||||
; #Match{:template "/api/ping"
|
||||
; :meta {:name :user/ping}
|
||||
; :result nil
|
||||
; :params {}
|
||||
; :path "/api/ping"}
|
||||
```
|
||||
|
||||
If not all path-parameters are set, a `PartialMatch` is returned:
|
||||
|
||||
```clj
|
||||
(r/match-by-name router ::user)
|
||||
|
|
@ -23,7 +53,7 @@ Matching by name:
|
|||
; true
|
||||
```
|
||||
|
||||
We only got a partial match as we didn't provide the needed path-parameters. Let's provide the them too:
|
||||
With provided path-parameters:
|
||||
|
||||
```clj
|
||||
(r/match-by-name router ::user {:id "1"})
|
||||
|
|
|
|||
|
|
@ -6,11 +6,27 @@ Path-based routing is done using the `reitit.core/match-by-path` function. It ta
|
|||
* `PartialMatch`, path matched, missing path-parameters (only in reverse-routing)
|
||||
* `Match`, exact match
|
||||
|
||||
Given a router:
|
||||
|
||||
```clj
|
||||
(require '[reitit.core :as r])
|
||||
|
||||
(def router
|
||||
(r/router
|
||||
[["/api"
|
||||
["/ping" ::ping]
|
||||
["/user/:id" ::user]]]))
|
||||
```
|
||||
|
||||
No match returns `nil`:
|
||||
|
||||
```clj
|
||||
(r/match-by-path router "/hello")
|
||||
; nil
|
||||
```
|
||||
|
||||
Match provides the route information:
|
||||
|
||||
```clj
|
||||
(r/match-by-path router "/api/user/1")
|
||||
; #Match{:template "/api/user/:id"
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
# Route conflicts
|
||||
|
||||
Many routing libraries allow single path lookup could match multiple routes. Usually, first match is used. This is not good, especially if route tree is merged from multiple sources - routes might regress to be unreachable without a warning.
|
||||
Many routing libraries allow multiple matches for a single path lookup. Usually, the first match is used and the rest are effecively unreachanle. This is not good, especially if route tree is merged from multiple sources.
|
||||
|
||||
Reitit resolves this by running explicit conflicit resolution when a `Router` is created. Conflicting routes are passed into a `:conflicts` callback. Default implementation throws `ex-info` with a descriptive message.
|
||||
Reitit resolves this by running explicit conflicit resolution when a `router` is called. Conflicting routes are passed into a `:conflicts` callback. Default implementation throws `ex-info` with a descriptive message.
|
||||
|
||||
Examples routes with conflicts:
|
||||
Examples router with conflicting routes:
|
||||
|
||||
```clj
|
||||
(require '[reitit.core :as reitit])
|
||||
(require '[reitit.core :as r])
|
||||
|
||||
(def routes
|
||||
[["/ping"]
|
||||
|
|
@ -20,7 +20,7 @@ Examples routes with conflicts:
|
|||
By default, `ExceptionInfo` is thrown:
|
||||
|
||||
```clj
|
||||
(reitit/router routes)
|
||||
(r/router routes)
|
||||
; CompilerException clojure.lang.ExceptionInfo: Router contains conflicting routes:
|
||||
;
|
||||
; /:user-id/orders
|
||||
|
|
@ -38,7 +38,7 @@ By default, `ExceptionInfo` is thrown:
|
|||
Just logging the conflicts:
|
||||
|
||||
```clj
|
||||
(reitit/router
|
||||
(r/router
|
||||
routes
|
||||
{:conflicts (comp println reitit/conflicts-str)})
|
||||
; Router contains conflicting routes:
|
||||
|
|
@ -1,6 +1,47 @@
|
|||
# Route data
|
||||
|
||||
Routes can have arbitrary meta-data, interpreted by the router (via it's `:compile` hook) or the application itself. For nested routes, route data is accumulated recursively using [meta-merge](https://github.com/weavejester/meta-merge). By default, it appends collections, but it can be overridden to do `:prepend`, `:replace` or `:displace`.
|
||||
Route data is the heart of this library. Routes can have any data attachted to them. Data is interpeted either by the client application or the `Router` via it's `:coerce` and `:compile` hooks. This enables co-existence of both [adaptive and principled](https://youtu.be/x9pxbnFC4aQ?t=1907) components.
|
||||
|
||||
Routes can have a non-sequential route argument that is expanded into route data map when a router is created.
|
||||
|
||||
```clj
|
||||
(require '[reitit.core :as r])
|
||||
|
||||
(def router
|
||||
(r/router
|
||||
[["/ping" ::ping]
|
||||
["/pong" identity]
|
||||
["/users" {:get {:roles #{:admin}
|
||||
:handler identity}}]]))
|
||||
```
|
||||
|
||||
The expanded route data can be retrieved from a router with `routes` and is returned with `match-by-path` and `match-by-name` in case of a route match.
|
||||
|
||||
```clj
|
||||
(r/routes router)
|
||||
; [["/ping" {:name :user/ping}]
|
||||
; ["/pong" {:handler identity]}
|
||||
; ["/users" {:get {:roles #{:admin}
|
||||
; :handler identity}}]]
|
||||
|
||||
(r/match-by-path router "/ping")
|
||||
; #Match{:template "/ping"
|
||||
; :meta {:name :user/ping}
|
||||
; :result nil
|
||||
; :params {}
|
||||
; :path "/ping"}
|
||||
|
||||
(r/match-by-name router ::ping)
|
||||
; #Match{:template "/ping"
|
||||
; :meta {:name :user/ping}
|
||||
; :result nil
|
||||
; :params {}
|
||||
; :path "/ping"}
|
||||
```
|
||||
|
||||
## Nested route data
|
||||
|
||||
For nested route trees, route data is accumulated recursively from root towards leafs using [meta-merge](https://github.com/weavejester/meta-merge). Default behavior for colections is `:append`, but this can be overridden to `:prepend`, `:replace` or `:displace` using the target meta-data.
|
||||
|
||||
An example router with nested data:
|
||||
|
||||
|
|
@ -12,41 +53,47 @@ An example router with nested data:
|
|||
["/admin" {:roles #{:admin}}
|
||||
["/users" ::users]
|
||||
["/db" {:interceptors [::db]
|
||||
:roles ^:replace #{:db-admin}}
|
||||
["/:db" {:parameters {:db String}}
|
||||
["/drop" ::drop-db]
|
||||
["/stats" ::db-stats]]]]]))
|
||||
:roles ^:replace #{:db-admin}}]]]))
|
||||
```
|
||||
|
||||
Resolved route tree:
|
||||
|
||||
```clj
|
||||
(reitit/routes router)
|
||||
(r/routes router)
|
||||
; [["/api/ping" {:interceptors [::api]
|
||||
; :name ::ping}]
|
||||
; :name :user/ping}]
|
||||
; ["/api/admin/users" {:interceptors [::api]
|
||||
; :roles #{:admin}
|
||||
; :name ::users}]
|
||||
; ["/api/admin/db/:db/drop" {:interceptors [::api ::db]
|
||||
; :roles #{:db-admin}
|
||||
; :parameters {:db String}
|
||||
; :name ::drop-db}]
|
||||
; ["/api/admin/db/:db/stats" {:interceptors [::api ::db]
|
||||
; :roles #{:db-admin}
|
||||
; :parameters {:db String}
|
||||
; :name ::db-stats}]]
|
||||
; :name ::users} nil]
|
||||
; ["/api/admin/db" {:interceptors [::api ::db]
|
||||
; :roles #{:db-admin}}]]
|
||||
```
|
||||
|
||||
Route data is returned with `Match` and the application can act based on it.
|
||||
|
||||
## Expansion
|
||||
|
||||
By default, `reitit/Expand` protocol is used to expand the route arguments. It expands keywords into `:name` and functions into `:handler` key in the route data map. It's easy to add custom expanders and one can chenge the whole expand implementation via [router options](../advanced/configuring_routers.md).
|
||||
|
||||
```clj
|
||||
(r/match-by-path router "/api/admin/db/users/drop")
|
||||
; #Match{:template "/api/admin/db/:db/drop"
|
||||
; :meta {:interceptors [::api ::db]
|
||||
; :roles #{:db-admin}
|
||||
; :parameters {:db String}
|
||||
; :name ::drop-db}
|
||||
(require '[reitit.core :as r])
|
||||
|
||||
(def router
|
||||
(r/router
|
||||
[["/ping" ::ping]
|
||||
["/pong" identity]
|
||||
["/users" {:get {:roles #{:admin}
|
||||
:handler identity}}]]))
|
||||
|
||||
(r/routes router)
|
||||
; [["/ping" {:name :user/ping}]
|
||||
; ["/pong" {:handler identity]}
|
||||
; ["/users" {:get {:roles #{:admin}
|
||||
; :handler identity}}]]
|
||||
|
||||
(r/match-by-path router "/ping")
|
||||
; #Match{:template "/ping"
|
||||
; :meta {:name :user/ping}
|
||||
; :result nil
|
||||
; :params {:db "users"}
|
||||
; :path "/api/admin/db/users/drop"}
|
||||
; :params {}
|
||||
; :path "/ping"}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
# Route Syntax
|
||||
|
||||
Raw routes are defined as vectors, which have a String path, optional (non-sequential) route argument and optional child routes. Routes can be wrapped in vectors and lists and `nil` routes are ignored. Paths can have path-parameters (`:id`) or catch-all-parameters (`*path`).
|
||||
Routes are defined as vectors of String path and optional (non-sequential) route argument child routes.
|
||||
|
||||
Routes can be wrapped in vectors and lists and `nil` routes are ignored.
|
||||
|
||||
Paths can have path-parameters (`:id`) or catch-all-parameters (`*path`).
|
||||
|
||||
### Examples
|
||||
|
||||
Simple route:
|
||||
|
||||
|
|
@ -53,7 +59,8 @@ Same routes flattened:
|
|||
["/api/ping" {:name ::ping}]]
|
||||
```
|
||||
|
||||
As routes are just data, it's easy to create them programamtically:
|
||||
### Generating routes
|
||||
As routes are just data, it's easy to create them programmatically:
|
||||
|
||||
```clj
|
||||
(defn cqrs-routes [actions dev-mode?]
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# Router
|
||||
|
||||
Routes are just data and to do actual routing, we need a Router satisfying the `reitit.core/Router` protocol. Routers are created with `reitit.core/router` function, taking the raw routes and optionally an options map. Raw routes gets expanded and optionally coerced and compiled.
|
||||
Routes are just data and for routing, we need a router instance satisfying the `reitit.core/Router` protocol. Routers are created with `reitit.core/router` function, taking the raw routes and optionally an options map.
|
||||
|
||||
`Router` protocol:
|
||||
The `Router` protocol:
|
||||
|
||||
```clj
|
||||
(defprotocol Router
|
||||
|
|
@ -26,10 +26,25 @@ Creating a router:
|
|||
["/user/:id" ::user]]]))
|
||||
```
|
||||
|
||||
Router flattens the raw routes and expands the route arguments using `reitit.core/Expand` protocol. By default, `Keyword`s are expanded to `:name` and functions are expaned to `:handler`. `nil` routes are removed. The expanded routes can be retrieved with router:
|
||||
Name of the created router:
|
||||
|
||||
```clj
|
||||
(r/router-name router)
|
||||
; :mixed-router
|
||||
```
|
||||
|
||||
The flattened route tree:
|
||||
|
||||
```clj
|
||||
(r/routes router)
|
||||
; [["/api/ping" {:name :user/ping}]
|
||||
; ["/api/user/:id" {:name :user/user}]]
|
||||
```
|
||||
|
||||
### Behind the scenes
|
||||
When router is created, the following steps are done:
|
||||
* route tree is flattened
|
||||
* route arguments are expanded (via `reitit.core/Expand` protocol) and optionally coerced
|
||||
* [route conflicts](advanced/route_conflicts.md) are resolved
|
||||
* actual [router implementation](../advanced/different_routers.md) is selected and created
|
||||
* optionally route meta-data gets compiled
|
||||
|
|
|
|||
|
|
@ -2,5 +2,6 @@
|
|||
|
||||
* [Ring-router](ring.md)
|
||||
* [Dynamic extensions](dynamic_extensions.md)
|
||||
* [Data-driven Middleware](data_driven_middleware.md)
|
||||
* [Parameter coercion](parameter_coercion.md)
|
||||
* [Compiling middleware](compiling_middleware.md)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
# Compiling Middleware
|
||||
|
||||
The [meta-data extensions](ring.md#meta-data-based-extensions) are a easy way to extend the system. Routes meta-data can be transformed into any shape (records, functions etc.) in route compilation, enabling fast access at request-time.
|
||||
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.
|
||||
|
||||
Still, we can do better. As we know the exact route that interceptor/middleware is linked to, we can pass the (compiled) route information into the interceptor/middleware at creation-time. It can extract and transform relevant data just for it and pass it into the actual request-handler via a closure - yielding faster runtime processing.
|
||||
Still, we can do better. As we know the exact route that middleware/interceptor is linked to, we can pass the (compiled) route information into the middleware/interceptor at creation-time. It can do local reasoning: extract and transform relevant data just for it and pass it into the actual request-handler via a closure - yielding much faster runtime processing. Middleware/interceptor can also decide not to mount itself. Why mount a `wrap-enforce-roles` middleware for a route if there are no roles required for it?
|
||||
|
||||
To do this we use [middleware records](ring.md#middleware-records) `:gen` hook instead of the normal `:wrap`. `:gen` expects a function of `route-meta router-opts => wrap`. Middleware can also return `nil`, which effective unmounts the middleware. 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) `:gen` hook instead of the normal `:wrap`. `:gen` expects a function of `route-meta router-opts => wrap`. Middleware can also return `nil`, which effective unmounts the middleware.
|
||||
|
||||
To demonstrate the two approaches, below are response coercion middleware written as normal ring middleware function and as middleware record with `:gen`. These are the actual codes are from [`reitit.coercion`](https://github.com/metosin/reitit/blob/master/src/reitit/coercion.cljc):
|
||||
To demonstrate the two approaches, below are response coercion middleware written as normal ring middleware function and as middleware record with `:gen`. These are the actual codes are from [`reitit.ring.coercion`](https://github.com/metosin/reitit/blob/master/src/reitit/ring/coercion.cljc):
|
||||
|
||||
## Naive
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ To demonstrate the two approaches, below are response coercion middleware writte
|
|||
(defn wrap-coerce-response
|
||||
"Pluggable response coercion middleware.
|
||||
Expects a :coercion of type `reitit.coercion.protocol/Coercion`
|
||||
and :responses from route meta, otherwise does not mount."
|
||||
and :responses from route meta, otherwise will do nothing."
|
||||
[handler]
|
||||
(fn
|
||||
([request]
|
||||
|
|
@ -27,20 +27,17 @@ To demonstrate the two approaches, below are response coercion middleware writte
|
|||
coercion (-> match :meta :coercion)
|
||||
opts (-> match :meta :opts)]
|
||||
(if (and coercion responses)
|
||||
(let [coercers (response-coercers coercion responses opts)
|
||||
coerced (coerce-response coercers request response)]
|
||||
(coerce-response coercers request (handler request)))
|
||||
(handler request))))
|
||||
(let [coercers (response-coercers coercion responses opts)]
|
||||
(coerce-response coercers request response))
|
||||
response)))
|
||||
([request respond raise]
|
||||
(let [response (handler request)
|
||||
method (:request-method request)
|
||||
(let [method (:request-method request)
|
||||
match (ring/get-match request)
|
||||
responses (-> match :result method :meta :responses)
|
||||
coercion (-> match :meta :coercion)
|
||||
opts (-> match :meta :opts)]
|
||||
(if (and coercion responses)
|
||||
(let [coercers (response-coercers coercion responses opts)
|
||||
coerced (coerce-response coercers request response)]
|
||||
(let [coercers (response-coercers coercion responses opts)]
|
||||
(handler request #(respond (coerce-response coercers request %))))
|
||||
(handler request respond raise))))))
|
||||
```
|
||||
|
|
@ -69,4 +66,4 @@ To demonstrate the two approaches, below are response coercion middleware writte
|
|||
(handler request #(respond (coerce-response coercers request %)) raise)))))))}))
|
||||
```
|
||||
|
||||
The `:gen` -version has 50% less code, is easier to reason about and is 2-4x faster on basic perf tests.
|
||||
The `:gen` -version has 50% less code, is easier to reason about and is twice as faster on basic perf tests.
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
# Middleware Records
|
||||
# Data-driven Middleware
|
||||
|
||||
Reitit supports first-class data-driven middleware via `reitit.middleware/Middleware` records, created with `reitit.middleware/create` function. The following keys have special purpose:
|
||||
Reitit supports first-class data-driven middleware via `reitit.ring.middleware/Middleware` records, created with `reitit.ring.middleware/create` function. The following keys have special purpose:
|
||||
|
||||
| key | description |
|
||||
| -----------|-------------|
|
||||
| `:name` | Name of the middleware as qualified keyword (optional,recommended for libs)
|
||||
| `:wrap` | The actual middleware function of `handler args? => request => response`
|
||||
| `:gen` | Middleware compile function, see [compiling middleware](#compiling-middleware).
|
||||
| `:gen` | Middleware compile function, see [compiling middleware](compiling_middleware.md).
|
||||
|
||||
When routes are compiled, all middleware are expanded (and optionally compiled) into `Middleware` and stored in compilation results for later use (api-docs etc). For actual request processing, they are unwrapped into normal middleware functions producing zero runtime performance penalty. Middleware expansion is backed by `reitit.middleware/IntoMiddleware` protocol, enabling plain clojure(script) maps to be used.
|
||||
When routes are compiled, all middleware are expanded (and optionally compiled) into `Middleware` Records and stored in compilation results for later use (api-docs etc). For actual request processing, they are unwrapped into normal middleware functions and composed together producing zero runtime performance penalty. Middleware expansion is backed by `reitit.middleware/IntoMiddleware` protocol, enabling plain clojure(script) maps to be used.
|
||||
|
||||
A Record:
|
||||
|
||||
|
|
@ -32,3 +32,7 @@ As plain map:
|
|||
:wrap (fn [handler]
|
||||
(wrap handler :api))})
|
||||
```
|
||||
|
||||
### TODO
|
||||
|
||||
more!
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# 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 dynamic 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:
|
||||
|
||||
|
|
@ -10,7 +10,7 @@ Example middleware to guard routes based on user roles:
|
|||
(defn wrap-enforce-roles [handler]
|
||||
(fn [{:keys [::roles] :as request}]
|
||||
(let [required (some-> request (ring/get-match) :meta ::roles)]
|
||||
(if (and (seq required) (not (set/intersection required roles)))
|
||||
(if (and (seq required) (not (set/subset? required roles)))
|
||||
{:status 403, :body "forbidden"}
|
||||
(handler request)))))
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
# Parameter coercion
|
||||
|
||||
Reitit provides pluggable parameter coercion via `reitit.coercion.protocol/Coercion` protocol, originally introduced in [compojure-api](https://clojars.org/metosin/compojure-api). Reitit ships with `reitit.coercion.spec/SpecCoercion` providing implemenation for [clojure.spec](https://clojure.org/about/spec) and [data-specs](https://github.com/metosin/spec-tools#data-specs).
|
||||
Reitit provides pluggable parameter coercion via `reitit.ring.coercion.protocol/Coercion` protocol, originally introduced in [compojure-api](https://clojars.org/metosin/compojure-api). Reitit ships with `reitit.ring.coercion.spec/SpecCoercion` providing implemenation for [clojure.spec](https://clojure.org/about/spec) and [data-specs](https://github.com/metosin/spec-tools#data-specs).
|
||||
|
||||
**NOTE**: Before Clojure 1.9.0 is shipped, to use the spec-coercion, one needs to add the following dependencies manually to the project:
|
||||
|
||||
```clj
|
||||
[org.clojure/clojure "1.9.0-alpha20"]
|
||||
[org.clojure/clojure "1.9.0-beta2"]
|
||||
[org.clojure/spec.alpha "0.1.123"]
|
||||
[metosin/spec-tools "0.3.3"]
|
||||
[metosin/spec-tools "0.4.0"]
|
||||
```
|
||||
|
||||
### Ring request and response coercion
|
||||
|
|
@ -18,7 +18,9 @@ To use `Coercion` with Ring, one needs to do the following:
|
|||
* `:parameters` map, with submaps for different parameters: `:query`, `:body`, `:form`, `:header` and `:path`. Parameters are defined in the format understood by the `Coercion`.
|
||||
* `:responses` map, with response status codes as keys (or `:default` for "everything else") with maps with `:schema` and optionally `:description` as values.
|
||||
2. Define a `Coercion` to route meta-data under `:coercion`
|
||||
3. Mount request & response coercion middleware to the routes.
|
||||
3. Mount request & response coercion middleware to the routes (recommended to mount to all routes under router as they mounted only to routes which have the parameters / responses defined):
|
||||
* `reitit.ring.coercion/gen-wrap-coerce-parameters`
|
||||
* `gen-wrap-coerce-parameters/gen-wrap-coerce-responses`
|
||||
|
||||
If the request coercion succeeds, the coerced parameters are injected into request under `:parameters`.
|
||||
|
||||
|
|
@ -28,8 +30,8 @@ If either request or response coercion fails, an descriptive error is thrown.
|
|||
|
||||
```clj
|
||||
(require '[reitit.ring :as ring])
|
||||
(require '[reitit.coercion :as coercion])
|
||||
(require '[reitit.coercion.spec :as spec])
|
||||
(require '[reitit.ring.coercion :as coercion])
|
||||
(require '[reitit.ring.coercion.spec :as spec])
|
||||
|
||||
(def app
|
||||
(ring/ring-handler
|
||||
|
|
@ -56,10 +58,12 @@ If either request or response coercion fails, an descriptive error is thrown.
|
|||
|
||||
#### Example with specs
|
||||
|
||||
Currently, `clojure.spec` [doesn't support runtime transformations via conforming](https://dev.clojure.org/jira/browse/CLJ-2116), so one needs to wrap all specs with `spec-tools.core/spec`.
|
||||
|
||||
```clj
|
||||
(require '[reitit.ring :as ring])
|
||||
(require '[reitit.coercion :as coercion])
|
||||
(require '[reitit.coercion.spec :as spec])
|
||||
(require '[reitit.ring.coercion :as coercion])
|
||||
(require '[reitit.ring.coercion.spec :as spec])
|
||||
(require '[clojure.spec.alpha :as s])
|
||||
(require '[spec-tools.core :as st])
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ Applying the handler:
|
|||
; {:status 200, :body "ok"}
|
||||
```
|
||||
|
||||
The expanded routes:
|
||||
The expanded routes shows the compilation results:
|
||||
|
||||
```clj
|
||||
(-> app (ring/get-router) (reitit/routes))
|
||||
|
|
@ -58,7 +58,7 @@ Handler are also looked under request-method keys: `:get`, `:head`, `:patch`, `:
|
|||
; nil
|
||||
```
|
||||
|
||||
Reverse routing:
|
||||
Name-based reverse routing:
|
||||
|
||||
```clj
|
||||
(-> app
|
||||
|
|
@ -70,9 +70,9 @@ Reverse routing:
|
|||
|
||||
# Middleware
|
||||
|
||||
Middleware can be added with a `:middleware` key, with a vector value of the following:
|
||||
Middleware can be added with a `:middleware` key, either to top-level or under `:request-method` submap. It's value should be a vector value of the following:
|
||||
|
||||
1. ring middleware function `handler -> request -> response`
|
||||
1. normal ring middleware function `handler -> request -> response`
|
||||
2. vector of middleware function `handler ?args -> request -> response` and optinally it's args.
|
||||
|
||||
A middleware and a handler:
|
||||
|
|
@ -96,7 +96,7 @@ App with nested middleware:
|
|||
["/ping" handler]
|
||||
["/admin" {:middleware [[wrap :admin]]}
|
||||
["/db" {:middleware [[wrap :db]]
|
||||
:delete {:middleware [#(wrap % :delete)]
|
||||
:delete {:middleware [[wrap :delete]]
|
||||
:handler handler}}]]])))
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@
|
|||
(defn wrap-coerce-parameters
|
||||
"Pluggable request coercion middleware.
|
||||
Expects a :coercion of type `reitit.coercion.protocol/Coercion`
|
||||
and :parameters from route meta, otherwise does not mount."
|
||||
and :parameters from route meta, otherwise will do nothing."
|
||||
[handler]
|
||||
(fn
|
||||
([request]
|
||||
|
|
|
|||
Loading…
Reference in a new issue