Walk the docs, fix links, maybe better texts

This commit is contained in:
Tommi Reiman 2017-10-30 08:46:20 +02:00
parent d44b18ad66
commit c3820239c5
20 changed files with 265 additions and 117 deletions

View file

@ -1,22 +1,47 @@
# reitit [![Build Status](https://travis-ci.org/metosin/reitit.svg?branch=master)](https://travis-ci.org/metosin/reitit) [![Dependencies Status](https://jarkeeper.com/metosin/reitit/status.svg)](https://jarkeeper.com/metosin/reitit)
# reitit [![Build Status](https://travis-ci.org/metosin/reitit.svg?branch=master)](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
[![Clojars Project](http://clojars.org/metosin/reitit/latest-version.svg)](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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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}}]]])))
```

View file

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