mirror of
https://github.com/metosin/reitit.git
synced 2025-12-16 16:01:11 +00:00
BREAKING: route :meta => :data
This commit is contained in:
parent
a246bf4a9e
commit
2dd91d91d6
23 changed files with 172 additions and 171 deletions
|
|
@ -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"}
|
||||
|
|
|
|||
|
|
@ -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{...}
|
||||
|
|
|
|||
|
|
@ -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!`)
|
||||
|
|
|
|||
|
|
@ -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"}}
|
||||
|
|
|
|||
|
|
@ -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"}}
|
||||
|
|
|
|||
|
|
@ -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"}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]} _]
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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}})))
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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 []}}]]
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)))))
|
||||
|
||||
|
|
|
|||
|
|
@ -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]} _]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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}})))
|
||||
|
||||
|
|
|
|||
|
|
@ -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}}))]
|
||||
|
||||
|
|
|
|||
|
|
@ -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"))))))
|
||||
|
|
|
|||
|
|
@ -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))))))))))
|
||||
|
||||
|
|
|
|||
|
|
@ -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"}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
Loading…
Reference in a new issue