BREAKING: route :meta => :data

This commit is contained in:
Tommi Reiman 2017-11-18 12:47:16 +02:00
parent a246bf4a9e
commit 2dd91d91d6
23 changed files with 172 additions and 171 deletions

View file

@ -4,7 +4,7 @@ A friendly 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)
* First-class [route meta-data](https://metosin.github.io/reitit/basics/route_data.html) * First-class [route data](https://metosin.github.io/reitit/basics/route_data.html)
* Bi-directional routing * Bi-directional routing
* [Ring-router](https://metosin.github.io/reitit/ring.html) with data-driven [middleware](https://metosin.github.io/reitit/ring/compiling_middleware.html) * [Ring-router](https://metosin.github.io/reitit/ring.html) with data-driven [middleware](https://metosin.github.io/reitit/ring/compiling_middleware.html)
* [Pluggable coercion](https://metosin.github.io/reitit/ring/parameter_coercion.html) ([clojure.spec](https://clojure.org/about/spec)) * [Pluggable coercion](https://metosin.github.io/reitit/ring/parameter_coercion.html) ([clojure.spec](https://clojure.org/about/spec))
@ -41,14 +41,14 @@ Optionally, the parts can be required separately:
(r/match-by-path router "/api/ping") (r/match-by-path router "/api/ping")
; #Match{:template "/api/ping" ; #Match{:template "/api/ping"
; :meta {:name ::ping} ; :data {:name ::ping}
; :result nil ; :result nil
; :params {} ; :params {}
; :path "/api/ping"} ; :path "/api/ping"}
(r/match-by-name router ::order {:id 2}) (r/match-by-name router ::order {:id 2})
; #Match{:template "/api/orders/:id", ; #Match{:template "/api/orders/:id",
;       :meta {:name ::order}, ;       :data {:name ::order},
; :result nil, ; :result nil,
; :params {:id 2}, ; :params {:id 2},
; :path "/api/orders/2"} ; :path "/api/orders/2"}

View file

@ -4,7 +4,7 @@
* Simple data-driven [route syntax](./basics/route_syntax.md) * Simple data-driven [route syntax](./basics/route_syntax.md)
* [Route conflict resolution](./advanced/route_conflicts.md) * [Route conflict resolution](./advanced/route_conflicts.md)
* First-class [route meta-data](./basics/route_data.md) * First-class [route data](./basics/route_data.md)
* Bi-directional routing * Bi-directional routing
* [Pluggable coercion](./ring/parameter_coercion.md) ([clojure.spec](https://clojure.org/about/spec)) * [Pluggable coercion](./ring/parameter_coercion.md) ([clojure.spec](https://clojure.org/about/spec))
* supports both [Middleware](./ring/compiling_middleware.md) & Interceptors * supports both [Middleware](./ring/compiling_middleware.md) & Interceptors
@ -48,14 +48,14 @@ Routing:
(r/match-by-path router "/api/ping") (r/match-by-path router "/api/ping")
; #Match{:template "/api/ping" ; #Match{:template "/api/ping"
; :meta {:name ::ping} ; :data {:name ::ping}
; :result nil ; :result nil
; :params {} ; :params {}
; :path "/api/ping"} ; :path "/api/ping"}
(r/match-by-path router "/api/orders/1") (r/match-by-path router "/api/orders/1")
; #Match{:template "/api/orders/:id" ; #Match{:template "/api/orders/:id"
; :meta {:name ::order-by-id} ; :data {:name ::order-by-id}
; :result nil ; :result nil
; :params {:id "1"} ; :params {:id "1"}
; :path "/api/orders/1"} ; :path "/api/orders/1"}
@ -69,14 +69,14 @@ Reverse-routing:
(r/match-by-name router ::ping) (r/match-by-name router ::ping)
; #Match{:template "/api/ping" ; #Match{:template "/api/ping"
; :meta {:name ::ping} ; :data {:name ::ping}
; :result nil ; :result nil
; :params {} ; :params {}
; :path "/api/ping"} ; :path "/api/ping"}
(r/match-by-name router ::order-by-id) (r/match-by-name router ::order-by-id)
; #PartialMatch{:template "/api/orders/:id" ; #PartialMatch{:template "/api/orders/:id"
; :meta {:name :user/order-by-id} ; :data {:name :user/order-by-id}
; :result nil ; :result nil
; :params nil ; :params nil
; :required #{:id}} ; :required #{:id}}
@ -86,7 +86,7 @@ Reverse-routing:
(r/match-by-name router ::order-by-id {:id 2}) (r/match-by-name router ::order-by-id {:id 2})
; #Match{:template "/api/orders/:id", ; #Match{:template "/api/orders/:id",
; :meta {:name ::order-by-id}, ; :data {:name ::order-by-id},
; :result nil, ; :result nil,
; :params {:id 2}, ; :params {:id 2},
; :path "/api/orders/2"} ; :path "/api/orders/2"}
@ -134,7 +134,7 @@ Reverse-routing:
(-> app (ring/get-router) (r/match-by-name ::ping)) (-> app (ring/get-router) (r/match-by-name ::ping))
; #Match{:template "/api/ping" ; #Match{:template "/api/ping"
; :meta {:middleware [[#object[user$wrap] :api]] ; :data {:middleware [[#object[user$wrap] :api]]
; :get {:handler #object[user$handler]} ; :get {:handler #object[user$handler]}
; :name ::ping} ; :name ::ping}
; :result #Methods{...} ; :result #Methods{...}

View file

@ -1,13 +1,13 @@
## Configuring Routers ## Configuring Routers
Routers can be configured via options. Options allow things like [`clojure.spec`](https://clojure.org/about/spec) validation for meta-data and fast, compiled handlers. The following options are available for the `reitit.core/router`: Routers can be configured via options. Options allow things like [`clojure.spec`](https://clojure.org/about/spec) validation for route data and fast, compiled handlers. The following options are available for the `reitit.core/router`:
| key | description | | key | description |
| -------------|-------------| | -------------|-------------|
| `:path` | Base-path for routes | `:path` | Base-path for routes
| `:routes` | Initial resolved routes (default `[]`) | `:routes` | Initial resolved routes (default `[]`)
| `:meta` | Initial expanded route-meta vector (default `[]`) | `:data` | Initial route data (default `{}`)
| `:expand` | Function of `arg opts => meta` to expand route arg to route meta-data (default `reitit.core/expand`) | `:expand` | Function of `arg opts => data` to expand route arg to route data (default `reitit.core/expand`)
| `:coerce` | Function of `route opts => route` to coerce resolved route, can throw or return `nil` | `:coerce` | Function of `route opts => route` to coerce resolved route, can throw or return `nil`
| `:compile` | Function of `route opts => result` to compile a route handler | `:compile` | Function of `route opts => result` to compile a route handler
| `:conflicts` | Function of `{route #{route}} => side-effect` to handle conflicting routes (default `reitit.core/throw-on-conflicts!`) | `:conflicts` | Function of `{route #{route}} => side-effect` to handle conflicting routes (default `reitit.core/throw-on-conflicts!`)

View file

@ -33,7 +33,7 @@ Matching a route:
```clj ```clj
(r/match-by-name router ::ping) (r/match-by-name router ::ping)
; #Match{:template "/api/ping" ; #Match{:template "/api/ping"
; :meta {:name :user/ping} ; :data {:name :user/ping}
; :result nil ; :result nil
; :params {} ; :params {}
; :path "/api/ping"} ; :path "/api/ping"}
@ -44,7 +44,7 @@ If not all path-parameters are set, a `PartialMatch` is returned:
```clj ```clj
(r/match-by-name router ::user) (r/match-by-name router ::user)
; #PartialMatch{:template "/api/user/:id", ; #PartialMatch{:template "/api/user/:id",
; :meta {:name :user/user}, ; :data {:name :user/user},
; :result nil, ; :result nil,
; :params nil, ; :params nil,
; :required #{:id}} ; :required #{:id}}
@ -58,7 +58,7 @@ With provided path-parameters:
```clj ```clj
(r/match-by-name router ::user {:id "1"}) (r/match-by-name router ::user {:id "1"})
; #Match{:template "/api/user/:id" ; #Match{:template "/api/user/:id"
; :meta {:name :user/user} ; :data {:name :user/user}
; :path "/api/user/1" ; :path "/api/user/1"
; :result nil ; :result nil
; :params {:id "1"}} ; :params {:id "1"}}

View file

@ -30,7 +30,7 @@ Match provides the route information:
```clj ```clj
(r/match-by-path router "/api/user/1") (r/match-by-path router "/api/user/1")
; #Match{:template "/api/user/:id" ; #Match{:template "/api/user/:id"
; :meta {:name :user/user} ; :data {:name :user/user}
; :path "/api/user/1" ; :path "/api/user/1"
; :result nil ; :result nil
; :params {:id "1"}} ; :params {:id "1"}}

View file

@ -26,14 +26,14 @@ The expanded route data can be retrieved from a router with `routes` and is retu
(r/match-by-path router "/ping") (r/match-by-path router "/ping")
; #Match{:template "/ping" ; #Match{:template "/ping"
; :meta {:name :user/ping} ; :data {:name :user/ping}
; :result nil ; :result nil
; :params {} ; :params {}
; :path "/ping"} ; :path "/ping"}
(r/match-by-name router ::ping) (r/match-by-name router ::ping)
; #Match{:template "/ping" ; #Match{:template "/ping"
; :meta {:name :user/ping} ; :data {:name :user/ping}
; :result nil ; :result nil
; :params {} ; :params {}
; :path "/ping"} ; :path "/ping"}
@ -92,7 +92,7 @@ By default, `reitit/Expand` protocol is used to expand the route arguments. It e
(r/match-by-path router "/ping") (r/match-by-path router "/ping")
; #Match{:template "/ping" ; #Match{:template "/ping"
; :meta {:name :user/ping} ; :data {:name :user/ping}
; :result nil ; :result nil
; :params {} ; :params {}
; :path "/ping"} ; :path "/ping"}

View file

@ -47,4 +47,4 @@ When router is created, the following steps are done:
* 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
* actual [router implementation](../advanced/different_routers.md) is selected and created * actual [router implementation](../advanced/different_routers.md) is selected and created
* optionally route meta-data gets compiled * optionally route data gets compiled

View file

@ -4,7 +4,7 @@ The [dynamic extensions](dynamic_extensions.md) is a easy way to extend the syst
Still, 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? Still, 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?
To enable this we use [middleware records](data_driven_middleware.md) `:gen-wrap` key instead of the normal `:wrap`. `:gen-wrap` expects a function of `route-meta router-opts => ?wrap`. To enable this we use [middleware records](data_driven_middleware.md) `:gen-wrap` key instead of the normal `:wrap`. `:gen-wrap` expects a function of `route-data router-opts => ?wrap`.
To demonstrate the two approaches, below are response coercion middleware written as normal ring middleware function and as middleware record with `:gen-wrap`. Actual codes can be found in [`reitit.ring.coercion`](https://github.com/metosin/reitit/blob/master/src/reitit/ring/coercion.cljc): To demonstrate the two approaches, below are response coercion middleware written as normal ring middleware function and as middleware record with `:gen-wrap`. Actual codes can be found in [`reitit.ring.coercion`](https://github.com/metosin/reitit/blob/master/src/reitit/ring/coercion.cljc):
@ -16,16 +16,16 @@ To demonstrate the two approaches, below are response coercion middleware writte
(defn wrap-coerce-response (defn wrap-coerce-response
"Pluggable response coercion middleware. "Pluggable response coercion middleware.
Expects a :coercion of type `reitit.coercion.protocol/Coercion` Expects a :coercion of type `reitit.coercion.protocol/Coercion`
and :responses from route meta, otherwise will do nothing." and :responses from route data, otherwise will do nothing."
[handler] [handler]
(fn (fn
([request] ([request]
(let [response (handler request) (let [response (handler request)
method (:request-method request) method (:request-method request)
match (ring/get-match request) match (ring/get-match request)
responses (-> match :result method :meta :responses) responses (-> match :result method :data :responses)
coercion (-> match :meta :coercion) coercion (-> match :data :coercion)
opts (-> match :meta :opts)] opts (-> match :data :opts)]
(if (and coercion responses) (if (and coercion responses)
(let [coercers (response-coercers coercion responses opts)] (let [coercers (response-coercers coercion responses opts)]
(coerce-response coercers request response)) (coerce-response coercers request response))
@ -33,9 +33,9 @@ To demonstrate the two approaches, below are response coercion middleware writte
([request respond raise] ([request respond raise]
(let [method (:request-method request) (let [method (:request-method request)
match (ring/get-match request) match (ring/get-match request)
responses (-> match :result method :meta :responses) responses (-> match :result method :data :responses)
coercion (-> match :meta :coercion) coercion (-> match :data :coercion)
opts (-> match :meta :opts)] opts (-> match :data :opts)]
(if (and coercion responses) (if (and coercion responses)
(let [coercers (response-coercers coercion responses opts)] (let [coercers (response-coercers coercion responses opts)]
(handler request #(respond (coerce-response coercers request %)))) (handler request #(respond (coerce-response coercers request %))))
@ -54,7 +54,7 @@ To demonstrate the two approaches, below are response coercion middleware writte
(def gen-wrap-coerce-response (def gen-wrap-coerce-response
"Generator for pluggable response coercion middleware. "Generator for pluggable response coercion middleware.
Expects a :coercion of type `reitit.coercion.protocol/Coercion` Expects a :coercion of type `reitit.coercion.protocol/Coercion`
and :responses from route meta, otherwise does not mount." and :responses from route data, otherwise does not mount."
(middleware/create (middleware/create
{:name ::coerce-response {:name ::coerce-response
:gen-wrap (fn [{:keys [responses coercion opts]} _] :gen-wrap (fn [{:keys [responses coercion opts]} _]

View file

@ -9,13 +9,13 @@ Example middleware to guard routes based on user roles:
(defn wrap-enforce-roles [handler] (defn wrap-enforce-roles [handler]
(fn [{:keys [::roles] :as request}] (fn [{:keys [::roles] :as request}]
(let [required (some-> request (ring/get-match) :meta ::roles)] (let [required (some-> request (ring/get-match) :data ::roles)]
(if (and (seq required) (not (set/subset? required roles))) (if (and (seq required) (not (set/subset? required roles)))
{:status 403, :body "forbidden"} {:status 403, :body "forbidden"}
(handler request))))) (handler request)))))
``` ```
Mounted to an app via router meta-data (effecting all routes): Mounted to an app via router data (effecting all routes):
```clj ```clj
(def handler (constantly {:status 200, :body "ok"})) (def handler (constantly {:status 200, :body "ok"}))
@ -27,7 +27,7 @@ Mounted to an app via router meta-data (effecting all routes):
["/ping" handler] ["/ping" handler]
["/admin" {::roles #{:admin}} ["/admin" {::roles #{:admin}}
["/ping" handler]]]] ["/ping" handler]]]]
{:meta {:middleware [wrap-enforce-roles]}}))) {:data {:middleware [wrap-enforce-roles]}})))
``` ```
Anonymous access to public route: Anonymous access to public route:

View file

@ -14,10 +14,10 @@ Reitit provides pluggable parameter coercion via `reitit.ring.coercion.protocol/
To use `Coercion` with Ring, one needs to do the following: To use `Coercion` with Ring, one needs to do the following:
1. Define parameters and responses as data into route meta-data, in format adopted from [ring-swagger](https://github.com/metosin/ring-swagger#more-complete-example): 1. Define parameters and responses as data into route data, in format adopted from [ring-swagger](https://github.com/metosin/ring-swagger#more-complete-example):
* `:parameters` map, with submaps for different parameters: `:query`, `:body`, `:form`, `:header` and `:path`. Parameters are defined in the format understood by the `Coercion`. * `:parameters` map, with submaps for different parameters: `:query`, `:body`, `:form`, `:header` and `:path`. Parameters are defined in the format understood by the `Coercion`.
* `:responses` map, with response status codes as keys (or `:default` for "everything else") with maps with `:schema` and optionally `:description` as values. * `:responses` map, with response status codes as keys (or `:default` for "everything else") with maps with `:schema` and optionally `:description` as values.
2. Define a `Coercion` to route meta-data under `:coercion` 2. Define a `Coercion` to route data under `:coercion`
3. Mount request & response coercion middleware to the routes (recommended to mount to all routes under router as they mounted only to routes which have the parameters / responses defined): 3. Mount request & response coercion middleware to the routes (recommended to mount to all routes under router as they mounted only to routes which have the parameters / responses defined):
* `reitit.ring.coercion/gen-wrap-coerce-parameters` * `reitit.ring.coercion/gen-wrap-coerce-parameters`
* `gen-wrap-coerce-parameters/gen-wrap-coerce-responses` * `gen-wrap-coerce-parameters/gen-wrap-coerce-responses`
@ -42,7 +42,7 @@ If either request or response coercion fails, an descriptive error is thrown.
:get {:handler (fn [{{{:keys [x y]} :body} :parameters}] :get {:handler (fn [{{{:keys [x y]} :body} :parameters}]
{:status 200 {:status 200
:body {:total (+ x y)}})}}]] :body {:total (+ x y)}})}}]]
{:meta {:middleware [coercion/gen-wrap-coerce-parameters {:data {:middleware [coercion/gen-wrap-coerce-parameters
coercion/gen-wrap-coerce-response] coercion/gen-wrap-coerce-response]
:coercion spec/coercion}}))) :coercion spec/coercion}})))
``` ```
@ -82,7 +82,7 @@ Currently, `clojure.spec` [doesn't support runtime transformations via conformin
:get {:handler (fn [{{{:keys [x y]} :body} :parameters}] :get {:handler (fn [{{{:keys [x y]} :body} :parameters}]
{:status 200 {:status 200
:body {:total (+ x y)}})}}]] :body {:total (+ x y)}})}}]]
{:meta {:middleware [coercion/gen-wrap-coerce-parameters {:data {:middleware [coercion/gen-wrap-coerce-parameters
coercion/gen-wrap-coerce-response] coercion/gen-wrap-coerce-response]
:coercion spec/coercion}}))) :coercion spec/coercion}})))
``` ```

View file

@ -32,7 +32,7 @@ The expanded routes shows the compilation results:
(-> app (ring/get-router) (reitit/routes)) (-> app (ring/get-router) (reitit/routes))
; [["/ping" ; [["/ping"
; {:handler #object[...]} ; {:handler #object[...]}
; #Methods{:any #Endpoint{:meta {:handler #object[...]}, ; #Methods{:any #Endpoint{:data {:handler #object[...]},
; :handler #object[...], ; :handler #object[...],
; :middleware []}}]] ; :middleware []}}]]
``` ```

View file

@ -30,36 +30,36 @@
nil nil
(expand [_ _])) (expand [_ _]))
(defn walk [data {:keys [path meta routes expand] (defn walk [raw-routes {:keys [path data routes expand]
:or {meta [], routes [], expand expand} :or {data [], routes [], expand expand}
:as opts}] :as opts}]
(letfn (letfn
[(walk-many [p m r] [(walk-many [p m r]
(reduce #(into %1 (walk-one p m %2)) [] r)) (reduce #(into %1 (walk-one p m %2)) [] r))
(walk-one [pacc macc routes] (walk-one [pacc macc routes]
(if (vector? (first routes)) (if (vector? (first routes))
(walk-many pacc macc routes) (walk-many pacc macc routes)
(let [[path & [maybe-meta :as args]] routes (let [[path & [maybe-arg :as args]] routes
[meta childs] (if (vector? maybe-meta) [data childs] (if (vector? maybe-arg)
[{} args] [{} args]
[maybe-meta (rest args)]) [maybe-arg (rest args)])
macc (into macc (expand meta opts))] macc (into macc (expand data opts))]
(if (seq childs) (if (seq childs)
(walk-many (str pacc path) macc childs) (walk-many (str pacc path) macc childs)
[[(str pacc path) macc]]))))] [[(str pacc path) macc]]))))]
(walk-one path (mapv identity meta) data))) (walk-one path (mapv identity data) raw-routes)))
(defn map-meta [f routes] (defn map-data [f routes]
(mapv #(update % 1 f) routes)) (mapv #(update % 1 f) routes))
(defn merge-meta [x] (defn merge-data [x]
(reduce (reduce
(fn [acc [k v]] (fn [acc [k v]]
(meta-merge acc {k v})) (meta-merge acc {k v}))
{} x)) {} x))
(defn resolve-routes [data {:keys [coerce] :as opts}] (defn resolve-routes [raw-routes {:keys [coerce] :as opts}]
(cond->> (->> (walk data opts) (map-meta merge-meta)) (cond->> (->> (walk raw-routes opts) (map-data merge-data))
coerce (into [] (keep #(coerce % opts))))) coerce (into [] (keep #(coerce % opts)))))
;; This whole function might be more efficient and easier to understand with transducers. ;; This whole function might be more efficient and easier to understand with transducers.
@ -100,7 +100,7 @@
(into [] (keep #(compile-route % opts) routes))) (into [] (keep #(compile-route % opts) routes)))
(defn route-info [route] (defn route-info [route]
(select-keys (impl/create route) [:path :parts :params :result :meta])) (select-keys (impl/create route) [:path :parts :params :result :data]))
(defprotocol Router (defprotocol Router
(router-name [this]) (router-name [this])
@ -113,8 +113,8 @@
(defn router? [x] (defn router? [x]
(satisfies? Router x)) (satisfies? Router x))
(defrecord Match [template meta result params path]) (defrecord Match [template data result params path])
(defrecord PartialMatch [template meta result params required]) (defrecord PartialMatch [template data result params required])
(defn partial-match? [x] (defn partial-match? [x]
(instance? PartialMatch x)) (instance? PartialMatch x))
@ -144,16 +144,16 @@
([routes opts] ([routes opts]
(let [compiled (compile-routes routes opts) (let [compiled (compile-routes routes opts)
names (find-names routes opts) names (find-names routes opts)
[data lookup] (reduce [pl nl] (reduce
(fn [[data lookup] [p {:keys [name] :as meta} result]] (fn [[pl nl] [p {:keys [name] :as data} result]]
(let [{:keys [params] :as route} (impl/create [p meta result]) (let [{:keys [params] :as route} (impl/create [p data result])
f #(if-let [path (impl/path-for route %)] f #(if-let [path (impl/path-for route %)]
(->Match p meta result % path) (->Match p data result % path)
(->PartialMatch p meta result % params))] (->PartialMatch p data result % params))]
[(conj data route) [(conj pl route)
(if name (assoc lookup name f) lookup)])) (if name (assoc nl name f) nl)]))
[[] {}] compiled) [[] {}] compiled)
lookup (impl/fast-map lookup)] lookup (impl/fast-map nl)]
(reify (reify
Router Router
(router-name [_] (router-name [_]
@ -168,8 +168,8 @@
(reduce (reduce
(fn [acc ^Route route] (fn [acc ^Route route]
(if-let [params ((:matcher route) path)] (if-let [params ((:matcher route) path)]
(reduced (->Match (:path route) (:meta route) (:result route) params path)))) (reduced (->Match (:path route) (:data route) (:result route) params path))))
nil data)) nil pl))
(match-by-name [_ name] (match-by-name [_ name]
(if-let [match (impl/fast-get lookup name)] (if-let [match (impl/fast-get lookup name)]
(match nil))) (match nil)))
@ -191,14 +191,14 @@
:routes routes}))) :routes routes})))
(let [compiled (compile-routes routes opts) (let [compiled (compile-routes routes opts)
names (find-names routes opts) names (find-names routes opts)
[data lookup] (reduce [pl nl] (reduce
(fn [[data lookup] [p {:keys [name] :as meta} result]] (fn [[pl nl] [p {:keys [name] :as data} result]]
[(assoc data p (->Match p meta result {} p)) [(assoc pl p (->Match p data result {} p))
(if name (if name
(assoc lookup name #(->Match p meta result % p)) (assoc nl name #(->Match p data result % p))
lookup)]) [{} {}] compiled) nl)]) [{} {}] compiled)
data (impl/fast-map data) data (impl/fast-map pl)
lookup (impl/fast-map lookup)] lookup (impl/fast-map nl)]
(reify Router (reify Router
(router-name [_] (router-name [_]
:lookup-router) :lookup-router)
@ -225,16 +225,16 @@
([routes opts] ([routes opts]
(let [compiled (compile-routes routes opts) (let [compiled (compile-routes routes opts)
names (find-names routes opts) names (find-names routes opts)
[node lookup] (reduce [pl nl] (reduce
(fn [[node lookup] [p {:keys [name] :as meta} result]] (fn [[pl nl] [p {:keys [name] :as data} result]]
(let [{:keys [params] :as route} (impl/create [p meta result]) (let [{:keys [params] :as route} (impl/create [p data result])
f #(if-let [path (impl/path-for route %)] f #(if-let [path (impl/path-for route %)]
(->Match p meta result % path) (->Match p data result % path)
(->PartialMatch p meta result % params))] (->PartialMatch p data result % params))]
[(trie/insert node p (->Match p meta result nil nil)) [(trie/insert pl p (->Match p data result nil nil))
(if name (assoc lookup name f) lookup)])) (if name (assoc nl name f) nl)]))
[nil {}] compiled) [nil {}] compiled)
lookup (impl/fast-map lookup)] lookup (impl/fast-map nl)]
(reify (reify
Router Router
(router-name [_] (router-name [_]
@ -246,7 +246,7 @@
(route-names [_] (route-names [_]
names) names)
(match-by-path [_ path] (match-by-path [_ path]
(if-let [match (trie/lookup node path {})] (if-let [match (trie/lookup pl path {})]
(-> (:data match) (-> (:data match)
(assoc :params (:params match)) (assoc :params (:params match))
(assoc :path path)))) (assoc :path path))))
@ -269,9 +269,9 @@
(str ":single-static-path-router requires exactly 1 static route: " routes) (str ":single-static-path-router requires exactly 1 static route: " routes)
{:routes routes}))) {:routes routes})))
(let [[n :as names] (find-names routes opts) (let [[n :as names] (find-names routes opts)
[[p meta result] :as compiled] (compile-routes routes opts) [[p data result] :as compiled] (compile-routes routes opts)
p #?(:clj (.intern ^String p) :cljs p) p #?(:clj (.intern ^String p) :cljs p)
match (->Match p meta result {} p)] match (->Match p data result {} p)]
(reify Router (reify Router
(router-name [_] (router-name [_]
:single-static-path-router) :single-static-path-router)
@ -333,17 +333,17 @@
| -------------|-------------| | -------------|-------------|
| `:path` | Base-path for routes | `:path` | Base-path for routes
| `:routes` | Initial resolved routes (default `[]`) | `:routes` | Initial resolved routes (default `[]`)
| `:meta` | Initial route meta (default `{}`) | `:data` | Initial route data (default `{}`)
| `:expand` | Function of `arg opts => meta` to expand route arg to route meta-data (default `reitit.core/expand`) | `:expand` | Function of `arg opts => data` to expand route arg to route data (default `reitit.core/expand`)
| `:coerce` | Function of `route opts => route` to coerce resolved route, can throw or return `nil` | `:coerce` | Function of `route opts => route` to coerce resolved route, can throw or return `nil`
| `:compile` | Function of `route opts => result` to compile a route handler | `:compile` | Function of `route opts => result` to compile a route handler
| `:conflicts` | Function of `{route #{route}} => side-effect` to handle conflicting routes (default `reitit.core/throw-on-conflicts!`) | `:conflicts` | Function of `{route #{route}} => side-effect` to handle conflicting routes (default `reitit.core/throw-on-conflicts!`)
| `:router` | Function of `routes opts => router` to override the actual router implementation" | `:router` | Function of `routes opts => router` to override the actual router implementation"
([data] ([raw-routes]
(router data {})) (router raw-routes {}))
([data opts] ([raw-routes opts]
(let [{:keys [router] :as opts} (meta-merge default-router-options opts) (let [{:keys [router] :as opts} (meta-merge default-router-options opts)
routes (resolve-routes data opts) routes (resolve-routes raw-routes opts)
conflicting (conflicting-routes routes) conflicting (conflicting-routes routes)
wilds? (boolean (some impl/wild-route? routes)) wilds? (boolean (some impl/wild-route? routes))
all-wilds? (every? impl/wild-route? routes) all-wilds? (every? impl/wild-route? routes)

View file

@ -111,9 +111,9 @@
;; Routing (c) Metosin ;; Routing (c) Metosin
;; ;;
(defrecord Route [path matcher parts params meta result]) (defrecord Route [path matcher parts params data result])
(defn create [[path meta result]] (defn create [[path data result]]
(let [path #?(:clj (.intern ^String path) :cljs path)] (let [path #?(:clj (.intern ^String path) :cljs path)]
(as-> (parse-path path) $ (as-> (parse-path path) $
(assoc $ :path-re (path-regex $)) (assoc $ :path-re (path-regex $))
@ -122,7 +122,7 @@
(path-matcher $) (path-matcher $)
#(if (#?(:clj .equals, :cljs =) path %) {})) #(if (#?(:clj .equals, :cljs =) path %) {}))
:result result :result result
:meta meta}) :data data})
(dissoc $ :path-re :path-constraints) (dissoc $ :path-re :path-constraints)
(update $ :path-params set) (update $ :path-params set)
(set/rename-keys $ {:path-parts :parts (set/rename-keys $ {:path-parts :parts

View file

@ -12,7 +12,7 @@
#(gen/fmap (fn [s] (str "/" s)) (s/gen string?)))) #(gen/fmap (fn [s] (str "/" s)) (s/gen string?))))
(s/def ::arg (s/and any? (complement vector?))) (s/def ::arg (s/and any? (complement vector?)))
(s/def ::meta (s/map-of keyword? any?)) (s/def ::data (s/map-of keyword? any?))
(s/def ::result any?) (s/def ::result any?)
(s/def ::raw-route (s/def ::raw-route
@ -26,7 +26,7 @@
(s/def ::route (s/def ::route
(s/cat :path ::path (s/cat :path ::path
:meta ::meta :data ::data
:result (s/? any?))) :result (s/? any?)))
(s/def ::routes (s/def ::routes
@ -40,7 +40,7 @@
(s/def ::router reitit/router?) (s/def ::router reitit/router?)
(s/def :reitit.router/path ::path) (s/def :reitit.router/path ::path)
(s/def :reitit.router/routes ::routes) (s/def :reitit.router/routes ::routes)
(s/def :reitit.router/meta ::meta) (s/def :reitit.router/data ::data)
(s/def :reitit.router/expand fn?) (s/def :reitit.router/expand fn?)
(s/def :reitit.router/coerce fn?) (s/def :reitit.router/coerce fn?)
(s/def :reitit.router/compile fn?) (s/def :reitit.router/compile fn?)
@ -51,7 +51,7 @@
(s/nilable (s/nilable
(s/keys :opt-un [:reitit.router/path (s/keys :opt-un [:reitit.router/path
:reitit.router/routes :reitit.router/routes
:reitit.router/meta :reitit.router/data
:reitit.router/expand :reitit.router/expand
:reitit.router/coerce :reitit.router/coerce
:reitit.router/compile :reitit.router/compile

View file

@ -7,12 +7,12 @@
(def http-methods #{:get :head :patch :delete :options :post :put}) (def http-methods #{:get :head :patch :delete :options :post :put})
(defrecord Methods [get head post put delete trace options connect patch any]) (defrecord Methods [get head post put delete trace options connect patch any])
(defn- group-keys [meta] (defn- group-keys [data]
(reduce-kv (reduce-kv
(fn [[top childs] k v] (fn [[top childs] k v]
(if (http-methods k) (if (http-methods k)
[top (assoc childs k v)] [top (assoc childs k v)]
[(assoc top k v) childs])) [{} {}] meta)) [(assoc top k v) childs])) [{} {}] data))
(defn ring-handler (defn ring-handler
"Creates a ring-handler out of a ring-router. "Creates a ring-handler out of a ring-router.
@ -50,23 +50,23 @@
(defn get-match [request] (defn get-match [request]
(::match request)) (::match request))
(defn coerce-handler [[path meta] {:keys [expand] :as opts}] (defn coerce-handler [[path data] {:keys [expand] :as opts}]
[path (reduce [path (reduce
(fn [acc method] (fn [acc method]
(if (contains? acc method) (if (contains? acc method)
(update acc method expand opts) (update acc method expand opts)
acc)) meta http-methods)]) acc)) data http-methods)])
(defn compile-result [[path meta] opts] (defn compile-result [[path data] opts]
(let [[top childs] (group-keys meta)] (let [[top childs] (group-keys data)]
(if-not (seq childs) (if-not (seq childs)
(let [middleware (middleware/compile-result [path top] opts)] (let [middleware (middleware/compile-result [path top] opts)]
(map->Methods {:any (middleware/compile-result [path top] opts)})) (map->Methods {:any (middleware/compile-result [path top] opts)}))
(let [any-handler (if (:handler top) (middleware/compile-result [path meta] opts))] (let [any-handler (if (:handler top) (middleware/compile-result [path data] opts))]
(reduce-kv (reduce-kv
(fn [acc method meta] (fn [acc method data]
(let [meta (meta-merge top meta)] (let [data (meta-merge top data)]
(assoc acc method (middleware/compile-result [path meta] opts method)))) (assoc acc method (middleware/compile-result [path data] opts method))))
(map->Methods {:any any-handler}) (map->Methods {:any any-handler})
childs))))) childs)))))

View file

@ -107,14 +107,14 @@
(defn wrap-coerce-parameters (defn wrap-coerce-parameters
"Pluggable request coercion middleware. "Pluggable request coercion middleware.
Expects a :coercion of type `reitit.coercion.protocol/Coercion` Expects a :coercion of type `reitit.coercion.protocol/Coercion`
and :parameters from route meta, otherwise will do nothing." and :parameters from route data, otherwise will do nothing."
[handler] [handler]
(fn (fn
([request] ([request]
(let [method (:request-method request) (let [method (:request-method request)
match (ring/get-match request) match (ring/get-match request)
parameters (-> match :result method :meta :parameters) parameters (-> match :result method :data :parameters)
coercion (-> match :meta :coercion)] coercion (-> match :data :coercion)]
(if (and coercion parameters) (if (and coercion parameters)
(let [coercers (request-coercers coercion parameters) (let [coercers (request-coercers coercion parameters)
coerced (coerce-parameters coercers request)] coerced (coerce-parameters coercers request)]
@ -123,8 +123,8 @@
([request respond raise] ([request respond raise]
(let [method (:request-method request) (let [method (:request-method request)
match (ring/get-match request) match (ring/get-match request)
parameters (-> match :result method :meta :parameters) parameters (-> match :result method :data :parameters)
coercion (-> match :meta :coercion)] coercion (-> match :data :coercion)]
(if (and coercion parameters) (if (and coercion parameters)
(let [coercers (request-coercers coercion parameters) (let [coercers (request-coercers coercion parameters)
coerced (coerce-parameters coercers request)] coerced (coerce-parameters coercers request)]
@ -133,7 +133,7 @@
(def gen-wrap-coerce-parameters (def gen-wrap-coerce-parameters
"Generator for pluggable request coercion middleware. "Generator for pluggable request coercion middleware.
Expects a :coercion of type `reitit.coercion.protocol/Coercion` Expects a :coercion of type `reitit.coercion.protocol/Coercion`
and :parameters from route meta, otherwise does not mount." and :parameters from route data, otherwise does not mount."
(middleware/create (middleware/create
{:name ::coerce-parameters {:name ::coerce-parameters
:gen-wrap (fn [{:keys [parameters coercion]} _] :gen-wrap (fn [{:keys [parameters coercion]} _]
@ -151,16 +151,16 @@
(defn wrap-coerce-response (defn wrap-coerce-response
"Pluggable response coercion middleware. "Pluggable response coercion middleware.
Expects a :coercion of type `reitit.coercion.protocol/Coercion` Expects a :coercion of type `reitit.coercion.protocol/Coercion`
and :responses from route meta, otherwise will do nothing." and :responses from route data, otherwise will do nothing."
[handler] [handler]
(fn (fn
([request] ([request]
(let [response (handler request) (let [response (handler request)
method (:request-method request) method (:request-method request)
match (ring/get-match request) match (ring/get-match request)
responses (-> match :result method :meta :responses) responses (-> match :result method :data :responses)
coercion (-> match :meta :coercion) coercion (-> match :data :coercion)
opts (-> match :meta :opts)] opts (-> match :data :opts)]
(if (and coercion responses) (if (and coercion responses)
(let [coercers (response-coercers coercion responses opts)] (let [coercers (response-coercers coercion responses opts)]
(coerce-response coercers request response)) (coerce-response coercers request response))
@ -168,9 +168,9 @@
([request respond raise] ([request respond raise]
(let [method (:request-method request) (let [method (:request-method request)
match (ring/get-match request) match (ring/get-match request)
responses (-> match :result method :meta :responses) responses (-> match :result method :data :responses)
coercion (-> match :meta :coercion) coercion (-> match :data :coercion)
opts (-> match :meta :opts)] opts (-> match :data :opts)]
(if (and coercion responses) (if (and coercion responses)
(let [coercers (response-coercers coercion responses opts)] (let [coercers (response-coercers coercion responses opts)]
(handler request #(respond (coerce-response coercers request %)))) (handler request #(respond (coerce-response coercers request %))))
@ -179,7 +179,7 @@
(def gen-wrap-coerce-response (def gen-wrap-coerce-response
"Generator for pluggable response coercion middleware. "Generator for pluggable response coercion middleware.
Expects a :coercion of type `reitit.coercion.protocol/Coercion` Expects a :coercion of type `reitit.coercion.protocol/Coercion`
and :responses from route meta, otherwise does not mount." and :responses from route data, otherwise does not mount."
(middleware/create (middleware/create
{:name ::coerce-response {:name ::coerce-response
:gen-wrap (fn [{:keys [responses coercion opts]} _] :gen-wrap (fn [{:keys [responses coercion opts]} _]

View file

@ -3,10 +3,10 @@
[reitit.core :as r])) [reitit.core :as r]))
(defprotocol IntoMiddleware (defprotocol IntoMiddleware
(into-middleware [this meta opts])) (into-middleware [this data opts]))
(defrecord Middleware [name wrap]) (defrecord Middleware [name wrap])
(defrecord Endpoint [meta handler middleware]) (defrecord Endpoint [data handler middleware])
(defn create [{:keys [name wrap gen-wrap] :as m}] (defn create [{:keys [name wrap gen-wrap] :as m}]
(when (and wrap gen-wrap) (when (and wrap gen-wrap)
@ -19,8 +19,8 @@
#?(:clj clojure.lang.APersistentVector #?(:clj clojure.lang.APersistentVector
:cljs cljs.core.PersistentVector) :cljs cljs.core.PersistentVector)
(into-middleware [[f & args] meta opts] (into-middleware [[f & args] data opts]
(if-let [{:keys [wrap] :as mw} (into-middleware f meta opts)] (if-let [{:keys [wrap] :as mw} (into-middleware f data opts)]
(assoc mw :wrap #(apply wrap % args)))) (assoc mw :wrap #(apply wrap % args))))
#?(:clj clojure.lang.Fn #?(:clj clojure.lang.Fn
@ -31,19 +31,19 @@
#?(:clj clojure.lang.PersistentArrayMap #?(:clj clojure.lang.PersistentArrayMap
:cljs cljs.core.PersistentArrayMap) :cljs cljs.core.PersistentArrayMap)
(into-middleware [this meta opts] (into-middleware [this data opts]
(into-middleware (create this) meta opts)) (into-middleware (create this) data opts))
#?(:clj clojure.lang.PersistentHashMap #?(:clj clojure.lang.PersistentHashMap
:cljs cljs.core.PersistentHashMap) :cljs cljs.core.PersistentHashMap)
(into-middleware [this meta opts] (into-middleware [this data opts]
(into-middleware (create this) meta opts)) (into-middleware (create this) data opts))
Middleware Middleware
(into-middleware [{:keys [wrap gen-wrap] :as this} meta opts] (into-middleware [{:keys [wrap gen-wrap] :as this} data opts]
(if-not gen-wrap (if-not gen-wrap
this this
(if-let [wrap (gen-wrap meta opts)] (if-let [wrap (gen-wrap data opts)]
(map->Middleware (map->Middleware
(-> this (-> this
(dissoc :gen-wrap) (dissoc :gen-wrap)
@ -52,17 +52,17 @@
nil nil
(into-middleware [_ _ _])) (into-middleware [_ _ _]))
(defn- ensure-handler! [path meta scope] (defn- ensure-handler! [path data scope]
(when-not (:handler meta) (when-not (:handler data)
(throw (ex-info (throw (ex-info
(str "path \"" path "\" doesn't have a :handler defined" (str "path \"" path "\" doesn't have a :handler defined"
(if scope (str " for " scope))) (if scope (str " for " scope)))
(merge {:path path, :meta meta} (merge {:path path, :data data}
(if scope {:scope scope})))))) (if scope {:scope scope}))))))
(defn expand [middleware meta opts] (defn expand [middleware data opts]
(->> middleware (->> middleware
(keep #(into-middleware % meta opts)) (keep #(into-middleware % data opts))
(into []))) (into [])))
(defn compile-handler [middleware handler] (defn compile-handler [middleware handler]
@ -78,13 +78,13 @@
(defn compile-result (defn compile-result
([route opts] ([route opts]
(compile-result route opts nil)) (compile-result route opts nil))
([[path {:keys [middleware handler] :as meta}] opts scope] ([[path {:keys [middleware handler] :as data}] opts scope]
(ensure-handler! path meta scope) (ensure-handler! path data scope)
(let [middleware (expand middleware meta opts)] (let [middleware (expand middleware data opts)]
(map->Endpoint (map->Endpoint
{:handler (compile-handler middleware handler) {:handler (compile-handler middleware handler)
:middleware middleware :middleware middleware
:meta meta})))) :data data}))))
(defn router (defn router
([data] ([data]

View file

@ -101,23 +101,23 @@
app (ring/ring-handler app (ring/ring-handler
(ring/router (ring/router
routes routes
{:meta {:middleware [coercion/wrap-coerce-parameters] {:data {:middleware [coercion/wrap-coerce-parameters]
:coercion coercion}})) :coercion coercion}}))
app2 (ring/ring-handler app2 (ring/ring-handler
(ring/router (ring/router
routes routes
{:meta {:middleware [coercion/gen-wrap-coerce-parameters] {:data {:middleware [coercion/gen-wrap-coerce-parameters]
:coercion coercion}})) :coercion coercion}}))
app3 (ring/ring-handler app3 (ring/ring-handler
(ring/router (ring/router
routes routes
{:meta {:middleware [coercion/wrap-coerce-parameters {:data {:middleware [coercion/wrap-coerce-parameters
coercion/wrap-coerce-response] coercion/wrap-coerce-response]
:coercion coercion}})) :coercion coercion}}))
app4 (ring/ring-handler app4 (ring/ring-handler
(ring/router (ring/router
routes routes
{:meta {:middleware [coercion/gen-wrap-coerce-parameters {:data {:middleware [coercion/gen-wrap-coerce-parameters
coercion/gen-wrap-coerce-response] coercion/gen-wrap-coerce-response]
:coercion coercion}})) :coercion coercion}}))
req {:request-method :get req {:request-method :get
@ -159,7 +159,7 @@
:get {:handler (fn [{{{:keys [x y]} :body} :parameters}] :get {:handler (fn [{{{:keys [x y]} :body} :parameters}]
{:status 200 {:status 200
:body {:total (+ x y)}})}}]] :body {:total (+ x y)}})}}]]
{:meta {:middleware [coercion/gen-wrap-coerce-parameters {:data {:middleware [coercion/gen-wrap-coerce-parameters
coercion/gen-wrap-coerce-response] coercion/gen-wrap-coerce-response]
:coercion spec/coercion}}))) :coercion spec/coercion}})))

View file

@ -30,7 +30,7 @@
{:keys [e]} :path} :parameters}] {:keys [e]} :path} :parameters}]
{:status 200 {:status 200
:body {:total (+ a b c d e)}})}}]] :body {:total (+ a b c d e)}})}}]]
{:meta {:middleware [coercion/gen-wrap-coerce-parameters {:data {:middleware [coercion/gen-wrap-coerce-parameters
coercion/gen-wrap-coerce-response] coercion/gen-wrap-coerce-response]
:coercion spec/coercion}}))] :coercion spec/coercion}}))]

View file

@ -16,13 +16,13 @@
(is (= true (map? (r/options router)))) (is (= true (map? (r/options router))))
(is (= (r/map->Match (is (= (r/map->Match
{:template "/api/ipa/:size" {:template "/api/ipa/:size"
:meta {:name ::beer} :data {:name ::beer}
:path "/api/ipa/large" :path "/api/ipa/large"
:params {:size "large"}}) :params {:size "large"}})
(r/match-by-path router "/api/ipa/large"))) (r/match-by-path router "/api/ipa/large")))
(is (= (r/map->Match (is (= (r/map->Match
{:template "/api/ipa/:size" {:template "/api/ipa/:size"
:meta {:name ::beer} :data {:name ::beer}
:path "/api/ipa/large" :path "/api/ipa/large"
:params {:size "large"}}) :params {:size "large"}})
(r/match-by-name router ::beer {:size "large"}))) (r/match-by-name router ::beer {:size "large"})))
@ -32,7 +32,7 @@
(testing "name-based routing with missing parameters" (testing "name-based routing with missing parameters"
(is (= (r/map->PartialMatch (is (= (r/map->PartialMatch
{:template "/api/ipa/:size" {:template "/api/ipa/:size"
:meta {:name ::beer} :data {:name ::beer}
:required #{:size} :required #{:size}
:params nil}) :params nil})
(r/match-by-name router ::beer))) (r/match-by-name router ::beer)))
@ -55,13 +55,13 @@
(is (= true (map? (r/options router)))) (is (= true (map? (r/options router))))
(is (= (r/map->Match (is (= (r/map->Match
{:template "/api/ipa/large" {:template "/api/ipa/large"
:meta {:name ::beer} :data {:name ::beer}
:path "/api/ipa/large" :path "/api/ipa/large"
:params {}}) :params {}})
(r/match-by-path router "/api/ipa/large"))) (r/match-by-path router "/api/ipa/large")))
(is (= (r/map->Match (is (= (r/map->Match
{:template "/api/ipa/large" {:template "/api/ipa/large"
:meta {:name ::beer} :data {:name ::beer}
:path "/api/ipa/large" :path "/api/ipa/large"
:params {:size "large"}}) :params {:size "large"}})
(r/match-by-name router ::beer {:size "large"}))) (r/match-by-name router ::beer {:size "large"})))
@ -86,10 +86,10 @@
(testing "custom compile" (testing "custom compile"
(let [compile-times (atom 0) (let [compile-times (atom 0)
coerce (fn [[path meta] _] coerce (fn [[path data] _]
(if-not (:invalid? meta) (if-not (:invalid? data)
[path (assoc meta :path path)])) [path (assoc data :path path)]))
compile (fn [[path meta] _] compile (fn [[path data] _]
(swap! compile-times inc) (swap! compile-times inc)
(constantly path)) (constantly path))
router (r/router router (r/router
@ -159,7 +159,7 @@
(is (= expected (r/resolve-routes routes {}))) (is (= expected (r/resolve-routes routes {})))
(is (= (r/map->Match (is (= (r/map->Match
{:template "/api/user/:id/:sub-id" {:template "/api/user/:id/:sub-id"
:meta {:mw [:api], :parameters {:id "String", :sub-id "String"}} :data {:mw [:api], :parameters {:id "String", :sub-id "String"}}
:path "/api/user/1/2" :path "/api/user/1/2"
:params {:id "1", :sub-id "2"}}) :params {:id "1", :sub-id "2"}})
(r/match-by-path router "/api/user/1/2")))))) (r/match-by-path router "/api/user/1/2"))))))

View file

@ -27,7 +27,7 @@
[value request])) [value request]))
->app (fn [ast handler] ->app (fn [ast handler]
(middleware/compile-handler (middleware/compile-handler
(middleware/expand ast :meta {}) (middleware/expand ast :data {})
handler))] handler))]
(testing "as middleware function" (testing "as middleware function"
@ -74,29 +74,29 @@
(testing "compiled Middleware" (testing "compiled Middleware"
(let [calls (atom 0) (let [calls (atom 0)
mw {:gen-wrap (fn [meta _] mw {:gen-wrap (fn [data _]
(swap! calls inc) (swap! calls inc)
(fn [handler value] (fn [handler value]
(swap! calls inc) (swap! calls inc)
(fn [request] (fn [request]
[meta value request])))} [data value request])))}
->app (fn [ast handler] ->app (fn [ast handler]
(middleware/compile-handler (middleware/compile-handler
(middleware/expand ast :meta {}) (middleware/expand ast :data {})
handler))] handler))]
(testing "as map" (testing "as map"
(reset! calls 0) (reset! calls 0)
(let [app (->app [[mw :value]] identity)] (let [app (->app [[mw :value]] identity)]
(dotimes [_ 10] (dotimes [_ 10]
(is (= [:meta :value :request] (app :request))) (is (= [:data :value :request] (app :request)))
(is (= 2 @calls))))) (is (= 2 @calls)))))
(testing "as Middleware" (testing "as Middleware"
(reset! calls 0) (reset! calls 0)
(let [app (->app [[(middleware/create mw) :value]] identity)] (let [app (->app [[(middleware/create mw) :value]] identity)]
(dotimes [_ 10] (dotimes [_ 10]
(is (= [:meta :value :request] (app :request))) (is (= [:data :value :request] (app :request)))
(is (= 2 @calls))))) (is (= 2 @calls)))))
(testing "nil unmounts the middleware" (testing "nil unmounts the middleware"
@ -169,3 +169,4 @@
:result :result
:middleware :middleware
(map :name)))))))))) (map :name))))))))))

View file

@ -102,16 +102,16 @@
(testing "all named routes can be matched" (testing "all named routes can be matched"
(doseq [name (r/route-names router)] (doseq [name (r/route-names router)]
(is (= name (-> (r/match-by-name router name) :meta :name)))))))) (is (= name (-> (r/match-by-name router name) :data :name))))))))
(defn wrap-enforce-roles [handler] (defn wrap-enforce-roles [handler]
(fn [{:keys [::roles] :as request}] (fn [{:keys [::roles] :as request}]
(let [required (some-> request (ring/get-match) :meta ::roles)] (let [required (some-> request (ring/get-match) :data ::roles)]
(if (and (seq required) (not (set/intersection required roles))) (if (and (seq required) (not (set/intersection required roles)))
{:status 403, :body "forbidden"} {:status 403, :body "forbidden"}
(handler request))))) (handler request)))))
(deftest enforcing-meta-data-rules-at-runtime-test (deftest enforcing-data-rules-at-runtime-test
(let [handler (constantly {:status 200, :body "ok"}) (let [handler (constantly {:status 200, :body "ok"})
app (ring/ring-handler app (ring/ring-handler
(ring/router (ring/router
@ -119,7 +119,7 @@
["/ping" handler] ["/ping" handler]
["/admin" {::roles #{:admin}} ["/admin" {::roles #{:admin}}
["/ping" handler]]]] ["/ping" handler]]]]
{:meta {:middleware [wrap-enforce-roles]}}))] {:data {:middleware [wrap-enforce-roles]}}))]
(testing "public handler" (testing "public handler"
(is (= {:status 200, :body "ok"} (is (= {:status 200, :body "ok"}

View file

@ -40,7 +40,7 @@
;; path ;; path
[:invalid {}] [:invalid {}]
;; vector meta ;; vector data
["/api" [] ["/api" []
["/ipa"]]))) ["/ipa"]])))
@ -53,7 +53,7 @@
(is (= true (r/router? (r/router ["/api"] opts)))) (is (= true (r/router? (r/router ["/api"] opts))))
{:path "/"} {:path "/"}
{:meta {}} {:data {}}
{:expand (fn [_ _] {})} {:expand (fn [_ _] {})}
{:coerce (fn [route _] route)} {:coerce (fn [route _] route)}
{:compile (fn [_ _])} {:compile (fn [_ _])}
@ -69,7 +69,7 @@
{:path "api"} {:path "api"}
{:path nil} {:path nil}
{:meta nil} {:data nil}
{:expand nil} {:expand nil}
{:coerce nil} {:coerce nil}
{:compile nil} {:compile nil}