diff --git a/README.md b/README.md index 535dd651d..a0ac585b2 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,37 @@ Messages can be localized: ; :age "10, pitäisi olla > 18"} ``` +Top-level humanized map-errors are under `:malli/error`: + +```clj +(-> [:and [:map + [:password string?] + [:password2 string?]] + [:fn {:error/message "passwords don't match"} + '(fn [{:keys [password password2]}] + (= password password2))]] + (m/explain {:password "secret" + :password2 "faarao"}) + (me/humanize {:wrap :message})) +; {:malli/error "passwords don't match"} +``` + +Errors can be targetted using `:error/path` property: + +```clj +(-> [:and [:map + [:password string?] + [:password2 string?]] + [:fn {:error/message "passwords don't match" + :error/path [:password2]} + '(fn [{:keys [password password2]}] + (= password password2))]] + (m/explain {:password "secret" + :password2 "faarao"}) + (me/humanize {:wrap :message})) +; {:password2 "passwords don't match"} +``` + ## Value Transformation Schema-driven value transformations with `m/transform`: diff --git a/src/malli/error.cljc b/src/malli/error.cljc index a81987567..04c964231 100644 --- a/src/malli/error.cljc +++ b/src/malli/error.cljc @@ -78,10 +78,23 @@ acc acc :else error)) +(defn- -path [{:keys [schema]} + {:keys [locale default-locale] + :or {default-locale :en}}] + (let [properties (m/properties schema)] + (or (-maybe-localized (:error/path properties) locale) + (-maybe-localized (:error/path properties) default-locale)))) + ;; ;; public api ;; +(defn error-path + ([error] + (error-path error nil)) + ([error opts] + (into (:in error) (-path error opts)))) + (defn error-message ([error] (error-message error nil)) @@ -118,6 +131,6 @@ (if (coll? value) (reduce (fn [acc error] - (-assoc-in acc value (:in error) (f (with-error-message error opts)))) + (-assoc-in acc value (error-path error opts) (f (with-error-message error opts)))) nil errors) (f (with-error-message (first errors) opts)))))) diff --git a/test/malli/error_test.cljc b/test/malli/error_test.cljc index d9d11094d..9b2a594a4 100644 --- a/test/malli/error_test.cljc +++ b/test/malli/error_test.cljc @@ -107,16 +107,32 @@ (deftest composing-with-and-test (testing "top-level map-schemas are written in :malli/error" - (let [map-schema [:and [:map [:x int?] - [:y int?] - [:z int?]] - [:fn {:error/message "(> x y)"} - '(fn [{:keys [x y]}] (> x y))]]] + (let [schema [:and [:map + [:x int?] + [:y int?] + [:z int?]] + [:fn {:error/message "(> x y)"} + '(fn [{:keys [x y]}] (> x y))]]] (is (= {:z "should be int", :malli/error "(> x y)"} - (-> map-schema + (-> schema (m/explain {:x 1 :y 2, :z "1"}) - (me/humanize {:wrap :message})))))) + (me/humanize {:wrap :message}))))) + + (testing ":error/path contributes to path" + (let [schema [:and [:map + [:password string?] + [:password2 string?]] + [:fn {:error/message "passwords don't match" + :error/path [:password2]} + '(fn [{:keys [password password2]}] + (= password password2))]]] + + (is (= {:password2 "passwords don't match"} + (-> schema + (m/explain {:password "secret" + :password2 "faarao"}) + (me/humanize {:wrap :message}))))))) (testing "on collections, first error wins" (let [schema [:and