Update docs, including brackets

This commit is contained in:
Tommi Reiman 2019-02-10 12:43:00 +02:00
parent e60d176a2d
commit 885ca8813c
3 changed files with 200 additions and 18 deletions

View file

@ -1,8 +1,17 @@
# Route Data
Route data is the core feature of reitit. Routes can have any map-like data attached to them. This data is interpreted either by the client application or the `Router` via its `:coerce` and `:compile` hooks. Route data format can be defined and validated with `clojure.spec` enabling an architecture of both [adaptive and principled](https://youtu.be/x9pxbnFC4aQ?t=1907) components.
Route data is the key feature of reitit. Routes can have any map-like data attached to them, to be interpreted by the client application, `Router` or routing components like `Middleware` or `Interceptors`.
Raw routes can have a non-sequential route argument that is expanded (via router `:expand` hook) into route data at router creation time. By default, Keywords are expanded into `:name` and functions into `:handler` keys.
```clj
[["/ping" {:name ::ping}]
["/pong" {:handler identity}]
["/users" {:get {:roles #{:admin}
:handler identity}}]]
```
Besides map-like data, raw routes can have any non-sequential route argument after the path. This argument is expanded by `Router` (via `:expand` option) into route data at router creation time.
By default, Keywords are expanded into `:name` and functions into `:handler` keys.
```clj
(require '[reitit.core :as r])
@ -15,11 +24,13 @@ Raw routes can have a non-sequential route argument that is expanded (via router
: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.
## Using Route Data
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}]
; [["/ping" {:name ::ping}]
; ["/pong" {:handler identity]}
; ["/users" {:get {:roles #{:admin}
; :handler identity}}]]
@ -43,7 +54,7 @@ The expanded route data can be retrieved from a router with `routes` and is retu
; :path "/ping"}
```
## Nested route data
## 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 collections is `:append`, but this can be overridden to `:prepend`, `:replace` or `:displace` using the target meta-data.
@ -73,9 +84,72 @@ Resolved route tree:
; :roles #{:db-admin}}]]
```
## Route Data Fragments
Just like [fragments in React.js](https://reactjs.org/docs/fragments.html), we can create routing tree fragments by using empty path `""`. This allows us to add route data without accumulating to path.
Given a route tree:
```clj
[["/swagger.json" ::swagger]
["/api-docs" ::api-docs]
["/api/ping" ::ping]
["/api/pong" ::pong]]
```
Adding `:no-doc` route data to exclude the first routes from generated [Swagger documentation](../ring/swagger.md):
```clj
[["" {:no-doc true}
["/swagger.json" ::swagger]
["/api-docs" ::api-docs]]
["/api/ping" ::ping]
["/api/pong" ::pong]]
```
Accumulated route data:
```clj
(def router
(r/router
[["" {:no-doc true}
["/swagger.json" ::swagger]
["/api-docs" ::api-docs]]
["/api/ping" ::ping]
["/api/pong" ::pong]]))
(r/routes router)
; [["/swagger.json" {:no-doc true, :name ::swagger}]
; ["/api-docs" {:no-doc true, :name ::api-docs}]
; ["/api/ping" {:name ::ping}]
; ["/api/pong" {:name ::pong}]]
```
## Top-level Route Data
Route data can be introduced also via `Router` option `:data`:
```clj
(def router
(r/router
["/api"
{:middleware [::api]}
["/ping" ::ping]
["/pong" ::pong]]
{:data {:middleware [::session]}}))
```
Expanded routes:
```clj
[["/api/ping" {:middleware [::session ::api], :name ::ping}]
["/api/pong" {:middleware [::session ::api], :name ::pong}]]
```
## Expansion
By default, router `:expand` hook maps to `reitit.core/expand` function, backed by a `reitit.core/Expand` protocol. One can provide either a totally different function or add new implementations to that protocol. Expand implementations can be recursive.
By default, router `:expand` option has value `r/expand` function, backed by a `r/Expand` protocol. Expansion can be customized either by swapping the `:expand` implementation or by extending the Protocol. `r/Expand` implementations can be recursive.
Naive example to add direct support for `java.io.File` route argument:
@ -91,4 +165,8 @@ Naive example to add direct support for `java.io.File` route argument:
["/" (java.io.File. "index.html")])
```
See [router options](../advanced/configuring_routers.md) for all available options.
Page [shared routes](../advanced/shared_routes.md#using-custom-expander) has an example of an custom `:expand` implementation.
## Route data validation
See [Route data validation](route_data_validation.md), which enables an architecture of both [adaptive and principled](https://youtu.be/x9pxbnFC4aQ?t=1907) components.

View file

@ -4,7 +4,7 @@ Routes are defined as vectors of String path and optional (non-sequential) route
Routes can be wrapped in vectors and lists and `nil` routes are ignored.
Paths can have path-parameters (`:id`) or catch-all-parameters (`*path`).
Paths can have path-parameters (`:id`) or catch-all-parameters (`*path`). Since `0.4.0`, parameters can also be wrapped in brackets, enabling use of qualified keywords `{user/id}`, `{*user/path}`. The non-bracket syntax might be deprecated later.
### Examples
@ -35,12 +35,21 @@ Routes with path parameters:
["/api/:version/ping"]]
```
```clj
[["/users/{user-id}"]
["/files/file-{number}.pdf"]]
```
Route with catch-all parameter:
```clj
["/public/*path"]
```
```clj
["/public/{*path}"]
```
Nested routes:
```clj
@ -59,6 +68,42 @@ Same routes flattened:
["/api/ping" {:name ::ping}]]
```
### Encoding
Reitit does not apply any encoding to your paths. If you need that, you must encode them yourself. E.g., `/foo bar` should be `/foo%20bar`.
### Wildcards
Normal path-parameters (`:id`) can start anywhere in the path string, but have to end either to slash `/` (currently hardcoded) or to en end of path string:
```clj
[["/api/:version"]
["/files/file-:number"]
["/user/:user-id/orders"]]
```
Bracket path-parameters can start and stop anywhere in the path-string, the following character is used as a terminator.
```clj
[["/api/{version}"]
["/files/{name}.{extension}"]
["/user/{user-id}/orders"]]
```
Having multiple terminators after a bracket path-path parameter with identical path prefix will cause a compile-time error at router creation:
```clj
[["/files/file-{name}.pdf"] ;; terminator \.
["/files/file-{name}-{version}.pdf"]] ;; terminator \-
```
### Slash Free Routing
```clj
[["broker.{customer}.{device}.{*data}"]
["events.{target}.{type}"]]
```
### Generating routes
Routes are just data, so it's easy to create them programmatically:
@ -68,7 +113,7 @@ Routes are just data, so it's easy to create them programmatically:
["/api" {:interceptors [::api ::db]}
(for [[type interceptor] actions
:let [path (str "/" (name interceptor))
method (condp = type
method (case type
:query :get
:command :post)]]
[path {method {:interceptors [interceptor]}}])])
@ -84,8 +129,3 @@ Routes are just data, so it's easy to create them programmatically:
; ["/add-user" {:post {:interceptors [add-user]}}]
; ["/add-order" {:post {:interceptors [add-order]}}])]
```
### Encoding
Reitit does not apply any encoding to your paths. If you need that, you must encode them yourself.
E.g., `/foo bar` should be `/foo%20bar`.

View file

@ -41,10 +41,74 @@ The flattened route tree:
; ["/api/user/:id" {:name :user/user}]]
```
With a router instance, we can do [Path-based routing](path_based_routing.md) or [Name-based (Reverse) routing](name_based_routing.md).
## More details
Router options:
```clj
(r/options router)
{:lookup #object[...]
:expand #object[...]
:coerce #object[...]
:compile #object[...]
:conflicts #object[...]}
```
Route names:
```clj
(r/route-names router)
; [:user/ping :user/user]
```
The compiled route tree:
```clj
(r/routes router)
; [["/api/ping" {:name :user/ping} nil]
; ["/api/user/:id" {:name :user/user} nil]]
```
### Composing
As routes are defined as plain data, it's easy to merge multiple route trees into a single router
```clj
(def user-routes
[["/users" ::users]
["/users/:id" ::user]])
(def admin-routes
["/admin"
["/ping" ::ping]
["/users" ::users]])
(r/router
[admin-routes
user-routes])
```
Merged route tree:
```clj
(r/routes router)
; [["/admin/ping" {:name :user/ping}]
; ["/admin/db" {:name :user/db}]
; ["/users" {:name :user/users}]
; ["/users/:id" {:name :user/user}]]
```
More details on [composing routers](../advanced/composing_routers.md).
### 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
* route tree is compiled
* actual [router implementation](../advanced/different_routers.md) is selected and created
* route arguments are expanded (via `:expand` option)
* routes are coerced (via `:coerce` options)
* route tree is compiled (via `:compile` options)
* [route conflicts](advanced/route_conflicts.md) are resolved (via `:conflicts` options)
* optionally, route data is validated (via `:validate` options)
* [router implementation](../advanced/different_routers.md) is automatically selected (or forced via `:router` options) and created