From 885ca8813cdb2e8d044ba277de6888fcc5483021 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Sun, 10 Feb 2019 12:43:00 +0200 Subject: [PATCH] Update docs, including brackets --- doc/basics/route_data.md | 92 +++++++++++++++++++++++++++++++++++--- doc/basics/route_syntax.md | 54 +++++++++++++++++++--- doc/basics/router.md | 72 +++++++++++++++++++++++++++-- 3 files changed, 200 insertions(+), 18 deletions(-) diff --git a/doc/basics/route_data.md b/doc/basics/route_data.md index 212b5149..b615ab7c 100644 --- a/doc/basics/route_data.md +++ b/doc/basics/route_data.md @@ -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. diff --git a/doc/basics/route_syntax.md b/doc/basics/route_syntax.md index b96a8501..cb3216a2 100644 --- a/doc/basics/route_syntax.md +++ b/doc/basics/route_syntax.md @@ -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`. diff --git a/doc/basics/router.md b/doc/basics/router.md index 684c8ff8..3ac30136 100644 --- a/doc/basics/router.md +++ b/doc/basics/router.md @@ -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