From f26dc1ab19e1356861b8bdcfd243e2eb412bca20 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Fri, 10 Oct 2025 08:48:40 +0300 Subject: [PATCH 1/2] feat: :default-values-for-optional-keys for malli coercion --- doc/coercion/malli_coercion.md | 9 ++++++-- .../src/reitit/coercion/malli.cljc | 9 ++++---- test/cljc/reitit/coercion_test.cljc | 21 +++++++++++++++++++ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/doc/coercion/malli_coercion.md b/doc/coercion/malli_coercion.md index c7ecc34d..eb25b27c 100644 --- a/doc/coercion/malli_coercion.md +++ b/doc/coercion/malli_coercion.md @@ -73,9 +73,10 @@ Using `create` with options to create the coercion instead of `coercion`: {:transformers {:body {:default reitit.coercion.malli/default-transformer-provider :formats {"application/json" reitit.coercion.malli/json-transformer-provider}} :string {:default reitit.coercion.malli/string-transformer-provider} - :response {:default reitit.coercion.malli/default-transformer-provider}} + :response {:default reitit.coercion.malli/default-transformer-provider + :formats {"application/json" reitit.coercion.malli/json-transformer-provider}}} ;; set of keys to include in error messages - :error-keys #{:type :coercion :in :schema :value :errors :humanized #_:transformed} + :error-keys #{:type :coercion :in #_:schema :value #_:errors :humanized #_:transformed} ;; support lite syntax? :lite true ;; schema identity function (default: close all map schemas) @@ -88,6 +89,10 @@ Using `create` with options to create the coercion instead of `coercion`: :strip-extra-keys true ;; add/set default values :default-values true + ;; add/set defaults also for optional keys. Corresponds to :malli.transform/add-optional-keys + :default-values-for-optional-keys false + ;; encode-error + :encode-error nil ;; malli options :options nil}) ``` diff --git a/modules/reitit-malli/src/reitit/coercion/malli.cljc b/modules/reitit-malli/src/reitit/coercion/malli.cljc index 813bbc20..568f605d 100644 --- a/modules/reitit-malli/src/reitit/coercion/malli.cljc +++ b/modules/reitit-malli/src/reitit/coercion/malli.cljc @@ -9,8 +9,7 @@ [malli.swagger :as swagger] [malli.transform :as mt] [malli.util :as mu] - [reitit.coercion :as coercion] - [clojure.string :as string])) + [reitit.coercion :as coercion])) ;; ;; coercion @@ -27,11 +26,11 @@ (defn- -provider [transformer] (reify TransformationProvider - (-transformer [_ {:keys [strip-extra-keys default-values]}] + (-transformer [_ {:keys [strip-extra-keys default-values default-values-for-optional-keys]}] (mt/transformer (if strip-extra-keys (mt/strip-extra-keys-transformer)) transformer - (if default-values (mt/default-value-transformer)))))) + (if default-values (mt/default-value-transformer {:malli.transform/add-optional-keys default-values-for-optional-keys})))))) (def string-transformer-provider (-provider (mt/string-transformer))) (def json-transformer-provider (-provider (mt/json-transformer))) @@ -118,6 +117,8 @@ :strip-extra-keys true ;; add/set default values :default-values true + ;; add/set defaults also for optional keys. Corresponds to :malli.transform/add-optional-keys + :default-values-for-optional-keys false ;; encode-error :encode-error nil ;; malli options diff --git a/test/cljc/reitit/coercion_test.cljc b/test/cljc/reitit/coercion_test.cljc index 0fcfd125..e1f0cd20 100644 --- a/test/cljc/reitit/coercion_test.cljc +++ b/test/cljc/reitit/coercion_test.cljc @@ -140,6 +140,27 @@ (let [m (r/match-by-path r "/none/kikka/abba")] (is (= nil (coercion/coerce! m)))))))) +(deftest malli-query-parameter-coercion-test + (let [router (fn [coercion] + (r/router ["/test" + {:coercion coercion + :parameters {:query [:map + [:a [:string {:default "a"}]] + [:x {:optional true} [:keyword {:default :a}]]]}}] + {:compile coercion/compile-request-coercers}))] + (testing "default values for :optional query keys do not get added" + (is (= {:query {:a "a"}} + (-> (r/match-by-path (router reitit.coercion.malli/coercion) "/test") + (assoc :query-params {}) + (coercion/coerce!))))) + (testing "default values for :optional query keys get added when :default-values-for-optional-keys is set" + (is (= {:query {:a "a" :x :a}} + (-> (r/match-by-path (router (reitit.coercion.malli/create + (assoc reitit.coercion.malli/default-options + :default-values-for-optional-keys true))) "/test") + (assoc :query-params {}) + (coercion/coerce!))))))) + (defn match-by-path-and-coerce! [router path] (if-let [match (r/match-by-path router path)] (assoc match :parameters (coercion/coerce! match)))) From 67918a3f9c73d48fa41079b99bfe676de8495c40 Mon Sep 17 00:00:00 2001 From: Joel Kaasinen Date: Mon, 13 Oct 2025 15:16:16 +0300 Subject: [PATCH 2/2] feat: reuse :default-values config key instead of adding a new one --- doc/coercion/malli_coercion.md | 4 ++-- .../reitit-malli/src/reitit/coercion/malli.cljc | 10 +++++----- test/cljc/reitit/coercion_test.cljc | 15 ++++++++++++--- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/doc/coercion/malli_coercion.md b/doc/coercion/malli_coercion.md index eb25b27c..b193db11 100644 --- a/doc/coercion/malli_coercion.md +++ b/doc/coercion/malli_coercion.md @@ -88,9 +88,9 @@ Using `create` with options to create the coercion instead of `coercion`: ;; strip-extra-keys (affects only predefined transformers) :strip-extra-keys true ;; add/set default values + ;; Can be false, true or a map of options to pass to malli.transform/default-value-transformer, + ;; for example {:malli.transform/add-optional-keys true} :default-values true - ;; add/set defaults also for optional keys. Corresponds to :malli.transform/add-optional-keys - :default-values-for-optional-keys false ;; encode-error :encode-error nil ;; malli options diff --git a/modules/reitit-malli/src/reitit/coercion/malli.cljc b/modules/reitit-malli/src/reitit/coercion/malli.cljc index 568f605d..d8fbd663 100644 --- a/modules/reitit-malli/src/reitit/coercion/malli.cljc +++ b/modules/reitit-malli/src/reitit/coercion/malli.cljc @@ -26,11 +26,11 @@ (defn- -provider [transformer] (reify TransformationProvider - (-transformer [_ {:keys [strip-extra-keys default-values default-values-for-optional-keys]}] + (-transformer [_ {:keys [strip-extra-keys default-values]}] (mt/transformer (if strip-extra-keys (mt/strip-extra-keys-transformer)) transformer - (if default-values (mt/default-value-transformer {:malli.transform/add-optional-keys default-values-for-optional-keys})))))) + (if default-values (mt/default-value-transformer (if (map? default-values) default-values {}))))))) (def string-transformer-provider (-provider (mt/string-transformer))) (def json-transformer-provider (-provider (mt/json-transformer))) @@ -115,10 +115,10 @@ :enabled true ;; strip-extra-keys (affects only predefined transformers) :strip-extra-keys true - ;; add/set default values + ;; add/set default values. + ;; Can be false, true or a map of options to pass to malli.transform/default-value-transformer, + ;; for example {:malli.transform/add-optional-keys true} :default-values true - ;; add/set defaults also for optional keys. Corresponds to :malli.transform/add-optional-keys - :default-values-for-optional-keys false ;; encode-error :encode-error nil ;; malli options diff --git a/test/cljc/reitit/coercion_test.cljc b/test/cljc/reitit/coercion_test.cljc index e1f0cd20..5089ba29 100644 --- a/test/cljc/reitit/coercion_test.cljc +++ b/test/cljc/reitit/coercion_test.cljc @@ -153,13 +153,22 @@ (-> (r/match-by-path (router reitit.coercion.malli/coercion) "/test") (assoc :query-params {}) (coercion/coerce!))))) - (testing "default values for :optional query keys get added when :default-values-for-optional-keys is set" + (testing "default values for :optional query keys get added when :malli.transform/add-optional-keys is set" (is (= {:query {:a "a" :x :a}} (-> (r/match-by-path (router (reitit.coercion.malli/create (assoc reitit.coercion.malli/default-options - :default-values-for-optional-keys true))) "/test") + :default-values {:malli.transform/add-optional-keys true}))) "/test") (assoc :query-params {}) - (coercion/coerce!))))))) + (coercion/coerce!))))) + (testing "default values can be disabled" + (is (thrown-with-msg? + ExceptionInfo + #"Request coercion failed" + (-> (r/match-by-path (router (reitit.coercion.malli/create + (assoc reitit.coercion.malli/default-options + :default-values false))) "/test") + (assoc :query-params {}) + (coercion/coerce!))))))) (defn match-by-path-and-coerce! [router path] (if-let [match (r/match-by-path router path)]