Re-organize docs

This commit is contained in:
Tommi Reiman 2017-09-18 08:30:03 +03:00
parent 8e2e0c07f6
commit eb22bae047
14 changed files with 715 additions and 417 deletions

View file

@ -1,16 +1,133 @@
# reitit # Introduction
[reitit](https://github.com/metosin/reitit) is a friendly data-driven router for Clojure(Script). [Reitit](https://github.com/metosin/reitit) is a small Clojure(Script) library for data-driven routing.
* Simple data-driven [route syntax](./routing/route_syntax.md) * Simple data-driven [route syntax](./routing/route_syntax.md)
* First-class [route meta-data](./routing/route_metadata.md) * First-class [route meta-data](./routing/route_metadata.md)
* Generic, not tied to HTTP * Bi-directional-routing
* [Route conflict resolution](./routing/route_conflicts.md) * [Route conflict resolution](./routing/route_conflicts.md)
* [Pluggable coercion](./parameter-coercion.md) ([clojure.spec](https://clojure.org/about/spec)) * [Pluggable coercion](./parameter-coercion.md) ([clojure.spec](https://clojure.org/about/spec))
* both [Middleware](./ring.md#middleware) & Interceptors * Both [Middleware](./ring.md#middleware) & Interceptors
* Extendable * Extendable
* Fast * Fast
## Latest version To use Reitit, add the following dependecy to your project:
[![Clojars Project](http://clojars.org/metosin/reitit/latest-version.svg)](http://clojars.org/metosin/reitit) ```clj
[metosin/reitit "0.1.0-SNAPSHOT"]
```
# Examples
## Simple router
```clj
(require '[reitit.core :as r])
(def router
(r/router
[["/api/ping" ::ping]
["/api/orders/:id" ::order-by-id]]))
```
Routing:
```clj
(r/match-by-path router "/api/ipa")
; nil
(r/match-by-path router "/api/ping")
; #Match{:template "/api/ping"
; :meta {:name ::ping}
; :result nil
; :params {}
; :path "/api/ping"}
(r/match-by-path router "/api/orders/1")
; #Match{:template "/api/orders/:id"
; :meta {:name ::order-by-id}
; :result nil
; :params {:id "1"}
; :path "/api/orders/1"}
```
Reverse-routing:
```clj
(r/match-by-name router ::ipa)
; nil
(r/match-by-name router ::ping)
; #Match{:template "/api/ping"
; :meta {:name ::ping}
; :result nil
; :params {}
; :path "/api/ping"}
(r/match-by-name router ::order-by-id)
; #PartialMatch{:template "/api/orders/:id"
; :meta {:name :user/order-by-id}
; :result nil
; :params nil
; :required #{:id}}
(r/partial-match? (r/match-by-name router ::order-by-id))
; true
(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"}
```
## Ring-router
Ring-router adds support for `:handler` functions, `:middleware` and routing based on `:request-method`. It also supports pluggable parameter coercion (`clojure.spec`), data-driven middleware, route and middleware compilation, dynamic extensions and more.
```clj
(require '[reitit.ring :as ring])
(def handler [_]
{:status 200, :body "ok"})
(defn wrap [handler id]
(fn [request]
(update (handler request) :wrap (fnil conj '()) id)))
(def app
(ring/ring-handler
(ring/router
["/api" {:middleware [[wrap :api]]}
["/ping" {:get handler
:name ::ping}]
["/admin" {:middleware [[wrap :admin]]}
["/users" {:get handler
:post handler}]]])))
```
Routing:
```clj
(app {:request-method :get, :uri "/api/admin/users"})
; {:status 200, :body "ok", :wrap (:api :admin}
(app {:request-method :put, :uri "/api/admin/users"})
; nil
```
Reverse-routing:
```clj
(require '[reitit.core :as r])
(-> app (ring/get-router) (r/match-by-name ::ping))
; #Match{:template "/api/ping"
; :meta {:middleware [[#object[user$wrap] :api]]
; :get {:handler #object[user$handler]}
; :name ::ping}
; :result #Methods{...}
; :params nil
; :path "/api/ping"}
```

View file

@ -1,16 +1,21 @@
# Summary # Summary
* [Introduction](README.md) * [Introduction](README.md)
* Routing * Basics
* [Route syntax](routing/route_syntax.md) * [Route syntax](basics.md#route-syntax)
* [Routers](routing/routers.md) * [Router](basics.md#router)
* [Route metadata](routing/route_metadata.md) * [Path-based Routing](basics.md#path-based-routing)
* [Route conflicts](routing/route_conflicts.md) * [Name-based Routing](basics.md#name-based-routing)
* [Ring support](ring.md) * [Route data](basics.md#route-data)
* [Parameter coercion](parameter_coercion.md) * [Different Routers](basics.md#different-routers)
* [Compiling middleware](compiling_middleware.md) * Advanced
* [Validating route-trees](validating.md) * [Route conflicts](route_conflicts.md)
* [Configuring routers](configuring_routers.md) * [Route Validation](route_validation.md)
* TODO: Merging route-trees * [Configuring routers](configuring_routers.md)
* Ring
* [Ring-router](ring.md)
* [Dynamic extensions](dynamic_extensions.md)
* [Parameter coercion](parameter_coercion.md)
* [Compiling middleware](compiling_middleware.md)
* TODO: Swagger & OpenAPI * TODO: Swagger & OpenAPI
* TODO: Interceptors * TODO: Interceptors

254
doc/basics.md Normal file
View file

@ -0,0 +1,254 @@
# 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`).
Simple route:
```clj
["/ping"]
```
Two routes:
```clj
[["/ping"]
["/pong"]]
```
Routes with route arguments:
```clj
[["/ping" ::ping]
["/pong" {:name ::pong}]]
```
Routes with path parameters:
```clj
[["/users/:user-id"]
["/api/:version/ping"]]
```
Route with catch-all parameter:
```clj
["/public/*path"]
```
Nested routes:
```clj
["/api"
["/admin" {:middleware [::admin]}
["" ::admin]
["/db" ::db]]
["/ping" ::ping]]
```
Same routes flattened:
```clj
[["/api/admin" {:middleware [::admin], :name ::admin}]
["/api/admin/db" {:middleware [::admin], :name ::db}]
["/api/ping" {:name ::ping}]]
```
As routes are just data, it's easy to create them programamtically:
```clj
(defn cqrs-routes [actions dev-mode?]
["/api" {:interceptors [::api ::db]}
(for [[type interceptor] actions
:let [path (str "/" (name interceptor))
method (condp = type
:query :get
:command :post)]]
[path {method {:interceptors [interceptor]}}])
(if dev-mode? ["/dev-tools" ::dev-tools])])
```
```clj
(cqrs-routes
[[:query 'get-user]
[:command 'add-user]
[:command 'add-order]]
false)
; ["/api" {:interceptors [::api ::db]}
; (["/get-user" {:get {:interceptors [get-user]}}]
; ["/add-user" {:post {:interceptors [add-user]}}]
; ["/add-order" {:post {:interceptors [add-order]}}])
; nil]
```
# 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.
`Router` protocol:
```clj
(defprotocol Router
(router-name [this])
(routes [this])
(options [this])
(route-names [this])
(match-by-path [this path])
(match-by-name [this name] [this name params]))
```
Creating a router:
```clj
(require '[reitit.core :as r])
(def router
(r/router
[["/api"
["/ping" ::ping]
["/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:
```clj
(r/routes router)
; [["/api/ping" {:name :user/ping}]
; ["/api/user/:id" {:name :user/user}]]
```
## Path-based routing
Path-based routing is done using the `reitit.core/match-by-path` function. It takes the router and path as arguments and returns one of the following:
* `nil`, no match
* `PartialMatch`, path matched, missing path-parameters (only in reverse-routing)
* `Match`, exact match
```clj
(r/match-by-path router "/hello")
; nil
```
```clj
(r/match-by-path router "/api/user/1")
; #Match{:template "/api/user/:id"
; :meta {:name :user/user}
; :path "/api/user/1"
; :result nil
; :params {:id "1"}}
```
## Name-based routing
All routes which `:name` route data defined, can be matched by name.
Listing all route names:
```clj
(r/route-names router)
; [:user/ping :user/user]
```
Matching by name:
```clj
(r/match-by-name router ::user)
; #PartialMatch{:template "/api/user/:id",
; :meta {:name :user/user},
; :result nil,
; :params nil,
; :required #{:id}}
(r/partial-match? (r/match-by-name router ::user))
; true
```
We only got a partial match as we didn't provide the needed path-parameters. Let's provide the them too:
```clj
(r/match-by-name router ::user {:id "1"})
; #Match{:template "/api/user/:id"
; :meta {:name :user/user}
; :path "/api/user/1"
; :result nil
; :params {:id "1"}}
```
There is also a exception throwing version:
```clj
(r/match-by-name! router ::user)
; ExceptionInfo missing path-params for route /api/user/:id: #{:id}
```
# 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`.
An example router with nested data:
```clj
(def router
(r/router
["/api" {:interceptors [::api]}
["/ping" ::ping]
["/admin" {:roles #{:admin}}
["/users" ::users]
["/db" {:interceptors [::db]
:roles ^:replace #{:db-admin}}
["/:db" {:parameters {:db String}}
["/drop" ::drop-db]
["/stats" ::db-stats]]]]]))
```
Resolved route tree:
```clj
(reitit/routes router)
; [["/api/ping" {:interceptors [::api]
; :name ::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}]]
```
Route data is returned with `Match` and the application can act based on it.
```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}
; :result nil
; :params {:db "users"}
; :path "/api/admin/db/users/drop"}
```
# 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,4 +1,4 @@
## Compiling Middleware # 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 [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.
@ -8,7 +8,7 @@ To do this we use [middleware records](ring.md#middleware-records) `:gen` hook i
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.coercion`](https://github.com/metosin/reitit/blob/master/src/reitit/coercion.cljc):
### Naive ## Naive
* Extracts the compiled route information on every request. * Extracts the compiled route information on every request.
@ -45,7 +45,7 @@ To demonstrate the two approaches, below are response coercion middleware writte
(handler request respond raise)))))) (handler request respond raise))))))
``` ```
### Compiled ## Compiled
* Route information is provided via a closure * Route information is provided via a closure
* Pre-compiled coercers * Pre-compiled coercers

52
doc/dynamic_extensions.md Normal file
View file

@ -0,0 +1,52 @@
# 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.
Example middleware to guard routes based on user roles:
```clj
(require '[clojure.set :as set])
(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)))
{:status 403, :body "forbidden"}
(handler request)))))
```
Mounted to an app via router meta-data (effecting all routes):
```clj
(def handler (constantly {:status 200, :body "ok"}))
(def app
(ring/ring-handler
(ring/router
[["/api"
["/ping" handler]
["/admin" {::roles #{:admin}}
["/ping" handler]]]]
{:meta {:middleware [wrap-enforce-roles]}})))
```
Anonymous access to public route:
```clj
(app {:request-method :get, :uri "/api/ping"})
; {:status 200, :body "ok"}
```
Anonymous access to guarded route:
```clj
(app {:request-method :get, :uri "/api/admin/ping"})
; {:status 403, :body "forbidden"}
```
Authorized access to guarded route:
```clj
(app {:request-method :get, :uri "/api/admin/ping", ::roles #{:admin}})
; {:status 200, :body "ok"}
```

34
doc/middleware_records.md Normal file
View file

@ -0,0 +1,34 @@
# Middleware Records
Reitit supports first-class data-driven middleware via `reitit.middleware/Middleware` records, created with `reitit.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).
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.
A Record:
```clj
(require '[reitit.middleware :as middleware])
(def wrap2
(middleware/create
{:name ::wrap2
:description "a nice little mw, takes 1 arg."
:wrap wrap}))
```
As plain map:
```clj
;; plain map
(def wrap3
{:name ::wrap3
:description "a nice little mw, :api as arg"
:wrap (fn [handler]
(wrap handler :api))})
```

View file

@ -1,6 +1,6 @@
# Ring support # Ring Router
[Ring](https://github.com/ring-clojure/ring)-router adds support for ring concepts like [handlers](https://github.com/ring-clojure/ring/wiki/Concepts#handlers), [middleware](https://github.com/ring-clojure/ring/wiki/Concepts#middleware) and routing based on `:request-method`. Ring-router is created with `reitit.ring/router` function. It runs a custom route compiler, creating a optimized stucture for handling route matches, with compiled middleware chain & handlers for all request methods. It also ensures that all routes have a `:handler` defined. [Ring](https://github.com/ring-clojure/ring)-router adds support for [handlers](https://github.com/ring-clojure/ring/wiki/Concepts#handlers), [middleware](https://github.com/ring-clojure/ring/wiki/Concepts#middleware) and routing based on `:request-method`. Ring-router is created with `reitit.ring/router` function. It runs a custom route compiler, creating a optimized stucture for handling route matches, with compiled middleware chain & handlers for all request methods. It also ensures that all routes have a `:handler` defined.
Simple Ring app: Simple Ring app:
@ -39,7 +39,7 @@ The expanded routes:
Note that the compiled resuts as third element in the route vector. Note that the compiled resuts as third element in the route vector.
### Request-method based routing # Request-method based routing
Handler are also looked under request-method keys: `:get`, `:head`, `:patch`, `:delete`, `:options`, `:post` or `:put`. Top-level handler is used if request-method based handler is not found. Handler are also looked under request-method keys: `:get`, `:head`, `:patch`, `:delete`, `:options`, `:post` or `:put`. Top-level handler is used if request-method based handler is not found.
@ -68,7 +68,7 @@ Reverse routing:
; "/ping" ; "/ping"
``` ```
### Middleware # Middleware
Middleware can be added with a `:middleware` key, with a vector value of the following: Middleware can be added with a `:middleware` key, with a vector value of the following:
@ -112,94 +112,6 @@ Middleware is applied correctly:
; {:status 200, :body [:api :admin :db :delete :handler]} ; {:status 200, :body [:api :admin :db :delete :handler]}
``` ```
### Middleware Records # Async Ring
Reitit supports first-class data-driven middleware via `reitit.middleware/Middleware` records, created with `reitit.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).
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.
A Record:
```clj
(require '[reitit.middleware :as middleware])
(def wrap2
(middleware/create
{:name ::wrap2
:description "a nice little mw, takes 1 arg."
:wrap wrap}))
```
As plain map:
```clj
;; plain map
(def wrap3
{:name ::wrap3
:description "a nice little mw, :api as arg"
:wrap (fn [handler]
(wrap handler :api))})
```
### Async Ring
All built-in middleware provide both 2 and 3-arity and are compiled for both Clojure & ClojureScript, so they work with [Async Ring](https://www.booleanknot.com/blog/2016/07/15/asynchronous-ring.html) and [Node.js](https://nodejs.org) too. All built-in middleware provide both 2 and 3-arity and are compiled for both Clojure & ClojureScript, so they work with [Async Ring](https://www.booleanknot.com/blog/2016/07/15/asynchronous-ring.html) and [Node.js](https://nodejs.org) too.
### Meta-data based 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.
Example middleware to guard routes based on user roles:
```clj
(require '[clojure.set :as set])
(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)))
{:status 403, :body "forbidden"}
(handler request)))))
```
Mounted to an app via router meta-data (effecting all routes):
```clj
(def handler (constantly {:status 200, :body "ok"}))
(def app
(ring/ring-handler
(ring/router
[["/api"
["/ping" handler]
["/admin" {::roles #{:admin}}
["/ping" handler]]]]
{:meta {:middleware [wrap-enforce-roles]}})))
```
Anonymous access to public route:
```clj
(app {:request-method :get, :uri "/api/ping"})
; {:status 200, :body "ok"}
```
Anonymous access to guarded route:
```clj
(app {:request-method :get, :uri "/api/admin/ping"})
; {:status 403, :body "forbidden"}
```
Authorized access to guarded route:
```clj
(app {:request-method :get, :uri "/api/admin/ping", ::roles #{:admin}})
; {:status 200, :body "ok"}
```

56
doc/route_conflicts.md Normal file
View file

@ -0,0 +1,56 @@
# 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.
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.
Examples routes with conflicts:
```clj
(require '[reitit.core :as reitit])
(def routes
[["/ping"]
["/:user-id/orders"]
["/bulk/:bulk-id"]
["/public/*path"]
["/:version/status"]])
```
By default, `ExceptionInfo` is thrown:
```clj
(reitit/router routes)
; CompilerException clojure.lang.ExceptionInfo: Router contains conflicting routes:
;
; /:user-id/orders
; -> /public/*path
; -> /bulk/:bulk-id
;
; /bulk/:bulk-id
; -> /:version/status
;
; /public/*path
; -> /:version/status
;
```
Just logging the conflicts:
```clj
(reitit/router
routes
{:conflicts (comp println reitit/conflicts-str)})
; Router contains conflicting routes:
;
; /:user-id/orders
; -> /public/*path
; -> /bulk/:bulk-id
;
; /bulk/:bulk-id
; -> /:version/status
;
; /public/*path
; -> /:version/status
;
```

172
doc/route_validation.md Normal file
View file

@ -0,0 +1,172 @@
# Route validation
Namespace `reitit.spec` contains [clojure.spec](https://clojure.org/about/spec) definitions for raw-routes, routes, router and router options.
**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)
## 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.
```clj
(require '[clojure.spec.alpha :as s])
(require '[reitit.spec :as spec])
(def routes-from-db
["tenant1" ::tenant1])
(s/valid? ::spec/raw-routes routes-from-db)
; false
(s/explain ::spec/raw-routes routes-from-db)
; In: [0] val: "tenant1" fails spec: :reitit.spec/path at: [:route :path] predicate: (or (blank? %) (starts-with? % "/"))
; In: [0] val: "tenant1" fails spec: :reitit.spec/raw-route at: [:routes] predicate: (cat :path :reitit.spec/path :arg (? :reitit.spec/arg) :childs (* (and (nilable :reitit.spec/raw-route))))
; In: [1] val: :user/tenant1 fails spec: :reitit.spec/raw-route at: [:routes] predicate: (cat :path :reitit.spec/path :arg (? :reitit.spec/arg) :childs (* (and (nilable :reitit.spec/raw-route))))
; :clojure.spec.alpha/spec :reitit.spec/raw-routes
; :clojure.spec.alpha/value ["tenant1" :user/tenant1]
```
## At development time
`reitit.core/router` can be instrumented and use something like [expound](https://github.com/bhb/expound) to pretty-print the spec problems.
First add a `:dev` dependency to:
```clj
[expound "0.3.0"]
```
Some bootstrapping:
```clj
(require '[clojure.spec.test.alpha :as stest])
(require '[expound.alpha :as expound])
(require '[clojure.spec.alpha :as s])
(require '[reitit.spec])
(stest/instrument `reitit/router)
(set! s/*explain-out* expound/printer)
```
And we are ready to go:
```clj
(reitit/router
["/api"
["/public"
["/ping"]
["pong"]]])
; CompilerException clojure.lang.ExceptionInfo: Call to #'reitit.core/router did not conform to spec:
;
; -- Spec failed --------------------
;
; Function arguments
;
; (["/api" ...])
; ^^^^^^
;
; should satisfy
;
; (clojure.spec.alpha/cat
; :path
; :reitit.spec/path
; :arg
; (clojure.spec.alpha/? :reitit.spec/arg)
; :childs
; (clojure.spec.alpha/*
; (clojure.spec.alpha/and
; (clojure.spec.alpha/nilable :reitit.spec/raw-route))))
;
; or
;
; (clojure.spec.alpha/cat
; :path
; :reitit.spec/path
; :arg
; (clojure.spec.alpha/? :reitit.spec/arg)
; :childs
; (clojure.spec.alpha/*
; (clojure.spec.alpha/and
; (clojure.spec.alpha/nilable :reitit.spec/raw-route))))
;
; -- Relevant specs -------
;
; :reitit.spec/raw-route:
; (clojure.spec.alpha/cat
; :path
; :reitit.spec/path
; :arg
; (clojure.spec.alpha/? :reitit.spec/arg)
; :childs
; (clojure.spec.alpha/*
; (clojure.spec.alpha/and
; (clojure.spec.alpha/nilable :reitit.spec/raw-route))))
; :reitit.spec/raw-routes:
; (clojure.spec.alpha/or
; :route
; :reitit.spec/raw-route
; :routes
; (clojure.spec.alpha/coll-of :reitit.spec/raw-route :into []))
;
; -- Spec failed --------------------
;
; Function arguments
;
; ([... [... ... ["pong"]]])
; ^^^^^^
;
; should satisfy
;
; (fn
; [%]
; (or
; (clojure.string/blank? %)
; (clojure.string/starts-with? % "/")))
;
; or
;
; (fn
; [%]
; (or
; (clojure.string/blank? %)
; (clojure.string/starts-with? % "/")))
;
; -- Relevant specs -------
;
; :reitit.spec/path:
; (clojure.spec.alpha/and
; clojure.core/string?
; (clojure.core/fn
; [%]
; (clojure.core/or
; (clojure.string/blank? %)
; (clojure.string/starts-with? % "/"))))
; :reitit.spec/raw-route:
; (clojure.spec.alpha/cat
; :path
; :reitit.spec/path
; :arg
; (clojure.spec.alpha/? :reitit.spec/arg)
; :childs
; (clojure.spec.alpha/*
; (clojure.spec.alpha/and
; (clojure.spec.alpha/nilable :reitit.spec/raw-route))))
; :reitit.spec/raw-routes:
; (clojure.spec.alpha/or
; :route
; :reitit.spec/raw-route
; :routes
; (clojure.spec.alpha/coll-of :reitit.spec/raw-route :into []))
;
; -------------------------
; Detected 2 errors
```
# Validating route data
*TODO*

View file

@ -1,24 +0,0 @@
## Route conflicts
Route trees should not have multiple routes that match to a single (request) path. `router` checks the route tree at creation for conflicts and calls a registered `:conflicts` option callback with the found conflicts. Default implementation throws `ex-info` with a descriptive message.
```clj
(reitit/router
[["/ping"]
["/:user-id/orders"]
["/bulk/:bulk-id"]
["/public/*path"]
["/:version/status"]])
; CompilerException clojure.lang.ExceptionInfo: router contains conflicting routes:
;
; /:user-id/orders
; -> /public/*path
; -> /bulk/:bulk-id
;
; /bulk/:bulk-id
; -> /:version/status
;
; /public/*path
; -> /:version/status
;
```

View file

@ -1,55 +0,0 @@
# Route meta-data
Routes can have arbitrary meta-data. For nested routes, the meta-data is accumulated from root towards leafs using [meta-merge](https://github.com/weavejester/meta-merge).
A router based on nested route tree:
```clj
(def router
(reitit/router
["/api" {:interceptors [::api]}
["/ping" ::ping]
["/admin" {:roles #{:admin}}
["/users" ::users]
["/db" {:interceptors [::db]
:roles ^:replace #{:db-admin}}
["/:db" {:parameters {:db String}}
["/drop" ::drop-db]
["/stats" ::db-stats]]]]]))
```
Resolved route tree:
```clj
(reitit/routes router)
; [["/api/ping" {:interceptors [::api]
; :name ::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}]]
```
Path-based routing:
```clj
(reitit/match-by-path router "/api/admin/users")
; #Match{:template "/api/admin/users"
; :meta {:interceptors [::api]
; :roles #{:admin}
; :name ::users}
; :result nil
; :params {}
; :path "/api/admin/users"}
```
On match, route meta-data is returned and can interpreted by the application.
Routers also support meta-data compilation enabling things like fast [Ring](https://github.com/ring-clojure/ring) or [Pedestal](http://pedestal.io/) -style handlers. Compilation results are found under `:result` in the match. See [configuring routers](../configuring_routers.md) for details.

View file

@ -1,48 +0,0 @@
# Route Syntax
Routes are defined as vectors, which String path, optional (non-vector) route argument and optional child routes. Routes can be wrapped in vectors.
Simple route:
```clj
["/ping"]
```
Two routes:
```clj
[["/ping"]
["/pong"]]
```
Routes with meta-data:
```clj
[["/ping" ::ping]
["/pong" {:name ::pong}]]
```
Routes with path and catch-all parameters:
```clj
[["/users/:user-id"]
["/public/*path"]]
```
Nested routes with meta-data:
```clj
["/api"
["/admin" {:middleware [::admin]}
["/user" ::user]
["/db" ::db]
["/ping" ::ping]]
```
Same routes flattened:
```clj
[["/api/admin/user" {:middleware [::admin], :name ::user}
["/api/admin/db" {:middleware [::admin], :name ::db}
["/api/ping" ::ping]]
```

View file

@ -1,85 +0,0 @@
# Routers
For routing, a `Router` is needed. Reitit ships with several different router implementations: `:linear-router`, `:lookup-router` and `:mixed-router`, based on the awesome [Pedestal](https://github.com/pedestal/pedestal/tree/master/route) implementation.
`Router` is created with `reitit.core/router`, which takes routes and optional options map as arguments. The route tree gets expanded, optionally coerced and compiled. Actual `Router` implementation is selected automatically but can be defined with a `:router` option. `Router` support both path- and name-based lookups.
Creating a router:
```clj
(require '[reitit.core :as reitit])
(def router
(reitit/router
[["/api"
["/ping" ::ping]
["/user/:id" ::user]]]))
```
`:mixed-router` is created (both static & wild routes are found):
```clj
(reitit/router-name router)
; :mixed-router
```
The expanded routes:
```clj
(reitit/routes router)
; [["/api/ping" {:name :user/ping}]
; ["/api/user/:id" {:name :user/user}]]
```
Route names:
```clj
(reitit/route-names router)
; [:user/ping :user/user]
```
### Path-based routing
```clj
(reitit/match-by-path router "/hello")
; nil
(reitit/match-by-path router "/api/user/1")
; #Match{:template "/api/user/:id"
; :meta {:name :user/user}
; :path "/api/user/1"
; :result nil
; :params {:id "1"}}
```
### Name-based (reverse) routing
```clj
(reitit/match-by-name router ::user)
; #PartialMatch{:template "/api/user/:id",
; :meta {:name :user/user},
; :result nil,
; :params nil,
; :required #{:id}}
(reitit/partial-match? (reitit/match-by-name router ::user))
; true
```
Only a partial match. Let's provide the path-parameters:
```clj
(reitit/match-by-name router ::user {:id "1"})
; #Match{:template "/api/user/:id"
; :meta {:name :user/user}
; :path "/api/user/1"
; :result nil
; :params {:id "1"}}
```
There is also a exception throwing version:
```clj
(reitit/match-by-name! router ::user)
; ExceptionInfo missing path-params for route /api/user/:id: #{:id}
```

View file

@ -1,92 +0,0 @@
# Validating route-trees
Namespace `reitit.spec` contains [specs](https://clojure.org/about/spec) for routes, router and router options.
To enable spec-validation of `router` inputs & outputs at development time, one can do the following:
```clj
; add to dependencies:
; [expound "0.3.0"]
(require '[clojure.spec.test.alpha :as st])
(require '[expound.alpha :as expound])
(require '[clojure.spec.alpha :as s])
(require '[reitit.spec])
(st/instrument `reitit/router)
(set! s/*explain-out* expound/printer)
(reitit/router
["/api"
["/publuc"
["/ping"]
["pong"]]])
; -- Spec failed --------------------
;
; ["/api" ...]
; ^^^^^^
;
; should satisfy
;
; (clojure.spec.alpha/cat
; :path
; :reitit.spec/path
; :arg
; (clojure.spec.alpha/? :reitit.spec/arg)
; :childs
; (clojure.spec.alpha/* (clojure.spec.alpha/and :reitit.spec/raw-route)))
;
; -- Relevant specs -------
;
; :reitit.spec/raw-route:
; (clojure.spec.alpha/cat
; :path
; :reitit.spec/path
; :arg
; (clojure.spec.alpha/? :reitit.spec/arg)
; :childs
; (clojure.spec.alpha/* (clojure.spec.alpha/and :reitit.spec/raw-route)))
; :reitit.spec/raw-routes:
; (clojure.spec.alpha/or
; :route
; :reitit.spec/raw-route
; :routes
; (clojure.spec.alpha/coll-of :reitit.spec/raw-route :into []))
;
; -- Spec failed --------------------
;
; [... [... ... ["pong"]]]
; ^^^^^^
;
; should satisfy
;
; (fn [%] (clojure.string/starts-with? % "/"))
;
; -- Relevant specs -------
;
; :reitit.spec/path:
; (clojure.spec.alpha/and
; clojure.core/string?
; (clojure.core/fn [%] (clojure.string/starts-with? % "/")))
; :reitit.spec/raw-route:
; (clojure.spec.alpha/cat
; :path
; :reitit.spec/path
; :arg
; (clojure.spec.alpha/? :reitit.spec/arg)
; :childs
; (clojure.spec.alpha/* (clojure.spec.alpha/and :reitit.spec/raw-route)))
; :reitit.spec/raw-routes:
; (clojure.spec.alpha/or
; :route
; :reitit.spec/raw-route
; :routes
; (clojure.spec.alpha/coll-of :reitit.spec/raw-route :into []))
;
; -------------------------
; Detected 2 errors
```
### Validating meta-data
*TODO*