From fc73d02e01685bb5582e1b0c2779deb3eb4bb999 Mon Sep 17 00:00:00 2001 From: Paulo Feodrippe Date: Wed, 10 Aug 2022 21:20:35 -0400 Subject: [PATCH] add `:meta-merge-fn` option --- doc/advanced/configuring_routers.md | 25 ++++---- modules/reitit-core/src/reitit/impl.cljc | 6 +- .../reitit-core/src/reitit/interceptor.cljc | 4 +- .../reitit-core/src/reitit/middleware.cljc | 4 +- modules/reitit-http/src/reitit/http.cljc | 4 +- modules/reitit-ring/src/reitit/ring.cljc | 4 +- test/cljc/reitit/ring_coercion_test.cljc | 58 ++++++++++++++++++- 7 files changed, 81 insertions(+), 24 deletions(-) diff --git a/doc/advanced/configuring_routers.md b/doc/advanced/configuring_routers.md index 4cc30be9..35601c94 100644 --- a/doc/advanced/configuring_routers.md +++ b/doc/advanced/configuring_routers.md @@ -4,15 +4,16 @@ Routers can be configured via options. The following options are available for t | key | description |--------------|------------- -| `:path` | Base-path for routes -| `:routes` | Initial resolved routes (default `[]`) -| `:data` | Initial route data (default `{}`) -| `: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}) -| `: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 -| `:validate` | Function of `routes opts => ()` to validate route (data) via side-effects -| `:conflicts` | Function of `{route #{route}} => ()` to handle conflicting routes -| `: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 +| `:path` | Base-path for routes +| `:routes` | Initial resolved routes (default `[]`) +| `:data` | Initial route data (default `{}`) +| `: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}) +| `: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` +| `: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 +| `:compile` | Function of `route opts => result` to compile a route handler +| `:validate` | Function of `routes opts => ()` to validate route (data) via side-effects +| `:conflicts` | Function of `{route #{route}} => ()` to handle conflicting routes +| `: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 diff --git a/modules/reitit-core/src/reitit/impl.cljc b/modules/reitit-core/src/reitit/impl.cljc index 03c55e72..0805f8ff 100644 --- a/modules/reitit-core/src/reitit/impl.cljc +++ b/modules/reitit-core/src/reitit/impl.cljc @@ -60,17 +60,17 @@ (defn map-data [f 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 (fn [acc [k v]] (try - (mm/meta-merge acc {k v}) + ((or meta-merge-fn mm/meta-merge) acc {k v}) (catch #?(:clj Exception, :cljs js/Error) e (ex/fail! ::merge-data {:path p, :left acc, :right {k v}, :exception e})))) {} x)) (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))))) (defn path-conflicting-routes [routes opts] diff --git a/modules/reitit-core/src/reitit/interceptor.cljc b/modules/reitit-core/src/reitit/interceptor.cljc index 8d90c2e4..9b544638 100644 --- a/modules/reitit-core/src/reitit/interceptor.cljc +++ b/modules/reitit-core/src/reitit/interceptor.cljc @@ -155,8 +155,8 @@ :handler get-user}]])" ([data] (router data nil)) - ([data opts] - (let [opts (meta-merge {:compile compile-result} opts)] + ([data {:keys [meta-merge-fn] :as opts}] + (let [opts ((or meta-merge-fn meta-merge) {:compile compile-result} opts)] (r/router data opts)))) (defn interceptor-handler [router] diff --git a/modules/reitit-core/src/reitit/middleware.cljc b/modules/reitit-core/src/reitit/middleware.cljc index 49381b68..58c13f67 100644 --- a/modules/reitit-core/src/reitit/middleware.cljc +++ b/modules/reitit-core/src/reitit/middleware.cljc @@ -138,8 +138,8 @@ :handler get-user}]])" ([data] (router data nil)) - ([data opts] - (let [opts (meta-merge {:compile compile-result} opts)] + ([data {:keys [meta-merge-fn] :as opts}] + (let [opts ((or meta-merge-fn meta-merge) {:compile compile-result} opts)] (r/router data opts)))) (defn middleware-handler [router] diff --git a/modules/reitit-http/src/reitit/http.cljc b/modules/reitit-http/src/reitit/http.cljc index c85817f8..36e96504 100644 --- a/modules/reitit-http/src/reitit/http.cljc +++ b/modules/reitit-http/src/reitit/http.cljc @@ -14,7 +14,7 @@ (update acc method expand opts) 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) childs (cond-> childs (and (not (:options childs)) (not (:handler top)) default-options-endpoint) @@ -38,7 +38,7 @@ (->methods true top) (reduce-kv (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)))) (->methods (:handler top) data) childs)))) diff --git a/modules/reitit-ring/src/reitit/ring.cljc b/modules/reitit-ring/src/reitit/ring.cljc index 4aceaaf1..0439ab86 100644 --- a/modules/reitit-ring/src/reitit/ring.cljc +++ b/modules/reitit-ring/src/reitit/ring.cljc @@ -29,7 +29,7 @@ (update acc method expand opts) 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) childs (cond-> childs (and (not (:options childs)) (not (:handler top)) default-options-endpoint) @@ -50,7 +50,7 @@ (->methods true top) (reduce-kv (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)))) (->methods (:handler top) data) childs)))) diff --git a/test/cljc/reitit/ring_coercion_test.cljc b/test/cljc/reitit/ring_coercion_test.cljc index cf4c9092..fb9879bc 100644 --- a/test/cljc/reitit/ring_coercion_test.cljc +++ b/test/cljc/reitit/ring_coercion_test.cljc @@ -3,6 +3,9 @@ [malli.experimental.lite :as l] #?@(:clj [[muuntaja.middleware] [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.schema :as schema] [reitit.coercion.spec :as spec] @@ -208,6 +211,38 @@ (let [{:keys [status]} (app invalid-request2)] (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 (let [create (fn [middleware routes] (ring/ring-handler @@ -524,7 +559,28 @@ (is (= {:status 200, :body {:total -4}} (call "application/json" [:int {:encode/json -}])))) (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 (deftest muuntaja-test