Merge pull request #66 from metosin/ReleasePolish

Updated docs
This commit is contained in:
Tommi Reiman 2018-02-11 21:35:15 +02:00 committed by GitHub
commit ae75021aac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 355 additions and 349 deletions

View file

@ -1,6 +1,6 @@
# reitit [![Build Status](https://travis-ci.org/metosin/reitit.svg?branch=master)](https://travis-ci.org/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). A fast data-driven router for Clojure(Script).
* Simple data-driven [route syntax](https://metosin.github.io/reitit/basics/route_syntax.html) * 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) * Route [conflict resolution](https://metosin.github.io/reitit/basics/route_conflicts.html)
@ -12,9 +12,9 @@ A friendly data-driven router for Clojure(Script).
* [Fast](https://metosin.github.io/reitit/performance.html) * [Fast](https://metosin.github.io/reitit/performance.html)
The following higher-level routers are also available as separate modules: The following higher-level routers are also available as separate modules:
* [ring-router](https://metosin.github.io/reitit/ring/ring.html) with [data-driven middleware](https://metosin.github.io/reitit/ring/data_driven_middleware.html) * [`ring-router`](https://metosin.github.io/reitit/ring/ring.html) with [data-driven middleware](https://metosin.github.io/reitit/ring/data_driven_middleware.html)
* http-router with Pedestal-style Interceptors (WIP) * `http-router` with enchanced Pedestal-style Interceptors (WIP)
* cljs-router with Keechma-style Controllers (WIP) * `frontend-router` with Keechma-style Controllers (WIP)
See the [full documentation](https://metosin.github.io/reitit/) for details. See the [full documentation](https://metosin.github.io/reitit/) for details.
@ -29,7 +29,7 @@ All bundled:
Optionally, the parts can be required separately: Optionally, the parts can be required separately:
```clj ```clj
[metosin/reitit-core "0.1.0-SNAPSHOT"] ; just the router [metosin/reitit-core "0.1.0-SNAPSHOT"] ; routing core
[metosin/reitit-ring "0.1.0-SNAPSHOT"] ; ring-router [metosin/reitit-ring "0.1.0-SNAPSHOT"] ; ring-router
[metosin/reitit-spec "0.1.0-SNAPSHOT"] ; spec coercion [metosin/reitit-spec "0.1.0-SNAPSHOT"] ; spec coercion
[metosin/reitit-schema "0.1.0-SNAPSHOT"] ; schema coercion [metosin/reitit-schema "0.1.0-SNAPSHOT"] ; schema coercion
@ -130,7 +130,7 @@ Roadmap is mostly written in [issues](https://github.com/metosin/reitit/issues).
[Ataraxy](https://github.com/weavejester/ataraxy), [Bide](https://github.com/funcool/bide), [Bidi](https://github.com/juxt/bidi), [Compojure](https://github.com/weavejester/compojure) and [Ataraxy](https://github.com/weavejester/ataraxy), [Bide](https://github.com/funcool/bide), [Bidi](https://github.com/juxt/bidi), [Compojure](https://github.com/weavejester/compojure) and
[Pedestal](https://github.com/pedestal/pedestal/tree/master/route). [Pedestal](https://github.com/pedestal/pedestal/tree/master/route).
* [Compojure-api](https://github.com/metosin/compojure-api), [Kekkonen](https://github.com/metosin/kekkonen), [Ring-swagger](https://github.com/metosin/ring-swagger) and [Yada](https://github.com/juxt/yada) and for ideas, coercion & stuff. * [Compojure-api](https://github.com/metosin/compojure-api), [Kekkonen](https://github.com/metosin/kekkonen), [Ring-swagger](https://github.com/metosin/ring-swagger) and [Yada](https://github.com/juxt/yada) and for ideas, coercion & stuff.
* [Schema](https://github.com/plumatic/schema) and [clojure.spec](https://clojure.org/about/spec) for the formal validation. * [Schema](https://github.com/plumatic/schema) and [clojure.spec](https://clojure.org/about/spec) for the validation part.
## Development instructions ## Development instructions

View file

@ -1,6 +1,6 @@
# Introduction # Introduction
[Reitit](https://github.com/metosin/reitit) is a small Clojure(Script) library for data-driven routing. [Reitit](https://github.com/metosin/reitit) is a fast data-driven router for Clojure(Script).
* Simple data-driven [route syntax](./basics/route_syntax.md) * Simple data-driven [route syntax](./basics/route_syntax.md)
* [Route conflict resolution](./basics/route_conflicts.md) * [Route conflict resolution](./basics/route_conflicts.md)
@ -12,9 +12,9 @@
* [Fast](performance.md) * [Fast](performance.md)
The following higher-level routers are also available as separate modules: The following higher-level routers are also available as separate modules:
* [ring-router](./ring/ring.md) with [data-driven middleware](./ring/data_driven_middleware.md) * [`ring-router`](./ring/ring.md) with [data-driven middleware](./ring/data_driven_middleware.md)
* http-router with Pedestal-style Interceptors (WIP) * `http-router` with enchanced Pedestal-style Interceptors (WIP)
* cljs-router with Keechma-style Controllers (WIP) * `frontend-router` with Keechma-style Controllers (WIP)
To use Reitit, add the following dependecy to your project: To use Reitit, add the following dependecy to your project:
@ -25,7 +25,7 @@ To use Reitit, add the following dependecy to your project:
Optionally, the parts can be required separately: Optionally, the parts can be required separately:
```clj ```clj
[metosin/reitit-core "0.1.0-SNAPSHOT"] ; just the router [metosin/reitit-core "0.1.0-SNAPSHOT"] ; routing core
[metosin/reitit-ring "0.1.0-SNAPSHOT"] ; ring-router [metosin/reitit-ring "0.1.0-SNAPSHOT"] ; ring-router
[metosin/reitit-spec "0.1.0-SNAPSHOT"] ; spec coercion [metosin/reitit-spec "0.1.0-SNAPSHOT"] ; spec coercion
[metosin/reitit-schema "0.1.0-SNAPSHOT"] ; schema coercion [metosin/reitit-schema "0.1.0-SNAPSHOT"] ; schema coercion

View file

@ -25,7 +25,7 @@
* [Pluggable Coercion](ring/coercion.md) * [Pluggable Coercion](ring/coercion.md)
* [Route Data Validation](ring/route_data_validation.md) * [Route Data Validation](ring/route_data_validation.md)
* [Compiling Middleware](ring/compiling_middleware.md) * [Compiling Middleware](ring/compiling_middleware.md)
* [Interceptors](interceptors.md)
* [Swagger & Openapi](openapi.md)
* [Performance](performance.md) * [Performance](performance.md)
* [Interceptors (WIP)](interceptors.md)
* [Swagger & Openapi (WIP)](openapi.md)
* [FAQ](faq.md) * [FAQ](faq.md)

View file

@ -4,11 +4,11 @@ Reitit ships with several different implementations for the `Router` protocol, o
| router | description | | 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. Slow, but works with all route trees. | `:linear-router` | Matches the routes one-by-one starting from the top until a match is found. Slow, but works with all route trees.
| `:lookup-router` | Fast router, uses hash-lookup to resolve the route. Valid if no paths have path or catch-all parameters and there are no [Route conflicts](../basics/route_conflicts.md).
| `:mixed-router` | Creates internally a `:segment-router` for wildcard routes and a `:lookup-router` or `:single-static-path-router` for static routes. Valid only if there are no [Route conflicts](../basics/route_conflicts.md).
| `:single-static-path-router` | Super fast router: sting-matches the route. Valid only if there is one static route.
| `:segment-router` | Router that creates a optimized [search trie](https://en.wikipedia.org/wiki/Trie) out of an route table. Much faster than `:linear-router` for wildcard routes. Valid only if there are no [Route conflicts](../basics/route_conflicts.md). | `:segment-router` | Router that creates a optimized [search trie](https://en.wikipedia.org/wiki/Trie) out of an route table. Much faster than `:linear-router` for wildcard routes. Valid only if there are no [Route conflicts](../basics/route_conflicts.md).
| `:lookup-router` | Fast router, uses hash-lookup to resolve the route. Valid if no paths have path or catch-all parameters and there are no [Route conflicts](../basics/route_conflicts.md).
| `:single-static-path-router` | Super fast router: string-matches a route. Valid only if there is one static route.
| `:mixed-router` | Contains two routers: `:segment-router` for wildcard routes and a `:lookup-router` or `:single-static-path-router` for static routes. Valid only if there are no [Route conflicts](../basics/route_conflicts.md).
The router name can be asked from the router: The router name can be asked from the router:
@ -23,3 +23,18 @@ The router name can be asked from the router:
(r/router-name router) (r/router-name router)
; :mixed-router ; :mixed-router
``` ```
Overriding the router implementation:
```clj
(require '[reitit.core :as r])
(def router
(r/router
[["/ping" ::ping]
["/api/:users" ::users]]
{:router r/linear-router}))
(r/router-name router)
; :linear-router
```

View file

@ -9,9 +9,9 @@ Given a router:
(def router (def router
(r/router (r/router
[["/api" ["/api"
["/ping" ::ping] ["/ping" ::ping]
["/user/:id" ::user]]])) ["/user/:id" ::user]]))
``` ```
Listing all route names: Listing all route names:

View file

@ -4,7 +4,7 @@ Path-based routing is done using the `reitit.core/match-by-path` function. It ta
* `nil`, no match * `nil`, no match
* `PartialMatch`, path matched, missing path-parameters (only in reverse-routing) * `PartialMatch`, path matched, missing path-parameters (only in reverse-routing)
* `Match`, exact match * `Match`, an exact match
Given a router: Given a router:
@ -13,9 +13,9 @@ Given a router:
(def router (def router
(r/router (r/router
[["/api" ["/api"
["/ping" ::ping] ["/ping" ::ping]
["/user/:id" ::user]]])) ["/user/:id" ::user]]))
``` ```
No match returns `nil`: No match returns `nil`:

View file

@ -1,8 +1,8 @@
# Route Data # Route Data
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. Together with `clojure.spec` -validation this enables co-existence of both [adaptive and principled](https://youtu.be/x9pxbnFC4aQ?t=1907) components. Route data is the core feature of reitit. Routes can have any map-like data attachted to them. This data is interpeted either by the client application or the `Router` via it's `:coerce` and `:compile` hooks. Route data format can be defined and validated with `clojure.spec` enabling a architecture 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. 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 ```clj
(require '[reitit.core :as r]) (require '[reitit.core :as r])
@ -23,14 +23,18 @@ The expanded route data can be retrieved from a router with `routes` and is retu
; ["/pong" {:handler identity]} ; ["/pong" {:handler identity]}
; ["/users" {:get {:roles #{:admin} ; ["/users" {:get {:roles #{:admin}
; :handler identity}}]] ; :handler identity}}]]
```
```clj
(r/match-by-path router "/ping") (r/match-by-path router "/ping")
; #Match{:template "/ping" ; #Match{:template "/ping"
; :data {:name :user/ping} ; :data {:name :user/ping}
; :result nil ; :result nil
; :path-params {} ; :path-params {}
; :path "/ping"} ; :path "/ping"}
```
```clj
(r/match-by-name router ::ping) (r/match-by-name router ::ping)
; #Match{:template "/ping" ; #Match{:template "/ping"
; :data {:name :user/ping} ; :data {:name :user/ping}
@ -72,26 +76,20 @@ Resolved route tree:
## Expansion ## 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). 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.
Naive example to add direct support for `java.io.File` route argument:
```clj ```clj
(def router (extend-type java.io.File
(r/router r/Expand
[["/ping" ::ping] (expand [file options]
["/pong" identity] (r/expand
["/users" {:get {:roles #{:admin} #(slurp file)
:handler identity}}]])) options)))
(r/routes router) (r/router
; [["/ping" {:name :user/ping}] ["/" (java.io.File. "index.html")])
; ["/pong" {:handler identity]}
; ["/users" {:get {:roles #{:admin}
; :handler identity}}]]
(r/match-by-path router "/ping")
; #Match{:template "/ping"
; :data {:name :user/ping}
; :result nil
; :path-params {}
; :path "/ping"}
``` ```
See [router options](../advanced/configuring_routers.md) for all available options.

View file

@ -1,10 +1,10 @@
# Route Data Validation # Route Data Validation
Route data can be anything, so it's easy to do mistakes. Accidentally using a `:role` key instead of `:roles` might render the whole routing app without any authorization in place. Route data can be anything, so it's easy to do go wrong. Accidentally adding a `:role` key instead of `:roles` might hinder the whole routing app without any authorization in place.
To fail fast, we could use the custom `:coerce` and `:compile` hooks to apply data validation and throw exceptions on first sighted problem. To fail fast, we could use the custom `:coerce` and `:compile` hooks to apply data validation and throw exceptions on first sighted problem.
But there is a better way. Router also has a `:validation` hook to validate the whole route tree after it's successfuly compiled. It expects a 2-arity function `routes opts => ()` that can side-effect in case of validation errors. But there is a better way. Router has a `:validation` hook to validate the whole route tree after it's successfuly compiled. It expects a 2-arity function `routes opts => ()` that can side-effect in case of validation errors.
## clojure.spec ## clojure.spec
@ -104,26 +104,26 @@ Explicitly requiring a `::roles` key in a route data:
::rs/explain e/expound-str ::rs/explain e/expound-str
:validate rs/validate-spec!}) :validate rs/validate-spec!})
; CompilerException clojure.lang.ExceptionInfo: Invalid route data: ; CompilerException clojure.lang.ExceptionInfo: Invalid route data:
; ;
; -- On route ----------------------- ; -- On route -----------------------
; ;
; "/api" ; "/api"
; ;
; -- Spec failed -------------------- ; -- Spec failed --------------------
; ;
; {:handler ; {:handler
; #object[clojure.core$identity 0x15b59b0e "clojure.core$identity@15b59b0e"]} ; #object[clojure.core$identity 0x15b59b0e "clojure.core$identity@15b59b0e"]}
; ;
; should contain key: `:user/roles` ; should contain key: `:user/roles`
; ;
; | key | spec | ; | key | spec |
; |-------------+----------------------------------------| ; |-------------+----------------------------------------|
; | :user/roles | (coll-of #{:admin :manager} :into #{}) | ; | :user/roles | (coll-of #{:admin :manager} :into #{}) |
; ;
; ;
; ;
; ------------------------- ; -------------------------
; Detected 1 error ; Detected 1 error
; ;
; {:problems (#reitit.spec.Problem{:path "/api", :scope nil, :data {:handler #object[clojure.core$identity 0x15b59b0e "clojure.core$identity@15b59b0e"]}, :spec #object[clojure.spec.alpha$merge_spec_impl$reify__2124 0x7461744b "clojure.spec.alpha$merge_spec_impl$reify__2124@7461744b"], :problems #:clojure.spec.alpha{:problems ({:path [], :pred (clojure.core/fn [%] (clojure.core/contains? % :user/roles)), :val {:handler #object[clojure.core$identity 0x15b59b0e "clojure.core$identity@15b59b0e"]}, :via [], :in []}), :spec #object[clojure.spec.alpha$merge_spec_impl$reify__2124 0x7461744b "clojure.spec.alpha$merge_spec_impl$reify__2124@7461744b"], :value {:handler #object[clojure.core$identity 0x15b59b0e "clojure.core$identity@15b59b0e"]}}})}, compiling:(/Users/tommi/projects/metosin/reitit/test/cljc/reitit/spec_test.cljc:151:1) ; {:problems (#reitit.spec.Problem{:path "/api", :scope nil, :data {:handler #object[clojure.core$identity 0x15b59b0e "clojure.core$identity@15b59b0e"]}, :spec #object[clojure.spec.alpha$merge_spec_impl$reify__2124 0x7461744b "clojure.spec.alpha$merge_spec_impl$reify__2124@7461744b"], :problems #:clojure.spec.alpha{:problems ({:path [], :pred (clojure.core/fn [%] (clojure.core/contains? % :user/roles)), :val {:handler #object[clojure.core$identity 0x15b59b0e "clojure.core$identity@15b59b0e"]}, :via [], :in []}), :spec #object[clojure.spec.alpha$merge_spec_impl$reify__2124 0x7461744b "clojure.spec.alpha$merge_spec_impl$reify__2124@7461744b"], :value {:handler #object[clojure.core$identity 0x15b59b0e "clojure.core$identity@15b59b0e"]}}})}, compiling:(/Users/tommi/projects/metosin/reitit/test/cljc/reitit/spec_test.cljc:151:1)
``` ```

View file

@ -60,29 +60,27 @@ Same routes flattened:
``` ```
### Generating routes ### Generating routes
As routes are just data, it's easy to create them programmatically:
Routes are just data, so it's easy to create them programmatically:
```clj ```clj
(defn cqrs-routes [actions dev-mode?] (defn cqrs-routes [actions]
["/api" {:interceptors [::api ::db]} ["/api" {:interceptors [::api ::db]}
(for [[type interceptor] actions (for [[type interceptor] actions
:let [path (str "/" (name interceptor)) :let [path (str "/" (name interceptor))
method (condp = type method (condp = type
:query :get :query :get
:command :post)]] :command :post)]]
[path {method {:interceptors [interceptor]}}]) [path {method {:interceptors [interceptor]}}])])
(if dev-mode? ["/dev-tools" ::dev-tools])])
``` ```
```clj ```clj
(cqrs-routes (cqrs-routes
[[:query 'get-user] [[:query 'get-user]
[:command 'add-user] [:command 'add-user]
[:command 'add-order]] [:command 'add-order]])
false)
; ["/api" {:interceptors [::api ::db]} ; ["/api" {:interceptors [::api ::db]}
; (["/get-user" {:get {:interceptors [get-user]}}] ; (["/get-user" {:get {:interceptors [get-user]}}]
; ["/add-user" {:post {:interceptors [add-user]}}] ; ["/add-user" {:post {:interceptors [add-user]}}]
; ["/add-order" {:post {:interceptors [add-order]}}]) ; ["/add-order" {:post {:interceptors [add-order]}}])]
; nil]
``` ```

View file

@ -1,6 +1,6 @@
# Router # Router
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. Routes are just data and to do 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.
The `Router` protocol: The `Router` protocol:
@ -21,9 +21,9 @@ Creating a router:
(def router (def router
(r/router (r/router
[["/api" ["/api"
["/ping" ::ping] ["/ping" ::ping]
["/user/:id" ::user]]])) ["/user/:id" ::user]]))
``` ```
Name of the created router: Name of the created router:
@ -46,5 +46,5 @@ When router is created, the following steps are done:
* route tree is flattened * route tree is flattened
* route arguments are expanded (via `reitit.core/Expand` protocol) and optionally coerced * route arguments are expanded (via `reitit.core/Expand` protocol) and optionally coerced
* [route conflicts](advanced/route_conflicts.md) are resolved * [route conflicts](advanced/route_conflicts.md) are resolved
* route tree is compiled
* actual [router implementation](../advanced/different_routers.md) is selected and created * actual [router implementation](../advanced/different_routers.md) is selected and created
* optionally route data gets compiled

View file

@ -2,7 +2,7 @@
Coercion is a process of transforming parameters (and responses) from one format into another. Reitit separates routing and coercion into two separate steps. Coercion is a process of transforming parameters (and responses) from one format into another. Reitit separates routing and coercion into two separate steps.
By default, all wildcard and catch-all parameters are parsed as Strings: By default, all wildcard and catch-all parameters are parsed into strings:
```clj ```clj
(require '[reitit.core :as r]) (require '[reitit.core :as r])
@ -12,7 +12,7 @@ By default, all wildcard and catch-all parameters are parsed as Strings:
["/:company/users/:user-id" ::user-view])) ["/:company/users/:user-id" ::user-view]))
``` ```
Match with the parsed `:params` as Strings: Match with the parsed `:path-params` as strings:
```clj ```clj
(r/match-by-path r "/metosin/users/123") (r/match-by-path r "/metosin/users/123")
@ -73,13 +73,13 @@ A Match:
; :path "/metosin/users/123"} ; :path "/metosin/users/123"}
``` ```
Coercion was not applied. Why? In Reitit, routing and coercion are separate processes and we haven't applied the coercion yet. We need to apply it ourselves after the successfull routing. Coercion was not applied. Why? In Reitit, routing and coercion are separate processes and we have done just the routing part. We need to apply coercion after the successful routing.
But now we should have enough data on the match to apply the coercion. But now we should have enough data on the match to apply the coercion.
## Compiling coercers ## Compiling coercers
Before the actual coercion, we need to compile the coercers against the route data. Compiled coercers yield much better performance and the manual step of adding a coercion compiler makes things explicit and non-magical. Before the actual coercion, we ~~should~~ need to compile the coercers against the route data. Compiled coercers yield much better performance and the manual step of adding a coercion compiler makes things explicit and non-magical.
Compiling can be done via a Middleware, Interceptor or a Router. We apply it now at router-level, effecting all routes (with `:parameters` and `:coercion` defined). Compiling can be done via a Middleware, Interceptor or a Router. We apply it now at router-level, effecting all routes (with `:parameters` and `:coercion` defined).
@ -176,9 +176,3 @@ Here's an full example for doing routing and coercion with Reitit and Schema:
## Ring Coercion ## Ring Coercion
For a full-blown http-coercion, see the [ring coercion](../ring/coercion.md). For a full-blown http-coercion, see the [ring coercion](../ring/coercion.md).
## Thanks to
* [compojure-api](https://clojars.org/metosin/compojure-api) for the initial `Coercion` protocol
* [schema](https://github.com/plumatic/schema) and [schema-tools](https://github.com/metosin/schema-tools) for Schema Coercion
* [spec-tools](https://github.com/metosin/spec-tools) for Spec Coercion

View file

@ -1,6 +1,142 @@
# Frequently Asked Questions # Frequently Asked Questions
* [Why yet another routing library?](#why-yet-another-routing-library)
* [How can I contribute?](#how-can-i-contribute)
* [How does Reitit differ from Bidi?](#how-does-reitit-differ-from-bidi)
* [How does Reitit differ from Pedestal?](#how-does-reitit-differ-from-pedestal)
* [How does Reitit differ from Compojure?](#how-does-reitit-differ-from-compojure)
### Why yet another routing library? ### Why yet another routing library?
There are many great routing libs for Clojure, but we felt that none was perfect. We picked Routing and dispatching is in the core of most business apps, so we should have a great library to for it. There are already many good routing libs for Clojure, but we felt none was perfect. So, we took best parts of existing libs and added features that were missing: first-class composable route data, full route conflict resolution and pluggable coercion. Goal was to make a data-driven library that works, is fun to use and is really, really fast.
best parts of existing libs, added things that were missing (like first-class route data, spec-coercion and full route conflict resolution) trying to make a library it both both fun to use and really, really fast.
### How can I contribute?
You can join [#reitit](https://clojurians.slack.com/messages/reitit/) channel in [Clojurians slack](http://clojurians.net/) to discuss things. Known roadmap is mostly written in [issues](https://github.com/metosin/reitit/issues), and many issues are marked already with "Help wanted".
### How does Reitit differ from Bidi?
[Bidi](https://github.com/juxt/bidi) is an great and proven library for ClojureScript and we have been using it in many of our frontend projects. Both Reitit and Bidi are data-driven, bi-directional and work with both Clojure & ClojureScript. Here are the main differences:
#### Route syntax
* Bidi supports multiple representations for route syntax, Reitit supports just one (simple) syntax.
* Bidi uses special (Clojure) syntax for route patterns while Reitit separates (human-readable) paths strings from route data - still exposing the machine-readable syntax for extensions.
Bidi:
```clj
(def routes
["/" [["auth/login" :auth/login]
[["auth/recovery/token/" :token] :auth/recovery]
["workspace/" [[[:project-uuid "/" :page-uuid] :workspace/page]]]]])
```
Reitit:
```clj
(def routes
[["/auth/login" :auth/login]
["/auth/recovery/token/:token" :auth/recovery]
["/workspace/:project-uuid/:page-uuid" :workspace/page]])
```
#### Features
* Bidi has extra features like route guards
* Reitit ships with composable route data, specs, full route conflict resolution and pluggable coercion.
#### Performance
* Bidi is not optimized for speed and thus, Reitit is [orders of magnitude faster](performance.md) than Bidi. From Bidi source:
```clj
;; Route compilation was only marginally effective and hard to
;; debug. When bidi matching takes in the order of 30 micro-seconds,
;; this is good enough in relation to the time taken to process the
;; overall request.
```
### How does Reitit differ from Pedestal?
[Pedestal](http://pedestal.io/) is an great and proven library and has had great influence in Reitit. Both Reitit and Pedestal are data-driven and provide bi-directional routing and fast. Here are the main differences:
#### ClojureScript
* Pedestal targets only Clojure, while Reitit works also with ClojureScript.
#### Route syntax
* Pedestal supports multiple representations for route syntax: terse, table and verbose. Reitit provides only one representation.
* Pedestal supports both maps or keyword-arguments in route data, in Reitit, it's all maps.
Pedestal:
```clj
["/api/ping" :get identity :route-name ::ping]
```
Reitit:
```clj
["/api/ping" {:get identity, :name ::ping}]
```
#### Features
* Pedestal supports route guards
* Pedestal supports interceptors (`reitit-http` module will support them too).
* Reitit ships with composable route data, specs, full route conflict resolution and pluggable coercion.
* In Pedestal, different routers [behave differently](https://github.com/pedestal/pedestal/issues/532), in Reitit, all work the same.
#### Performance
Reitit routing was originally based on Pedestal Routing an thus they same similar performance. For routing trees with both static and wildcard routes, Reitit is much faster thanks to it's `mixed-router` algorithm.
### How does Reitit differ from Compojure?
[Compojure](https://github.com/weavejester/compojure) is the most used routing library in Clojure. It's proven and awesome.
#### ClojureScript
* Compojure targets only Clojure, while Reitit works also with ClojureScript.
#### Route syntax
* Compojure uses routing functions and macros while reitit is all data
* Compojure allows easy destructuring of route params on mid-path
* Applying middleware for sub-paths is hacky on Compojure, `reitit-ring` resolves this with data-driven middleware
Compojure:
```clj
(defroutes routes
(wrap-routes
(context "/api" []
(GET "/users/:id" [id :<< as-int]
(ok (get-user id)))
(POST "/pizza" []
(wrap-log post-pizza-handler)))
wrap-api :secure))
```
`reitit-ring` with `reitit-spec` module:
```clj
(def routes
["/api" {:middleware [[wrap-api :secure]]}
["/users/:id" {:get {:parameters {:path {:id int?}}}
:handler (fn [{:keys [parameters]}]
(ok (get-user (-> parameters :body :id))))}
["/pizza" {:post {:middleware [wrap-log]
:handler post-pizza-handler}]]])
```
#### Features
* Dynamic routing is trivial in Compojure, with reitit, some trickery is needed
* Reitit ships with composable route data, specs, full route conflict resolution and pluggable coercion.
#### Performance
Reitit is [orders of magnitude faster](performance.md) than Compojure.

View file

@ -1,12 +1,16 @@
# Interceptors # Interceptors (WIP)
Reitit also supports [Pedestal](pedestal.io)-style [interceptors](http://pedestal.io/reference/interceptors) via `reitit.interceptor` package. Reitit has also support for [Pedestal](pedestal.io)-style [interceptors](http://pedestal.io/reference/interceptors) via `reitit.interceptor` package. Currently, there is no interceptor interpreter shipped, just a way to compose and manage the interceptor chains.
Full support is WIP at the moment: Plan is to have a full-featured `reitit-http` module with same features as the `reitit-ring` - enchanced interceptor maps & interceptor compilations. Stay tuned.
* separate module (or library?) for interceptor interpreters ### TODO
* figure out how to make a truly portable Interceptor definitions, e.g. Pedestal has namespaced keys for context errors, queues etc.
* finalize `reitit-http` module as an alternative to `reitit-ring` * Figure out how to make a truly portable Interceptor definitions, e.g. Pedestal has namespaced keys for context errors, queues etc.
* Separate modules for interceptor interpreters (including cljs)
* Finalize `reitit-http` module as an alternative to `reitit-ring`
### Example
Current `reitit-http` draft (with data-specs): Current `reitit-http` draft (with data-specs):

View file

@ -1,11 +1,59 @@
# Swagger & OpenAPI # Swagger & OpenAPI (WIP)
Goal is to support both [Swagger](https://swagger.io/) & [OpenAPI](https://www.openapis.org/) specifications for route documentation. Goal is to support both [Swagger](https://swagger.io/) & [OpenAPI](https://www.openapis.org/) for route documentation. Documentation is extracted from existing coercion definitions `:parameters`, `:responses` and from a set of new doumentation keys.
Swagge-support draft works, but only for Clojure. Swagger-support draft works, but only for Clojure.
TODO: ### TODO
* [metosin/schema-tools#38](https://github.com/metosin/schema-tools/issues/38): extract Schema-swagger from [ring-swagger](https://github.com/metosin/ring-swagger) into [schema-tools](https://github.com/metosin/schema-tools) to support both Clojure & ClojureScript * [metosin/schema-tools#38](https://github.com/metosin/schema-tools/issues/38): extract Schema-swagger from [ring-swagger](https://github.com/metosin/ring-swagger) into [schema-tools](https://github.com/metosin/schema-tools) to support both Clojure & ClojureScript
* separate modules for the swagger2 & openapi * separate modules for the swagger2 & openapi
* [metosin/spec-tools#105](https://github.com/metosin/spec-tools/issues/105): support OpenApi * [metosin/spec-tools#105](https://github.com/metosin/spec-tools/issues/105): support Openapi
### Example
Current `reitit-swagger` draft (with `reitit-ring` & data-specs):
```clj
(require '[reitit.ring :as ring])
(require '[reitit.ring.swagger :as swagger])
(require '[reitit.ring.coercion :as rrc])
(require '[reitit.coercion.spec :as spec])
(def app
(ring/ring-handler
(ring/router
["/api"
;; identify a swagger api
;; there can be several in a routing tree
{:swagger {:id :math}}
;; the (undocumented) swagger spec endpoint
["/swagger.json"
{:get {:no-doc true
:swagger {:info {:title "my-api"}}
:handler swagger/swagger-spec-handler}}]
["/minus"
{:get {:summary "minus"
:parameters {:query {:x int?, :y int?}}
:responses {200 {:body {:total int?}}}
:handler (fn [{{{:keys [x y]} :query} :parameters}]
{:status 200, :body {:total (- x y)}})}}]
["/plus"
{:get {:summary "plus"
:parameters {:query {:x int?, :y int?}}
:responses {200 {:body {:total int?}}}
:handler (fn [{{{:keys [x y]} :query} :parameters}]
{:status 200, :body {:total (+ x y)}})}}]]
{:data {:middleware [;; does not particiate in request processing
;; just defines specs for the extra keys
swagger/swagger-middleware
rrc/coerce-exceptions-middleware
rrc/coerce-request-middleware
rrc/coerce-response-middleware]
:coercion spec/coercion}})))
```

View file

@ -1,16 +1,16 @@
# Performance # Performance
There are many great routing libraries for Clojure(Script), but not many are optimized for perf. Reitit tries to be both great in features and be really fast. Originally the routing was adopted from [Pedestal](http://pedestal.io/) (which is known to be fast), but has been partially rewritten performance in mind. Hopefully some optimizations can be back-ported to Pedestal. Reitit tries to be both great in features and be really, really fast. Originally the routing was ported from [Pedestal](http://pedestal.io/), but has been mostly rewritten to get even better performance.
### Rationale ### Rationale
* Multiple routing algorithms, choose based on the route tree * Multiple routing algorithms, chosen based on the route tree
* Route flattening and re-ordering * Route flattening and re-ordering
* Managed mutability over Immutability * Managed mutability over immutability
* Precompute/compile as much as possible (matches, middleware, routes) * Precompute/compile as much as possible (matches, middleware, interceptors, routes)
* Use abstractions that enable JVM optimizations * Use abstractions that enable JVM optimizations
* Use small functions to enable JVM Inlining * Use small functions to enable JVM Inlining
* Protocols over Multimethods (or Maps) * Protocols over Multimethods
* Records over Maps * Records over Maps
* Always be measuring * Always be measuring
* Don't trust the (micro-)benchmarks * Don't trust the (micro-)benchmarks
@ -77,27 +77,27 @@ So, we need to test something more realistic.
To get better view on the real life routing performance, there is [test](https://github.com/metosin/reitit/blob/master/perf-test/clj/reitit/opensensors_perf_test.clj) of a mid-size rest(ish) http api with 50+ routes, having a lot of path parameters. The route definitions are pulled off from the [OpenSensors](https://opensensors.io/) swagger definitions. To get better view on the real life routing performance, there is [test](https://github.com/metosin/reitit/blob/master/perf-test/clj/reitit/opensensors_perf_test.clj) of a mid-size rest(ish) http api with 50+ routes, having a lot of path parameters. The route definitions are pulled off from the [OpenSensors](https://opensensors.io/) swagger definitions.
Thanks to the snappy [segment-tree](https://github.com/metosin/reitit/blob/master/modules/reitit-core/src/reitit/segment.cljc) algorithm, `reitit-ring` is fastest here. Pedestal is also fast with it's [prefix-tree](https://en.wikipedia.org/wiki/Radix_tree) implementation. Thanks to the snappy new [segment-tree](https://github.com/metosin/reitit/blob/master/modules/reitit-core/src/reitit/segment.cljc) algorithm, `reitit-ring` is fastest here. Pedestal is also fast with it's [prefix-tree](https://en.wikipedia.org/wiki/Radix_tree) implementation.
![Opensensors perf test](images/opensensors.png) ![Opensensors perf test](images/opensensors.png)
### CQRS apis ### CQRS apis
Another real-life [test scenario](https://github.com/metosin/reitit/blob/master/perf-test/clj/reitit/lupapiste_perf_test.clj) is a [CQRS](https://martinfowler.com/bliki/CQRS.html)-style route tree, where all the paths are static, e.g. `/api/command/add-order`. The route definitions are pulled out from [Lupapiste](https://github.com/lupapiste/lupapiste). The test consists of ~300 static routes (just the commands here, there would be ~200 queries too). Another real-life [test scenario](https://github.com/metosin/reitit/blob/master/perf-test/clj/reitit/lupapiste_perf_test.clj) is a [CQRS](https://martinfowler.com/bliki/CQRS.html)-style route tree, where all the paths are static, e.g. `/api/command/add-order`. The 300 route definitions are pulled out from [Lupapiste](https://github.com/lupapiste/lupapiste).
Again, both `reitit-ring` and Pedestal shine here, thanks to the fast lookup-routers. On average, they are **two** and on best case, **three orders of magnitude faster** than the other tested libs. Ataraxy failed this test on `Method code too large!` error. Both `reitit-ring` and Pedestal shine in this test, thanks to the fast lookup-routers. On average, they are **two** and on best case, **three orders of magnitude faster** than the other tested libs. Ataraxy failed this test on `Method code too large!` error.
![Opensensors perf test](images/lupapiste.png) ![Opensensors perf test](images/lupapiste.png)
**NOTE**: If there would be even one wildcard route in the route-tree, Pedestal would fallback from lookup-router to the prefix-tree router, yielding nearly constant, but an order of magnitude slower perf. Reitit instead fallbacks to `:mixed-router`, serving all the static routes with `:lookup-router`, just the wildcard route(s) with `:segment-tree`. So, the performance would not notably degrade. **NOTE**: in real life, there are usually always also wild-card routes present. In this case, Pedestal would fallback from lookup-router to the prefix-tree router, which is order of magnitude slower (30x in this test). Reitit would handle this nicely thanks to it's `:mixed-router`: all static routes would still be served with `:lookup-router`, just the wildcard routes with `:segment-tree`. The performance would not notably degrade.
### Why measure? ### Why measure?
The routing perf needs to be measured to get an internal baseline to optimize against. We also want to ensure that new features don't regress the performance. Perf tests should be run in a stable CI environment. Help welcome. The reitit routing perf is measured to get an internal baseline to optimize against. We also want to ensure that new features don't regress the performance. Perf tests should be run in a stable CI environment. Help welcome!
### Looking out of the box ### Looking out of the box
It might be interesting to compare reitit with the routers in other languages, like the [routers in Go](https://github.com/julienschmidt/go-http-routing-benchmark). A quick poke to [routers in Go](https://github.com/julienschmidt/go-http-routing-benchmark) indicates that the reitit is only few times slower than the fastest routers in Go. Which is really awesome (if true).
### Performance tips ### Performance tips

View file

@ -20,16 +20,20 @@ Reitit ships with the following coercion modules:
Coercion can be attached to route data under `:coercion` key. There can be multiple `Coercion` implementations within a single router, normal [scoping rules](../basics/route_data.html#nested-route-data) apply. Coercion can be attached to route data under `:coercion` key. There can be multiple `Coercion` implementations within a single router, normal [scoping rules](../basics/route_data.html#nested-route-data) apply.
# Defining parameters and responses ## Defining parameters and responses
Below is a ring route data defining [Plumatic Schema](https://github.com/plumatic/schema) coercion. It defines schemas for `:query`, `:body` and `:path` parameters and for a successful response `:body`. Parameters are defined in `:parameters` key and responses in `:responses`.
The coerced parameters can be read under `:parameters` key in the request. Below is an example with [Plumatic Schema](https://github.com/plumatic/schema). It defines input schemas for `:query`, `:body` and `:path` parameters and a schema for a successful response `:body`.
Handler can access the coerced parameters can be read under `:parameters` key in the request.
```clj ```clj
(require '[reitit.coercion.schema]) (require '[reitit.coercion.schema])
(require '[schema.core :as s]) (require '[schema.core :as s])
(def PositiveInt (s/constrained s/Int pos? 'PositiveInt))
(def plus-endpoint (def plus-endpoint
{:coercion reitit.coercion.schema/coercion {:coercion reitit.coercion.schema/coercion
:parameters {:query {:x s/Int} :parameters {:query {:x s/Int}
@ -48,9 +52,9 @@ The coerced parameters can be read under `:parameters` key in the request.
Defining a coercion for a route data doesn't do anything, as it's just data. We have to attach some code to apply the actual coercion. We can use the middleware from `reitit.ring.coercion`: Defining a coercion for a route data doesn't do anything, as it's just data. We have to attach some code to apply the actual coercion. We can use the middleware from `reitit.ring.coercion`:
* `coerce-request-middleware` for the parameter coercion * `coerce-request-middleware` to apply the parameter coercion
* `coerce-response-middleware` for the response coercion * `coerce-response-middleware` to apply the response coercion
* `coerce-exceptions-middleware` to turn coercion exceptions into pretty responses * `coerce-exceptions-middleware` to transform coercion exceptions into pretty responses
### Full example ### Full example
@ -133,7 +137,7 @@ Invalid response:
### Optimizations ### Optimizations
The coercion middleware are [compiled againts a route](compiling_middleware,md). In the compile step the actual coercer implementations are compiled for the defined models. Also, the mw doesn't mount itself if a route doesn't have `:coercion` and `:parameters` or `:responses` defined. The coercion middleware are [compiled againts a route](compiling_middleware,md). In the middleware compilation step the actual coercer implementations are constructed for the defined models. Also, the middleware doesn't mount itself if a route doesn't have `:coercion` and `:parameters` or `:responses` defined.
We can query the compiled middleware chain for the routes: We can query the compiled middleware chain for the routes:
@ -165,9 +169,3 @@ Has no mounted middleware:
(->> (mapv :name))) (->> (mapv :name)))
; [] ; []
``` ```
## Thanks to
* [compojure-api](https://clojars.org/metosin/compojure-api) for the initial `Coercion` protocol
* [ring-swagger](https://github.com/metosin/ring-swagger#more-complete-example) for the `:parameters` and `:responses` syntax.
* [schema](https://github.com/plumatic/schema) and [schema-tools](https://github.com/metosin/schema-tools) for Schema Coercion
* [spec-tools](https://github.com/metosin/spec-tools) for Spec Coercion

View file

@ -2,15 +2,15 @@
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. 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.
But, we can do much 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. It can also decide not to mount itself by returning `nil`. Why mount a `wrap-enforce-roles` middleware for a route if there are no roles required for it? But, we can do much better. As we know the exact route that middleware/interceptor is linked to, we can pass the (compiled) route information into the middleware at creation-time. It can do local reasoning: extract and transform relevant data just for it and pass the optimized data into the actual request-handler via a closure - yielding much faster runtime processing. Middleware can also decide not to mount itself by returning `nil`. 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) `:compile` key instead of the normal `:wrap`. `:compile` expects a function of `route-data router-opts => ?wrap`. To enable this we use [middleware records](data_driven_middleware.md) `:compile` key instead of the normal `:wrap`. `:compile` expects a function of `route-data router-opts => ?IntoMiddleware`.
To demonstrate the two approaches, below are response coercion middleware written as normal ring middleware function and as middleware record with `:compile`. To demonstrate the two approaches, below are response coercion middleware written as normal ring middleware function and as middleware record with `:compile`.
## Normal Middleware ## Normal Middleware
* Reads the compiled route information on every request. * Reads the compiled route information on every request. Everything is done at request-time.
```clj ```clj
(defn wrap-coerce-response (defn wrap-coerce-response
@ -44,9 +44,9 @@ To demonstrate the two approaches, below are response coercion middleware writte
## Compiled Middleware ## Compiled Middleware
* Route information is provided via a closure * Route information is provided at creation-time
* Pre-compiled coercers * Coercers are compiled at creation-time
* Mounts only if `:coercion` and `:responses` are defined for the route * Middleware mounts only if `:coercion` and `:responses` are defined for the route
* Also defines spec for the route data `:responses` for the [route data validation](route_data_validation.md). * Also defines spec for the route data `:responses` for the [route data validation](route_data_validation.md).
```clj ```clj
@ -69,4 +69,4 @@ To demonstrate the two approaches, below are response coercion middleware writte
(handler request #(respond (coercion/coerce-response coercers request %)) raise)))))))}) (handler request #(respond (coercion/coerce-response coercers request %)) raise)))))))})
``` ```
The latter has 50% less code, is easier to reason about and is much faster. It has 50% less code, it's much easier to reason about and is much faster.

View file

@ -5,13 +5,13 @@ Ring [defines middleware](https://github.com/ring-clojure/ring/wiki/Concepts#mid
Reitit defines middleware as data: Reitit defines middleware as data:
1. Middleware can be defined as first-class data entries 1. Middleware can be defined as first-class data entries
2. Middleware can be defined as a [duct-style](https://github.com/duct-framework/duct/wiki/Configuration) vector (of middleware) 2. Middleware can be mounted as a [duct-style](https://github.com/duct-framework/duct/wiki/Configuration) vector (of middleware)
4. Middleware can be optimized & [compiled](compiling_middleware.md) againt an endpoint 4. Middleware can be optimized & [compiled](compiling_middleware.md) againt an endpoint
3. Middleware chain can be transformed by the router 3. Middleware chain can be transformed by the router
## Middleware as data ## Middleware as data
All values in the `:middleware` vector in the route data are coerced into `reitit.middleware/Middleware` Records with using the `reitit.middleware/IntoMiddleware` Protocol. By default, functions, maps and `Middleware` records are allowed. All values in the `:middleware` vector in the route data are expanded into `reitit.middleware/Middleware` Records with using the `reitit.middleware/IntoMiddleware` Protocol. By default, functions, maps and `Middleware` records are allowed.
Records can have arbitrary keys, but the following keys have a special purpose: Records can have arbitrary keys, but the following keys have a special purpose:
@ -38,6 +38,15 @@ The following produce identical middleware runtime function.
(handler (update request ::acc (fnil conj []) id)))) (handler (update request ::acc (fnil conj []) id))))
``` ```
### Map
```clj
(def wrap3
{:name ::wrap3
:description "Middleware that does things."
:wrap wrap})
```
### Record ### Record
```clj ```clj
@ -50,15 +59,6 @@ The following produce identical middleware runtime function.
:wrap wrap})) :wrap wrap}))
``` ```
### Map
```clj
(def wrap3
{:name ::wrap3
:description "Middleware that does things."
:wrap wrap})
```
## Using Middleware ## Using Middleware
`:middleware` is merged to endpoints by the `router`. `:middleware` is merged to endpoints by the `router`.
@ -104,7 +104,7 @@ There is an extra option in ring-router (actually, in the undelaying middleware-
{::middleware/transform #(interleave % (repeat [wrap :debug]))}))) {::middleware/transform #(interleave % (repeat [wrap :debug]))})))
``` ```
``` ```clj
(app {:request-method :get, :uri "/api/ping"}) (app {:request-method :get, :uri "/api/ping"})
; {:status 200, :body [1 :debug 2 :debug 3 :debug :handler]} ; {:status 200, :body [1 :debug 2 :debug 3 :debug :handler]}
``` ```
@ -121,17 +121,15 @@ There is an extra option in ring-router (actually, in the undelaying middleware-
{::middleware/transform reverse)}))) {::middleware/transform reverse)})))
``` ```
``` ```clj
(app {:request-method :get, :uri "/api/ping"}) (app {:request-method :get, :uri "/api/ping"})
; {:status 200, :body [3 2 1 :handler]} ; {:status 200, :body [3 2 1 :handler]}
``` ```
## Roadmap for middleware ## Ideas for the future
Some things bubblin' under:
* Re-package all useful middleware into (optimized) data-driven Middleware * Re-package all useful middleware into (optimized) data-driven Middleware
* just package or a new community-repo with rehosting stuffm? * just package or a new community-repo with rehosting stuff?
* Support `Keyword` expansion into Middleware, enabling external Middleware Registries (duct/integrant/macchiato -style) * Support `Keyword` expansion into Middleware, enabling external Middleware Registries (duct/integrant/macchiato -style)
* Support Middleware dependency resolution with new keys `:requires` and `:provides`. Values are set of top-level keys of the request. e.g. * Support Middleware dependency resolution with new keys `:requires` and `:provides`. Values are set of top-level keys of the request. e.g.
* `InjectUserIntoRequestMiddleware` requires `#{:session}` and provides `#{:user}` * `InjectUserIntoRequestMiddleware` requires `#{:session}` and provides `#{:user}`

View file

@ -5,6 +5,7 @@
Example middleware to guard routes based on user roles: Example middleware to guard routes based on user roles:
```clj ```clj
(require '[reitit.ring :as ring])
(require '[clojure.set :as set]) (require '[clojure.set :as set])
(defn wrap-enforce-roles [handler] (defn wrap-enforce-roles [handler]
@ -50,3 +51,5 @@ Authorized access to guarded route:
(app {:request-method :get, :uri "/api/admin/ping", ::roles #{:admin}}) (app {:request-method :get, :uri "/api/admin/ping", ::roles #{:admin}})
; {:status 200, :body "ok"} ; {:status 200, :body "ok"}
``` ```
Dynamic extensions are nice, but we can do much better. See [data-driven middleware](data_driven_middleware.md) and [compiling routes](compiling_middleware.md).

View file

@ -1,6 +1,14 @@
# Ring Router # Ring Router
[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. [Ring](https://github.com/ring-clojure/ring) is a Clojure web applications library inspired by Python's WSGI and Ruby's Rack. By abstracting the details of HTTP into a simple, unified API, Ring allows web applications to be constructed of modular components that can be shared among a variety of applications, web servers, and web frameworks.
```clj
[metosin/reitit-ring "0.1.0-SNAPSHOT"]
```
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 uses a custom route compiler, creating a optimized data structure for handling route matches, with compiled middleware chain & handlers for all request methods. It also ensures that all routes have a `:handler` defined. `reitit.ring/ring-handler` is used to create a Ring handler out of ring-router.
### Example
Simple Ring app: Simple Ring app:
@ -21,7 +29,9 @@ Applying the handler:
```clj ```clj
(app {:request-method :get, :uri "/favicon.ico"}) (app {:request-method :get, :uri "/favicon.ico"})
; nil ; nil
```
```clj
(app {:request-method :get, :uri "/ping"}) (app {:request-method :get, :uri "/ping"})
; {:status 200, :body "ok"} ; {:status 200, :body "ok"}
``` ```
@ -37,7 +47,7 @@ The expanded routes shows the compilation results:
; :middleware []}}]] ; :middleware []}}]]
``` ```
Note that the compiled resuts as third element in the route vector. Note the compiled resuts as third element in the route vector.
# Request-method based routing # Request-method based routing
@ -92,10 +102,13 @@ App with nested middleware:
(def app (def app
(ring/ring-handler (ring/ring-handler
(ring/router (ring/router
;; a middleware function
["/api" {:middleware [#(wrap % :api)]} ["/api" {:middleware [#(wrap % :api)]}
["/ping" handler] ["/ping" handler]
;; a middleware vector at top level
["/admin" {:middleware [[wrap :admin]]} ["/admin" {:middleware [[wrap :admin]]}
["/db" {:middleware [[wrap :db]] ["/db" {:middleware [[wrap :db]]
;; a middleware vector at under a method
:delete {:middleware [[wrap :delete]] :delete {:middleware [[wrap :delete]]
:handler handler}}]]]))) :handler handler}}]]])))
``` ```

View file

@ -7,8 +7,6 @@ Ring route validation works [just like with core router](../basics/route_data_va
## Example ## Example
Let's build a ring app with with both explicit (via middleware) and implicit (fully-qualified keys) spec validation.
A simple app with spec-validation turned on: A simple app with spec-validation turned on:
```clj ```clj
@ -222,7 +220,7 @@ But fails if they are present and invalid:
### Pushing the data to the endpoints ### Pushing the data to the endpoints
Ability to define (and reuse) route-data in sub-paths is a powerful feature, but having data scattered all around might be harder to reason about. There is always an option to push all data to the endpoints. Ability to define (and reuse) route-data in mid-paths is a powerful feature, but having data defined all around might be harder to reason about. There is always an option to define all data at the endpoints.
```clj ```clj
(def app (def app

View file

@ -323,6 +323,8 @@
(def cqrs-routes (def cqrs-routes
(mapv (fn [command] [(str "/command/" (name command)) {:post handler :name command}]) commands)) (mapv (fn [command] [(str "/command/" (name command)) {:post handler :name command}]) commands))
cqrs-routes
(def cqrs-routes-pedestal (def cqrs-routes-pedestal
(map-tree/router (map-tree/router
(table/table-routes (table/table-routes
@ -332,7 +334,9 @@
["/command/" (into {} (mapv (fn [command] [(name command) command]) commands))]) ["/command/" (into {} (mapv (fn [command] [(name command) command]) commands))])
(def cqrs-routes-compojure (def cqrs-routes-compojure
(apply routes (map (fn [command] (compojure/ANY (str "/command/" (name command)) [] handler)) commands))) (apply
compojure/routes
(map (fn [command] (compojure/ANY (str "/command/" (name command)) [] handler)) commands)))
;; Method code too large! ;; Method code too large!
#_(def cqrs-routes-ataraxy #_(def cqrs-routes-ataraxy

File diff suppressed because one or more lines are too long