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)
* 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
* [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))
@ -41,14 +41,14 @@ Optionally, the parts can be required separately:
(r/match-by-path router "/api/ping")
; #Match{:template "/api/ping"
; :meta {:name ::ping}
; :data {:name ::ping}
; :result nil
; :params {}
; :path "/api/ping"}
(r/match-by-name router ::order {:id 2})
; #Match{:template "/api/orders/:id",
;       :meta {:name ::order},
;       :data {:name ::order},
; :result nil,
; :params {:id 2},
; :path "/api/orders/2"}

View file

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

View file

@ -1,13 +1,13 @@
## 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 |
| -------------|-------------|
| `:path` | Base-path for routes
| `:routes` | Initial resolved routes (default `[]`)
| `:meta` | Initial expanded route-meta vector (default `[]`)
| `:expand` | Function of `arg opts => meta` to expand route arg to route meta-data (default `reitit.core/expand`)
| `:data` | Initial route data (default `{}`)
| `: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`
| `: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!`)

View file

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

View file

@ -30,7 +30,7 @@ Match provides the route information:
```clj
(r/match-by-path router "/api/user/1")
; #Match{:template "/api/user/:id"
; :meta {:name :user/user}
; :data {:name :user/user}
; :path "/api/user/1"
; :result nil
; :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")
; #Match{:template "/ping"
; :meta {:name :user/ping}
; :data {:name :user/ping}
; :result nil
; :params {}
; :path "/ping"}
(r/match-by-name router ::ping)
; #Match{:template "/ping"
; :meta {:name :user/ping}
; :data {:name :user/ping}
; :result nil
; :params {}
; :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")
; #Match{:template "/ping"
; :meta {:name :user/ping}
; :data {:name :user/ping}
; :result nil
; :params {}
; :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 conflicts](advanced/route_conflicts.md) are resolved
* 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?
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):
@ -16,16 +16,16 @@ To demonstrate the two approaches, below are response coercion middleware writte
(defn wrap-coerce-response
"Pluggable response coercion middleware.
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]
(fn
([request]
(let [response (handler request)
method (:request-method request)
match (ring/get-match request)
responses (-> match :result method :meta :responses)
coercion (-> match :meta :coercion)
opts (-> match :meta :opts)]
responses (-> match :result method :data :responses)
coercion (-> match :data :coercion)
opts (-> match :data :opts)]
(if (and coercion responses)
(let [coercers (response-coercers coercion responses opts)]
(coerce-response coercers request response))
@ -33,9 +33,9 @@ To demonstrate the two approaches, below are response coercion middleware writte
([request respond raise]
(let [method (:request-method request)
match (ring/get-match request)
responses (-> match :result method :meta :responses)
coercion (-> match :meta :coercion)
opts (-> match :meta :opts)]
responses (-> match :result method :data :responses)
coercion (-> match :data :coercion)
opts (-> match :data :opts)]
(if (and coercion responses)
(let [coercers (response-coercers coercion responses opts)]
(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
"Generator for pluggable response coercion middleware.
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
{:name ::coerce-response
: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]
(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)))
{:status 403, :body "forbidden"}
(handler request)))))
```
Mounted to an app via router meta-data (effecting all routes):
Mounted to an app via router data (effecting all routes):
```clj
(def handler (constantly {:status 200, :body "ok"}))
@ -27,7 +27,7 @@ Mounted to an app via router meta-data (effecting all routes):
["/ping" handler]
["/admin" {::roles #{:admin}}
["/ping" handler]]]]
{:meta {:middleware [wrap-enforce-roles]}})))
{:data {:middleware [wrap-enforce-roles]}})))
```
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:
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`.
* `: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):
* `reitit.ring.coercion/gen-wrap-coerce-parameters`
* `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}]
{:status 200
:body {:total (+ x y)}})}}]]
{:meta {:middleware [coercion/gen-wrap-coerce-parameters
{:data {:middleware [coercion/gen-wrap-coerce-parameters
coercion/gen-wrap-coerce-response]
: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}]
{:status 200
:body {:total (+ x y)}})}}]]
{:meta {:middleware [coercion/gen-wrap-coerce-parameters
{:data {:middleware [coercion/gen-wrap-coerce-parameters
coercion/gen-wrap-coerce-response]
:coercion spec/coercion}})))
```

View file

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

View file

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

View file

@ -111,9 +111,9 @@
;; 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)]
(as-> (parse-path path) $
(assoc $ :path-re (path-regex $))
@ -122,7 +122,7 @@
(path-matcher $)
#(if (#?(:clj .equals, :cljs =) path %) {}))
:result result
:meta meta})
:data data})
(dissoc $ :path-re :path-constraints)
(update $ :path-params set)
(set/rename-keys $ {:path-parts :parts

View file

@ -12,7 +12,7 @@
#(gen/fmap (fn [s] (str "/" s)) (s/gen string?))))
(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 ::raw-route
@ -26,7 +26,7 @@
(s/def ::route
(s/cat :path ::path
:meta ::meta
:data ::data
:result (s/? any?)))
(s/def ::routes
@ -40,7 +40,7 @@
(s/def ::router reitit/router?)
(s/def :reitit.router/path ::path)
(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/coerce fn?)
(s/def :reitit.router/compile fn?)
@ -51,7 +51,7 @@
(s/nilable
(s/keys :opt-un [:reitit.router/path
:reitit.router/routes
:reitit.router/meta
:reitit.router/data
:reitit.router/expand
:reitit.router/coerce
:reitit.router/compile

View file

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

View file

@ -107,14 +107,14 @@
(defn wrap-coerce-parameters
"Pluggable request coercion middleware.
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]
(fn
([request]
(let [method (:request-method request)
match (ring/get-match request)
parameters (-> match :result method :meta :parameters)
coercion (-> match :meta :coercion)]
parameters (-> match :result method :data :parameters)
coercion (-> match :data :coercion)]
(if (and coercion parameters)
(let [coercers (request-coercers coercion parameters)
coerced (coerce-parameters coercers request)]
@ -123,8 +123,8 @@
([request respond raise]
(let [method (:request-method request)
match (ring/get-match request)
parameters (-> match :result method :meta :parameters)
coercion (-> match :meta :coercion)]
parameters (-> match :result method :data :parameters)
coercion (-> match :data :coercion)]
(if (and coercion parameters)
(let [coercers (request-coercers coercion parameters)
coerced (coerce-parameters coercers request)]
@ -133,7 +133,7 @@
(def gen-wrap-coerce-parameters
"Generator for pluggable request coercion middleware.
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
{:name ::coerce-parameters
:gen-wrap (fn [{:keys [parameters coercion]} _]
@ -151,16 +151,16 @@
(defn wrap-coerce-response
"Pluggable response coercion middleware.
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]
(fn
([request]
(let [response (handler request)
method (:request-method request)
match (ring/get-match request)
responses (-> match :result method :meta :responses)
coercion (-> match :meta :coercion)
opts (-> match :meta :opts)]
responses (-> match :result method :data :responses)
coercion (-> match :data :coercion)
opts (-> match :data :opts)]
(if (and coercion responses)
(let [coercers (response-coercers coercion responses opts)]
(coerce-response coercers request response))
@ -168,9 +168,9 @@
([request respond raise]
(let [method (:request-method request)
match (ring/get-match request)
responses (-> match :result method :meta :responses)
coercion (-> match :meta :coercion)
opts (-> match :meta :opts)]
responses (-> match :result method :data :responses)
coercion (-> match :data :coercion)
opts (-> match :data :opts)]
(if (and coercion responses)
(let [coercers (response-coercers coercion responses opts)]
(handler request #(respond (coerce-response coercers request %))))
@ -179,7 +179,7 @@
(def gen-wrap-coerce-response
"Generator for pluggable response coercion middleware.
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
{:name ::coerce-response
:gen-wrap (fn [{:keys [responses coercion opts]} _]

View file

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

View file

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

View file

@ -30,7 +30,7 @@
{:keys [e]} :path} :parameters}]
{:status 200
: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 spec/coercion}}))]

View file

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

View file

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

View file

@ -102,16 +102,16 @@
(testing "all named routes can be matched"
(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]
(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)))
{:status 403, :body "forbidden"}
(handler request)))))
(deftest enforcing-meta-data-rules-at-runtime-test
(deftest enforcing-data-rules-at-runtime-test
(let [handler (constantly {:status 200, :body "ok"})
app (ring/ring-handler
(ring/router
@ -119,7 +119,7 @@
["/ping" handler]
["/admin" {::roles #{:admin}}
["/ping" handler]]]]
{:meta {:middleware [wrap-enforce-roles]}}))]
{:data {:middleware [wrap-enforce-roles]}}))]
(testing "public handler"
(is (= {:status 200, :body "ok"}

View file

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