Merge pull request #561 from pfeodrippe/meta-merge

add `:meta-merge-fn` option
This commit is contained in:
Tommi Reiman 2023-01-09 16:39:12 +02:00 committed by GitHub
commit 26a581298a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 81 additions and 24 deletions

View file

@ -4,15 +4,16 @@ Routers can be configured via options. The following options are available for t
| key | description | key | description
|--------------|------------- |--------------|-------------
| `:path` | Base-path for routes | `:path` | Base-path for routes
| `:routes` | Initial resolved routes (default `[]`) | `:routes` | Initial resolved routes (default `[]`)
| `:data` | Initial route data (default `{}`) | `:data` | Initial route data (default `{}`)
| `:spec` | clojure.spec definition for a route data, see `reitit.spec` on how to use this | `:spec` | clojure.spec definition for a route data, see `reitit.spec` on how to use this
| `:syntax` | Path-parameter syntax as keyword or set of keywords (default #{:bracket :colon}) | `:syntax` | Path-parameter syntax as keyword or set of keywords (default #{:bracket :colon})
| `:expand` | Function of `arg opts => data` to expand route arg to route 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 | `:meta-merge-fn` | Function which follows the signature of `meta-merge.core/meta-merge`, useful for when you want to have more control over the meta merging
| `:validate` | Function of `routes opts => ()` to validate route (data) via side-effects | `:compile` | Function of `route opts => result` to compile a route handler
| `:conflicts` | Function of `{route #{route}} => ()` to handle conflicting routes | `:validate` | Function of `routes opts => ()` to validate route (data) via side-effects
| `:exception` | Function of `Exception => Exception ` to handle creation time exceptions (default `reitit.exception/exception`) | `:conflicts` | Function of `{route #{route}} => ()` to handle conflicting routes
| `:router` | Function of `routes opts => router` to override the actual router implementation | `:exception` | Function of `Exception => Exception ` to handle creation time exceptions (default `reitit.exception/exception`)
| `:router` | Function of `routes opts => router` to override the actual router implementation

View file

@ -60,17 +60,17 @@
(defn map-data [f routes] (defn map-data [f routes]
(mapv (fn [[p ds]] [p (f p ds)]) routes)) (mapv (fn [[p ds]] [p (f p ds)]) routes))
(defn merge-data [p x] (defn merge-data [{:keys [meta-merge-fn] :as g} p x]
(reduce (reduce
(fn [acc [k v]] (fn [acc [k v]]
(try (try
(mm/meta-merge acc {k v}) ((or meta-merge-fn mm/meta-merge) acc {k v})
(catch #?(:clj Exception, :cljs js/Error) e (catch #?(:clj Exception, :cljs js/Error) e
(ex/fail! ::merge-data {:path p, :left acc, :right {k v}, :exception e})))) (ex/fail! ::merge-data {:path p, :left acc, :right {k v}, :exception e}))))
{} x)) {} x))
(defn resolve-routes [raw-routes {:keys [coerce] :as opts}] (defn resolve-routes [raw-routes {:keys [coerce] :as opts}]
(cond->> (->> (walk raw-routes opts) (map-data merge-data)) (cond->> (->> (walk raw-routes opts) (map-data #(merge-data opts %1 %2)))
coerce (into [] (keep #(coerce % opts))))) coerce (into [] (keep #(coerce % opts)))))
(defn path-conflicting-routes [routes opts] (defn path-conflicting-routes [routes opts]

View file

@ -155,8 +155,8 @@
:handler get-user}]])" :handler get-user}]])"
([data] ([data]
(router data nil)) (router data nil))
([data opts] ([data {:keys [meta-merge-fn] :as opts}]
(let [opts (meta-merge {:compile compile-result} opts)] (let [opts ((or meta-merge-fn meta-merge) {:compile compile-result} opts)]
(r/router data opts)))) (r/router data opts))))
(defn interceptor-handler [router] (defn interceptor-handler [router]

View file

@ -138,8 +138,8 @@
:handler get-user}]])" :handler get-user}]])"
([data] ([data]
(router data nil)) (router data nil))
([data opts] ([data {:keys [meta-merge-fn] :as opts}]
(let [opts (meta-merge {:compile compile-result} opts)] (let [opts ((or meta-merge-fn meta-merge) {:compile compile-result} opts)]
(r/router data opts)))) (r/router data opts))))
(defn middleware-handler [router] (defn middleware-handler [router]

View file

@ -14,7 +14,7 @@
(update acc method expand opts) (update acc method expand opts)
acc)) data ring/http-methods)]) acc)) data ring/http-methods)])
(defn compile-result [[path data] {:keys [::default-options-endpoint expand] :as opts}] (defn compile-result [[path data] {:keys [::default-options-endpoint expand meta-merge-fn] :as opts}]
(let [[top childs] (ring/group-keys data) (let [[top childs] (ring/group-keys data)
childs (cond-> childs childs (cond-> childs
(and (not (:options childs)) (not (:handler top)) default-options-endpoint) (and (not (:options childs)) (not (:handler top)) default-options-endpoint)
@ -38,7 +38,7 @@
(->methods true top) (->methods true top)
(reduce-kv (reduce-kv
(fn [acc method data] (fn [acc method data]
(let [data (meta-merge top data)] (let [data ((or meta-merge-fn meta-merge) top data)]
(assoc acc method (->endpoint path data method method)))) (assoc acc method (->endpoint path data method method))))
(->methods (:handler top) data) (->methods (:handler top) data)
childs)))) childs))))

View file

@ -29,7 +29,7 @@
(update acc method expand opts) (update acc method expand opts)
acc)) data http-methods)]) acc)) data http-methods)])
(defn compile-result [[path data] {:keys [::default-options-endpoint expand] :as opts}] (defn compile-result [[path data] {:keys [::default-options-endpoint expand meta-merge-fn] :as opts}]
(let [[top childs] (group-keys data) (let [[top childs] (group-keys data)
childs (cond-> childs childs (cond-> childs
(and (not (:options childs)) (not (:handler top)) default-options-endpoint) (and (not (:options childs)) (not (:handler top)) default-options-endpoint)
@ -50,7 +50,7 @@
(->methods true top) (->methods true top)
(reduce-kv (reduce-kv
(fn [acc method data] (fn [acc method data]
(let [data (meta-merge top data)] (let [data ((or meta-merge-fn meta-merge) top data)]
(assoc acc method (->endpoint path data method method)))) (assoc acc method (->endpoint path data method method))))
(->methods (:handler top) data) (->methods (:handler top) data)
childs)))) childs))))

View file

@ -3,6 +3,9 @@
[malli.experimental.lite :as l] [malli.experimental.lite :as l]
#?@(:clj [[muuntaja.middleware] #?@(:clj [[muuntaja.middleware]
[jsonista.core :as j]]) [jsonista.core :as j]])
[malli.core :as m]
[malli.util :as mu]
[meta-merge.core :refer [meta-merge]]
[reitit.coercion.malli :as malli] [reitit.coercion.malli :as malli]
[reitit.coercion.schema :as schema] [reitit.coercion.schema :as schema]
[reitit.coercion.spec :as spec] [reitit.coercion.spec :as spec]
@ -208,6 +211,38 @@
(let [{:keys [status]} (app invalid-request2)] (let [{:keys [status]} (app invalid-request2)]
(is (= 500 status)))))))) (is (= 500 status))))))))
(defn- custom-meta-merge-checking-schema
([] {})
([left] left)
([left right]
(cond
(and (map? left) (map? right))
(merge-with custom-meta-merge-checking-schema left right)
(and (m/schema? left)
(m/schema? right))
(mu/merge left right)
:else
(meta-merge left right)))
([left right & more]
(reduce custom-meta-merge-checking-schema left (cons right more))))
(defn- custom-meta-merge-checking-parameters
([] {})
([left] left)
([left right]
(if (and (map? left) (map? right)
(contains? left :parameters)
(contains? right :parameters))
(-> (merge-with custom-meta-merge-checking-parameters left right)
(assoc :parameters (merge-with mu/merge
(:parameters left)
(:parameters right))))
(meta-merge left right)))
([left right & more]
(reduce custom-meta-merge-checking-parameters left (cons right more))))
(deftest malli-coercion-test (deftest malli-coercion-test
(let [create (fn [middleware routes] (let [create (fn [middleware routes]
(ring/ring-handler (ring/ring-handler
@ -524,7 +559,28 @@
(is (= {:status 200, :body {:total -4}} (call "application/json" [:int {:encode/json -}])))) (is (= {:status 200, :body {:total -4}} (call "application/json" [:int {:encode/json -}]))))
(testing "edn encoding (nada)" (testing "edn encoding (nada)"
(is (= {:status 200, :body {:total +4}} (call "application/edn" [:int {:encode/json -}])))))))) (is (= {:status 200, :body {:total +4}} (call "application/edn" [:int {:encode/json -}]))))))
(testing "using custom meta-merge function"
(let [->app (fn [schema-fn meta-merge-fn]
(ring/ring-handler
(ring/router
["/merging-params/:foo" {:parameters {:path (schema-fn [:map [:foo :string]])}}
["/:bar" {:parameters {:path (schema-fn [:map [:bar :string]])}
:get {:handler (fn [{{{:keys [foo bar]} :path} :parameters}]
{:status 200
:body {:total (str "FOO: " foo ", "
"BAR: " bar)}})}}]]
{:data {:middleware [rrc/coerce-request-middleware
rrc/coerce-response-middleware]
:coercion malli/coercion}
:meta-merge-fn meta-merge-fn})))
call (fn [schema-fn meta-merge-fn]
((->app schema-fn meta-merge-fn) {:uri "/merging-params/this/that"
:request-method :get}))]
(is (= {:status 200, :body {:total "FOO: this, BAR: that"}} (call m/schema custom-meta-merge-checking-schema)))
(is (= {:status 200, :body {:total "FOO: this, BAR: that"}} (call identity custom-meta-merge-checking-parameters)))))))
#?(:clj #?(:clj
(deftest muuntaja-test (deftest muuntaja-test