mirror of
https://github.com/metosin/reitit.git
synced 2025-12-16 16:01:11 +00:00
Update docs, including brackets
This commit is contained in:
parent
e60d176a2d
commit
885ca8813c
3 changed files with 200 additions and 18 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue