Skip to content

Commit

Permalink
User-defined errors
Browse files Browse the repository at this point in the history
  • Loading branch information
ikitommi committed Oct 2, 2019
1 parent a6b5da8 commit 222f025
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 4 deletions.
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,52 @@ Detailed errors with `m/explain`:
; {:path [3 1 4 1 2], :in [:address :lonlat 1], :schema double?, :value nil})}
```

## Custom Error Messages

Schema properties `:error/message` and `:error/fn` can be used for human-readable errors:

```clj
(-> [int? {:error/message "should be an int"}]
(m/explain "kikka")
:errors
(first)
(m/error-message))
; "should be an int"

(-> [int? {:error/fn '(fn [schema value opts] (str "should be a int, was " (type value)))}]
(m/explain "kikka")
:errors
(first)
(m/error-message))
; "should be a int, was class java.lang.String"
```

Error property values can be wrapped into localication maps (default-locale `:en`):

```clj
(-> [int? {:error/message {:en "should be an int"
:fi "pitäisi olla numero"}}]
(m/explain "kikka")
:errors
(first)
(m/error-message {:locale :fi}))
; "pitäisi olla numero"
```

Schema-based defaults can be used:

```clj
(-> int?
(m/explain "kikka")
:errors
(first)
(m/error-message
{:locale :fi
:errors {'int? {:error/message {:en "should be an int"
:fi "pitäisi olla numero"}}}}))
; "pitäisi olla numero"
```

## Value Transformation

Schema-driven value transformations with `m/transform`:
Expand Down
4 changes: 2 additions & 2 deletions deps.edn
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
"-Dclojure.compiler.direct-linking=true"]}}
:deps {org.clojure/clojure {:mvn/version "1.10.1"}
borkdude/sci {:git/url "https://github.com/borkdude/sci"
:sha "e5cc4e422e2712fe25876ee601f782c2b0d02e7d"}
:sha "1463f84738120275bc58351232ecd1acdaf0fcb1"}
borkdude/edamame {:git/url "https://github.com/borkdude/edamame"
:sha "739bce6ad55f1ea563ee1ddceb8d0a7f41d0f85b"}
:sha "b577e565b136d3dd51945fe874049d4297946f57"}
org.clojure/test.check {:mvn/version "0.9.0"}
com.gfredericks/test.chuck {:mvn/version "0.2.10"}}}
16 changes: 14 additions & 2 deletions src/malli/core.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@
(clojure.core/name x))
x))

(defn eval [code]
(sci/eval-string (str code) {:preset :termination-safe}))
(defn eval [?code]
(if (fn? ?code) ?code (sci/eval-string (str ?code) {:preset :termination-safe})))

(defn fail!
([type]
Expand Down Expand Up @@ -663,6 +663,18 @@
([?schema value opts]
((explainer ?schema opts) value [] [])))

(defn error-message
([error]
(error-message error nil))
([{:keys [value schema]} {:keys [errors locale] :or {errors {}} :as opts}]
(let [maybe-localized (fn [x] (if (map? x) (get x (or locale :en)) x))
schema-properties (properties schema)
default-properties (errors (name schema))]
(or (if-let [fn (maybe-localized (:error/fn schema-properties))] ((eval fn) schema value opts))
(maybe-localized (:error/message schema-properties))
(if-let [fn (maybe-localized (:error/fn default-properties))] ((eval fn) schema value opts))
(maybe-localized (:error/message default-properties))))))

(defn transformer
"Creates a value transformer given a transformer and a schema."
([?schema t]
Expand Down
31 changes: 31 additions & 0 deletions test/malli/core_test.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@
(defn visitor [schema childs _]
(into [(m/name schema)] (seq childs)))

(deftest eval-test
(is (= 2 ((m/eval inc) 1)))
(is (= 2 ((m/eval 'inc) 1)))
(is (= 2 ((m/eval '#(inc %)) 1)))
(is (= 2 ((m/eval '#(inc %1)) 1)))
(is (= 2 ((m/eval '(fn [x] (inc x))) 1)))
(is (= 2 ((m/eval "(fn [x] (inc x))") 1))))

(deftest validation-test

(testing "coercion"
Expand Down Expand Up @@ -497,6 +505,29 @@
(is (= [2 1] (?path (m/explain [:map {:name int?} [:x int?]] {:x "1"}))))
(is (= [2 2] (?path (m/explain [:map {:name int?} [:x {:optional false} int?]] {:x "1"}))))))

(deftest error-message-test
(let [msg "should be an int"
fn1 (fn [_ value _] (str "should be an int, was " value))
fn2 '(fn [_ value _] (str "should be an int, was " value))]
(doseq [[schema value message opts]
[;; via schema
[[int? {:error/message msg}] "kikka" "should be an int"]
[[int? {:error/fn fn1}] "kikka" "should be an int, was kikka"]
[[int? {:error/fn fn2}] "kikka" "should be an int, was kikka"]
[[int? {:error/message msg, :error/fn fn2}] "kikka" "should be an int, was kikka"]
;; via defaults
[[int?] "kikka" "should be an int" {:errors {'int? {:error/message msg}}}]
[[int?] "kikka" "should be an int, was kikka" {:errors {'int? {:error/fn fn1}}}]
[[int?] "kikka" "should be an int, was kikka" {:errors {'int? {:error/fn fn2}}}]
[[int?] "kikka" "should be an int, was kikka" {:errors {'int? {:error/message msg, :error/fn fn2}}}]
;; both
[[int?
{:error/message msg, :error/fn fn2}]
"kikka" "should be an int, was kikka"
{:errors {'int? {:error/message "fail1", :error/fn (constantly "fail2")}}}]]]
(is (= message (-> (m/explain schema value) :errors first (m/error-message opts)))))))


(deftest properties-test
(testing "properties can be set and retrieved"
(let [properties {:title "kikka"}]
Expand Down

0 comments on commit 222f025

Please sign in to comment.