Skip to content

Instantly share code, notes, and snippets.

@mbezjak
Last active January 12, 2023 16:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mbezjak/a76b737cd6330e60b60c78b7e2c8fb9e to your computer and use it in GitHub Desktop.
Save mbezjak/a76b737cd6330e60b60c78b7e2c8fb9e to your computer and use it in GitHub Desktop.
Wrapping malli to produce own errors
{:malli/required "%s is required"
:malli/extra-key "System doesn't recognize property %s"
:malli/null "%s must be empty"
:malli/some "%s must not be null"
:malli/invalid-type "%s has invalid type"
:malli/invalid "%s value is invalid"
:malli/not "%s value is invalid"
:malli/boolean "%s must be a boolean"
:malli/true "%s must have a value of `true`"
:malli/false "%s must have a value of `false`"
:malli/enum "%s value must be one of: %s"
:malli/time "%s must be a temporal value"
:malli/number "%s must be a number"
:malli/int "%s must be a whole number"
:malli/integer "%s must be a whole number"
:malli/positive-int "%s must be a positive whole number"
:malli/negative-int "%s must be a negative whole number"
:malli/natural-int "%s must be a natural whole number"
:malli/positive "%s must be a positive number"
:malli/negative "%s must be a negative number"
:malli/zero "%s must be zero"
:malli/decimal "%s must be a big decimal"
:malli/float "%s must be a float"
:malli/double "%s must be a double"
:malli/ratio "%s must be a ratio"
:malli/rational "%s must be a rational number"
:malli/int-between "%s must be a whole number between %s and %s"
:malli/int-const "%s must be a number with exactly value %s"
:malli/int-with-max "%s must be a whole number lower than or equal to %s"
:malli/int-with-min "%s must be a whole number greater than or equal to %s"
:malli/greater-or-equal-to "%s must be greater or equal to %s"
:malli/greater-than "%s must be greater than %s"
:malli/lower-or-equal-to "%s must be lower or equal to %s"
:malli/lower-than "%s must be lower than %s"
:malli/char "%s must be a character"
:malli/string "%s must be a text"
:malli/string-with-length-between "%s must be a text with length between %s and %s"
:malli/string-with-length-max "%s must be a text with length at most %s"
:malli/string-with-length-min "%s must be a text with length at least %s"
:malli/bytes "%s must be a byte array"
:malli/uuid "%s must be UUID"
:malli/uri "%s must be URI"
:malli/symbol "%s must be a symbol"
:malli/qualified-symbol "%s must be a qualified symbol"
:malli/simple-symbol "%s must be a simple symbol"
:malli/keyword "%s must be a keyword"
:malli/qualified-keyword "%s must be a qualified keyword"
:malli/simple-keyword "%s must be a simple keyword"
:malli/ident "%s must be a symbol or a keyword"
:malli/qualified-ident "%s must be a qualified symbol or a keyword"
:malli/simple-ident "%s must be a simple symbol or a keyword"
:malli/empty "%s must be empty"
:malli/seq "%s must be a seq"
:malli/vector "%s must be a vector"
:malli/list "%s must be a list"
:malli/set "%s must be a set"
:malli/map "%s must be a map"
:malli/coll "%s must be a collection"
:malli/sequential "%s must be sequential"
:malli/associative "%s must be associative"
:malli/indexed "%s must be indexed"
:malli/seqable "%s must be seqable"
:malli/fn "%s must be fn"
:malli/ifn "%s must be ifn"
:malli/equal "%s must be %s"
:malli/not-equal "%s must not be %s"
:malli/size "%s size is invalid"
:malli/tuple-size "%s tuple size is invalid"
:malli/multi-dispatch-value "Dispatch value on %s is invalid"
:malli/input-remaining "%s has too many elements"}
(ns my.company.malli-validator
"A wrapper around malli, integrated with `my.company.errors`."
(:require
[clojure.set :as set]
[clojure.string :as string]
[malli.core :as malli]
[malli.error :as me]
[my.company.errors :as errors]
[my.company.kw :as kw]))
(def ^:private malli-code->error-code
{'nil? :malli/null
'some? :malli/some
'boolean? :malli/boolean
'true? :malli/true
'false? :malli/false
'number? :malli/number
'int? :malli/int
'integer? :malli/integer
'pos-int? :malli/positive-int
'neg-int? :malli/negative-int
'nat-int? :malli/natural-int
'pos? :malli/positive
'neg? :malli/negative
'zero? :malli/zero
'decimal? :malli/decimal
'float? :malli/float
'double? :malli/double
'ratio? :malli/ratio
'rational? :malli/rational
'char? :malli/char
'string? :malli/string
'bytes? :malli/bytes
'inst? :malli/time
'uuid? :malli/uuid
'uri? :malli/uri
'symbol? :malli/symbol
'qualified-symbol? :malli/qualified-symbol
'simple-symbol? :malli/simple-symbol
'keyword? :malli/keyword
'qualified-keyword? :malli/qualified-keyword
'simple-keyword? :malli/simple-keyword
'ident? :malli/ident
'qualified-ident? :malli/qualified-ident
'simple-ident? :malli/simple-ident
'empty? :malli/empty
'seq? :malli/seq
'vector? :malli/vector
'list? :malli/list
'set? :malli/set
'map? :malli/map
'coll? :malli/coll
'sequential? :malli/sequential
'associative? :malli/associative
'indexed? :malli/indexed
'seqable? :malli/seqable
'fn? :malli/fn
'ifn? :malli/ifn
:=> :malli/fn
:function :malli/fn
:boolean :malli/boolean
:int :malli/int
:double :malli/double
:string :malli/string
:uuid :malli/uuid
:symbol :malli/symbol
:qualified-symbol :malli/qualified-symbol
:keyword :malli/keyword
:qualified-keyword :malli/qualified-keyword
:> :malli/greater-than
:>= :malli/greater-or-equal-to
:< :malli/lower-than
:<= :malli/lower-or-equal-to
:= :malli/equal
:not= :malli/not-equal
:re :malli/invalid
:enum :malli/enum
:nil :malli/null
:not :malli/not
::malli/input-remaining :malli/input-remaining
::malli/missing-key :malli/required
::malli/extra-key :malli/extra-key
::malli/invalid-type :malli/invalid-type
::malli/limits :malli/size
::malli/tuple-size :malli/tuple-size
::malli/invalid-dispatch-value :malli/multi-dispatch-value})
;; Don't know how to reproduce those errors
(def ^:private cannot-happen-keys
#{'any?
:any
::malli/end-of-input
:malli.error/misspelled-key
:malli.error/misspelled-value
:malli.error/unknown})
(def ^:private no-malli-default-errors-keys #{:function :not})
(defn check-compatibility-with-malli! [message-tpls]
(let [malli-keys (-> me/default-errors keys set)
transition-keys (-> malli-code->error-code keys set)
extra-keys (set/difference transition-keys malli-keys cannot-happen-keys no-malli-default-errors-keys)
missing-keys (set/difference malli-keys transition-keys cannot-happen-keys)]
(assert (empty? extra-keys)
(format "Updated malli lib has compatibility issues. Transition keys are no longer needed: %s"
(string/join ", " (sort extra-keys))))
(assert (empty? missing-keys)
(format "Updated malli lib has compatibility issues. Transition keys are missing: %s"
(string/join ", " (sort missing-keys)))))
(let [translation-codes (-> malli-code->error-code vals set)
tpl-codes (-> message-tpls keys set)
missing-keys (set/difference translation-codes tpl-codes)]
(assert (empty? missing-keys)
(format "Missing codes in error-templates.edn: %s"
(string/join ", " (sort missing-keys))))))
(defn- extract-from-fn-props [props]
(let [path (:error/path props)
code (:error/code props)]
(merge {:code (or code :malli/invalid)}
(when path
{::path path
::in path}))))
(defn- expand-int-type [{:keys [min max]} value]
(cond
(nil? value) {:code :malli/required}
(not (int? value)) {:code :malli/int}
(and min (= min max)) {:code :malli/int-const :args [min]}
(and min max) {:code :malli/int-between :args [min max]}
min {:code :malli/int-with-min :args [min]}
max {:code :malli/int-with-max :args [max]}))
(defn- expand-string-type [{:keys [min max]} value]
(cond
(nil? value) {:code :malli/required}
(not (string? value)) {:code :malli/string}
(and min (empty? value)) {:code :malli/required}
(and min max) {:code :malli/string-with-length-between :args [min max]}
min {:code :malli/string-with-length-min :args [min]}
max {:code :malli/string-with-length-max :args [max]}))
(defn- humanize-field [field]
(if (keyword? field)
(kw/str field)
field))
(defn- assoc-breadcrumb-info [error]
(let [in (::in error)
human-breadcrumbs (str "[" (string/join " -> " (map humanize-field in)) "]")]
(assoc error
:breadcrumbs in
:args (cons human-breadcrumbs (:args error)))))
(defn- malli->error [root-schema root-value malli-error]
(let [error-schema (:schema malli-error)
error-value (:value malli-error)
schema-type (malli/type error-schema)
schema-props (malli/properties error-schema)
schema-children (malli/children error-schema)]
(assoc-breadcrumb-info
(merge {::root-schema root-schema
::root-value root-value
::schema error-schema
::value error-value
::path (:path malli-error)
::in (:in malli-error)}
(if-let [type (:type malli-error)]
{:code (get malli-code->error-code type)}
{:code (get malli-code->error-code schema-type)})
(case schema-type
:enum {:args [(string/join ", " schema-children)]}
:> {:args [(first schema-children)]}
:>= {:args [(first schema-children)]}
:< {:args [(first schema-children)]}
:<= {:args [(first schema-children)]}
:= {:args [(first schema-children)]}
:not= {:args [(first schema-children)]}
:fn (extract-from-fn-props schema-props)
:int (expand-int-type schema-props error-value)
:string (expand-string-type schema-props error-value)
nil)))))
(defn validate [schema value]
(when-let [{:keys [schema value errors]} (malli/explain schema value)]
(->> errors
(map #(malli->error schema value %))
(errors/make))))
(ns my.company.malli-validator-test
(:require
[clojure.test :refer [deftest is testing]]
[my.company.error :as error]
[my.company.errors :as errors]
[my.company.malli-validator :as sut])
(:import
(java.net URI)))
(deftest validate
(testing "Error keys collected"
(let [expected-keys #{::sut/root-schema ::sut/root-value ::sut/schema ::sut/value ::sut/path ::sut/in
:my.company.error/code :my.company.error/breadcrumbs :my.company.error/args}]
(is (= expected-keys (set (keys (first (sut/validate [:int] "a"))))))
(is (= expected-keys (set (keys (first (sut/validate [:map [:a :int]] {:a "a"}))))))))
(testing "Path values"
(is (= [[0] [1]] (map ::sut/path (sut/validate [:and :string [:re #"a+"]] nil))))
(is (= [[1]] (map ::sut/path (sut/validate [:and :string [:re #"a+"]] ""))))
(is (= [[:a]] (map ::sut/path (sut/validate [:fn {:error/path [:a]} (constantly false)] {}))))
(is (= [[]] (map ::sut/path (sut/validate [:fn (constantly false)] {})))))
(testing "Breadcrumbs"
(is (= [[]] (map error/breadcrumbs (sut/validate [:int] ""))))
(is (= [[:a]] (map error/breadcrumbs (sut/validate [:map [:a :int]] {}))))
(is (= [[:a]] (map error/breadcrumbs (sut/validate [:fn {:error/path [:a]} (constantly false)] {})))))
(testing "Arguments"
(is (= ["[]"] (error/args (first (sut/validate [:int] "")))))
(is (= ["[a]"] (error/args (first (sut/validate [:map [:a :int]] {})))))
(is (= ["[test-batch-code]"] (error/args (first (sut/validate [:map [:test-batch-code :string]] {})))))
(is (= ["[a]"] (error/args (first (sut/validate [:fn {:error/path [:a]} (constantly false)] {})))))
(is (= ["[0]"] (error/args (first (sut/validate [:tuple :int :int] ["a" 2])))))
(is (= ["[a -> 0 -> b]"] (error/args (first (sut/validate [:map [:a [:sequential [:map [:b :string]]]]] {:a [{:b 2}]})))))
(is (= ["[]" 1] (error/args (first (sut/validate [:= 1] 2)))))
(is (= ["[]" 1] (error/args (first (sut/validate [:not= 1] 1))))))
;; Schema categories: https://github.com/metosin/malli#built-in-schemas
(testing "malli.core/predicate-schemas"
(is (nil? (sut/validate [any?] nil)))
(is (nil? (sut/validate [any?] 1)))
(is (nil? (sut/validate [nil?] nil)))
(is (= [:malli/null] (errors/codes (sut/validate [nil?] 1))))
(is (nil? (sut/validate [some?] 1)))
(is (nil? (sut/validate [some?] "")))
(is (= [:malli/some] (errors/codes (sut/validate [some?] nil))))
(is (nil? (sut/validate [boolean?] true)))
(is (nil? (sut/validate [boolean?] false)))
(is (= [:malli/boolean] (errors/codes (sut/validate [boolean?] nil))))
(is (nil? (sut/validate [true?] true)))
(is (= [:malli/true] (errors/codes (sut/validate [true?] nil))))
(is (nil? (sut/validate [false?] false)))
(is (= [:malli/false] (errors/codes (sut/validate [false?] nil))))
(is (nil? (sut/validate [number?] 1)))
(is (= [:malli/number] (errors/codes (sut/validate [number?] nil))))
(is (nil? (sut/validate [int?] 1)))
(is (= [:malli/int] (errors/codes (sut/validate [int?] nil))))
(is (= [:malli/int] (errors/codes (sut/validate [int?] "a"))))
(is (nil? (sut/validate [integer?] 1)))
(is (nil? (sut/validate [integer?] 1N)))
(is (= [:malli/integer] (errors/codes (sut/validate [integer?] 1.0))))
(is (nil? (sut/validate [pos-int?] 1)))
(is (= [:malli/positive-int] (errors/codes (sut/validate [pos-int?] nil))))
(is (nil? (sut/validate [neg-int?] -1)))
(is (= [:malli/negative-int] (errors/codes (sut/validate [neg-int?] nil))))
(is (nil? (sut/validate [nat-int?] 0)))
(is (nil? (sut/validate [nat-int?] 1)))
(is (= [:malli/natural-int] (errors/codes (sut/validate [nat-int?] nil))))
(is (nil? (sut/validate [pos?] 1)))
(is (= [:malli/positive] (errors/codes (sut/validate [pos?] 0))))
(is (nil? (sut/validate [neg?] -1)))
(is (= [:malli/negative] (errors/codes (sut/validate [neg?] 0))))
(is (nil? (sut/validate [zero?] 0)))
(is (nil? (sut/validate [zero?] 0.0)))
(is (= [:malli/zero] (errors/codes (sut/validate [zero?] 1))))
(is (nil? (sut/validate [decimal?] 1.1M)))
(is (= [:malli/decimal] (errors/codes (sut/validate [decimal?] 1))))
(is (nil? (sut/validate [float?] (float 1.1))))
(is (= [:malli/float] (errors/codes (sut/validate [float?] 1))))
(is (nil? (sut/validate [double?] 1.1)))
(is (= [:malli/double] (errors/codes (sut/validate [double?] 1))))
(is (nil? (sut/validate [ratio?] 1/2)))
(is (= [:malli/ratio] (errors/codes (sut/validate [ratio?] 1))))
(is (nil? (sut/validate [rational?] 1)))
(is (nil? (sut/validate [rational?] 1.0M)))
(is (nil? (sut/validate [rational?] 1/2)))
(is (= [:malli/rational] (errors/codes (sut/validate [rational?] nil))))
(is (nil? (sut/validate [char?] \a)))
(is (= [:malli/char] (errors/codes (sut/validate [char?] nil))))
(is (nil? (sut/validate [string?] "")))
(is (nil? (sut/validate [string?] "abc")))
(is (= [:malli/string] (errors/codes (sut/validate [string?] nil))))
(is (= [:malli/string] (errors/codes (sut/validate [string?] 1))))
(is (nil? (sut/validate [bytes?] (byte-array [1 2]))))
(is (= [:malli/bytes] (errors/codes (sut/validate [bytes?] nil))))
(is (nil? (sut/validate [inst?] #inst "2020-12-31")))
(is (nil? (sut/validate [inst?] #inst "2020-12-31T10:20:30")))
(is (= [:malli/time] (errors/codes (sut/validate [inst?] nil))))
(is (nil? (sut/validate [uuid?] #uuid "cdc87015-bafa-4f58-b52e-edcc1d9905bc")))
(is (= [:malli/uuid] (errors/codes (sut/validate [uuid?] nil))))
(is (nil? (sut/validate [uri?] (URI. "index.html"))))
(is (= [:malli/uri] (errors/codes (sut/validate [uri?] nil))))
(is (nil? (sut/validate [symbol?] 'a)))
(is (nil? (sut/validate [symbol?] 'a/b)))
(is (= [:malli/symbol] (errors/codes (sut/validate [symbol?] nil))))
(is (nil? (sut/validate [qualified-symbol?] 'a/b)))
(is (= [:malli/qualified-symbol] (errors/codes (sut/validate [qualified-symbol?] nil))))
(is (nil? (sut/validate [simple-symbol?] 'a)))
(is (= [:malli/simple-symbol] (errors/codes (sut/validate [simple-symbol?] nil))))
(is (nil? (sut/validate [keyword?] :a)))
(is (nil? (sut/validate [keyword?] :a/b)))
(is (= [:malli/keyword] (errors/codes (sut/validate [keyword?] nil))))
(is (nil? (sut/validate [qualified-keyword?] :a/b)))
(is (= [:malli/qualified-keyword] (errors/codes (sut/validate [qualified-keyword?] nil))))
(is (nil? (sut/validate [simple-keyword?] :a)))
(is (= [:malli/simple-keyword] (errors/codes (sut/validate [simple-keyword?] nil))))
(is (nil? (sut/validate [ident?] :a)))
(is (nil? (sut/validate [ident?] :a/b)))
(is (nil? (sut/validate [ident?] 'a)))
(is (nil? (sut/validate [ident?] 'a/b)))
(is (= [:malli/ident] (errors/codes (sut/validate [ident?] nil))))
(is (nil? (sut/validate [qualified-ident?] :a/b)))
(is (nil? (sut/validate [qualified-ident?] 'a/b)))
(is (= [:malli/qualified-ident] (errors/codes (sut/validate [qualified-ident?] nil))))
(is (nil? (sut/validate [simple-ident?] :a)))
(is (nil? (sut/validate [simple-ident?] 'a)))
(is (= [:malli/simple-ident] (errors/codes (sut/validate [simple-ident?] nil))))
(is (nil? (sut/validate [empty?] "")))
(is (nil? (sut/validate [empty?] '())))
(is (nil? (sut/validate [empty?] [])))
(is (nil? (sut/validate [empty?] #{})))
(is (nil? (sut/validate [empty?] {})))
(is (= [:malli/empty] (errors/codes (sut/validate [empty?] [:a :b]))))
(is (nil? (sut/validate [seq?] '())))
(is (nil? (sut/validate [seq?] (range 1 3))))
(is (= [:malli/seq] (errors/codes (sut/validate [seq?] ""))))
(is (nil? (sut/validate [vector?] [])))
(is (= [:malli/vector] (errors/codes (sut/validate [vector?] ""))))
(is (nil? (sut/validate [list?] '())))
(is (= [:malli/list] (errors/codes (sut/validate [list?] ""))))
(is (nil? (sut/validate [set?] #{})))
(is (= [:malli/set] (errors/codes (sut/validate [set?] ""))))
(is (nil? (sut/validate [map?] {})))
(is (= [:malli/map] (errors/codes (sut/validate [map?] ""))))
(is (nil? (sut/validate [coll?] '())))
(is (nil? (sut/validate [coll?] [])))
(is (nil? (sut/validate [coll?] #{})))
(is (nil? (sut/validate [coll?] {})))
(is (= [:malli/coll] (errors/codes (sut/validate [coll?] ""))))
(is (nil? (sut/validate [sequential?] '())))
(is (nil? (sut/validate [sequential?] [])))
(is (= [:malli/sequential] (errors/codes (sut/validate [sequential?] ""))))
(is (nil? (sut/validate [associative?] [])))
(is (nil? (sut/validate [associative?] {})))
(is (= [:malli/associative] (errors/codes (sut/validate [associative?] ""))))
(is (nil? (sut/validate [indexed?] [])))
(is (= [:malli/indexed] (errors/codes (sut/validate [indexed?] ""))))
(is (nil? (sut/validate [seqable?] nil)))
(is (nil? (sut/validate [seqable?] "")))
(is (nil? (sut/validate [seqable?] '())))
(is (nil? (sut/validate [seqable?] [])))
(is (nil? (sut/validate [seqable?] #{})))
(is (nil? (sut/validate [seqable?] {})))
(is (= [:malli/seqable] (errors/codes (sut/validate [seqable?] 1))))
(is (nil? (sut/validate [fn?] (fn []))))
(is (nil? (sut/validate [fn?] #())))
(is (nil? (sut/validate [fn?] inc)))
(is (= [:malli/fn] (errors/codes (sut/validate [fn?] 1))))
(is (= [:malli/fn] (errors/codes (sut/validate [fn?] {}))))
(is (nil? (sut/validate [ifn?] (fn []))))
(is (nil? (sut/validate [ifn?] #())))
(is (nil? (sut/validate [ifn?] inc)))
(is (nil? (sut/validate [ifn?] {})))
(is (= [:malli/ifn] (errors/codes (sut/validate [ifn?] 1)))))
(testing "malli.core/comparator-schemas"
(is (nil? (sut/validate [:> 1] 2)))
(is (= [:malli/greater-than] (errors/codes (sut/validate [:> 1] 0))))
(is (nil? (sut/validate [:>= 1] 1)))
(is (= [:malli/greater-or-equal-to] (errors/codes (sut/validate [:>= 1] 0))))
(is (nil? (sut/validate [:< 1] 0)))
(is (= [:malli/lower-than] (errors/codes (sut/validate [:< 1] 2))))
(is (nil? (sut/validate [:<= 1] 1)))
(is (= [:malli/lower-or-equal-to] (errors/codes (sut/validate [:<= 1] 2))))
(is (nil? (sut/validate [:= 1] 1)))
(is (= [:malli/equal] (errors/codes (sut/validate [:= 1] nil))))
(is (nil? (sut/validate [:not= 1] 0)))
(is (= [:malli/not-equal] (errors/codes (sut/validate [:not= 1] 1)))))
(testing "malli.core/type-schemas"
(is (nil? (sut/validate [:any] nil)))
(is (nil? (sut/validate [:any] 1)))
(is (nil? (sut/validate [:nil] nil)))
(is (= [:malli/null] (errors/codes (sut/validate [:nil] "a"))))
(is (nil? (sut/validate [:boolean] true)))
(is (nil? (sut/validate [:boolean] false)))
(is (= [:malli/boolean] (errors/codes (sut/validate [:boolean] nil))))
(is (= [:malli/boolean] (errors/codes (sut/validate [:boolean] "a"))))
(is (nil? (sut/validate [:int] 1)))
(is (nil? (sut/validate [:int {:min 1}] 1)))
(is (nil? (sut/validate [:int {:max 1}] 1)))
(is (nil? (sut/validate [:int {:min 1 :max 2}] 1)))
(is (nil? (sut/validate [:int {:min 1 :max 1}] 1)))
(is (= [:malli/required] (errors/codes (sut/validate [:int] nil))))
(is (= [:malli/required] (errors/codes (sut/validate [:int {:min 1}] nil))))
(is (= [:malli/int] (errors/codes (sut/validate [:int] "a"))))
(is (= [:malli/int] (errors/codes (sut/validate [:int {:min 1}] "a"))))
(is (= [:malli/int-with-min] (errors/codes (sut/validate [:int {:min 1}] 0))))
(is (= [:malli/int-with-max] (errors/codes (sut/validate [:int {:max 1}] 2))))
(is (= [:malli/int-between] (errors/codes (sut/validate [:int {:min 1 :max 2}] 0))))
(is (= [:malli/int-const] (errors/codes (sut/validate [:int {:min 1 :max 1}] 0))))
(is (nil? (sut/validate [:double] 1.1)))
(is (= [:malli/double] (errors/codes (sut/validate [:double] 1))))
(is (nil? (sut/validate [:string] "")))
(is (nil? (sut/validate [:string] "abc")))
(is (nil? (sut/validate [:string {:min 1}] "a")))
(is (nil? (sut/validate [:string {:max 1}] "a")))
(is (nil? (sut/validate [:string {:min 1 :max 2}] "a")))
(is (nil? (sut/validate [:string {:min 1 :max 1}] "a")))
(is (= [:malli/required] (errors/codes (sut/validate [:string] nil))))
(is (= [:malli/required] (errors/codes (sut/validate [:string {:min 1}] ""))))
(is (= [:malli/string] (errors/codes (sut/validate [:string] 1))))
(is (= [:malli/string] (errors/codes (sut/validate [:string] 1))))
(is (= [:malli/string-with-length-min] (errors/codes (sut/validate [:string {:min 2}] "a"))))
(is (= [:malli/string-with-length-max] (errors/codes (sut/validate [:string {:max 1}] "abc"))))
(is (= [:malli/string-with-length-between] (errors/codes (sut/validate [:string {:min 1 :max 2}] "abc"))))
(is (nil? (sut/validate [:uuid] #uuid "cdc87015-bafa-4f58-b52e-edcc1d9905bc")))
(is (= [:malli/uuid] (errors/codes (sut/validate [:uuid] "1"))))
(is (nil? (sut/validate [:symbol] 'a)))
(is (nil? (sut/validate [:symbol] 'a/b)))
(is (= [:malli/symbol] (errors/codes (sut/validate [:symbol] 1))))
(is (nil? (sut/validate [:qualified-symbol] 'a/b)))
(is (= [:malli/qualified-symbol] (errors/codes (sut/validate [:qualified-symbol] 'a))))
(is (nil? (sut/validate [:keyword] :a)))
(is (nil? (sut/validate [:keyword] :a/b)))
(is (= [:malli/keyword] (errors/codes (sut/validate [:keyword] 1))))
(is (nil? (sut/validate [:qualified-keyword] :a/b)))
(is (= [:malli/qualified-keyword] (errors/codes (sut/validate [:qualified-keyword] :a)))))
(testing "malli.core/sequence-schemas"
(is (nil? (sut/validate [:+ :string] ["a"])))
(is (= [:malli/required] (errors/codes (sut/validate [:+ :string] []))))
(is (nil? (sut/validate [:* :string] [])))
(is (= [:malli/invalid-type] (errors/codes (sut/validate [:* :string] nil))))
(is (nil? (sut/validate [:? :string] [])))
(is (nil? (sut/validate [:? :string] ["a"])))
(is (= [:malli/input-remaining] (errors/codes (sut/validate [:? :string] ["a" "b"]))))
(is (nil? (sut/validate [:repeat {:min 1 :max 2} :string] ["a"])))
(is (nil? (sut/validate [:repeat {:min 1 :max 2} :string] ["a" "b"])))
(is (= [:malli/required] (errors/codes (sut/validate [:repeat {:min 1 :max 2} :string] []))))
(is (= [:malli/required] (errors/codes (sut/validate [:repeat {:min 2 :max 3} :string] ["a"]))))
(is (= [:malli/input-remaining] (errors/codes (sut/validate [:repeat {:min 1 :max 2} :string] ["a" "b" "c"]))))
(is (nil? (sut/validate [:cat :string :int] ["a" 1])))
(is (= [:malli/required] (errors/codes (sut/validate [:cat :string :int] []))))
(is (= [:malli/required] (errors/codes (sut/validate [:cat :string :int] ["a"]))))
(is (= [:malli/input-remaining] (errors/codes (sut/validate [:cat :string :int] ["a" 1 1]))))
(is (nil? (sut/validate [:catn [:s :string] [:i :int]] ["a" 1])))
(is (= [:malli/required] (errors/codes (sut/validate [:catn [:s :string] [:i :int]] []))))
(is (= [:malli/required] (errors/codes (sut/validate [:catn [:s :string] [:i :int]] ["a"]))))
(is (= [:malli/input-remaining] (errors/codes (sut/validate [:catn [:s :string] [:i :int]] ["a" 1 1]))))
(is (nil? (sut/validate [:alt :string :int] ["a"])))
(is (nil? (sut/validate [:alt :string :int] [1])))
(is (= [:malli/required :malli/required] (errors/codes (sut/validate [:alt :string :int] []))))
(is (= [:malli/input-remaining] (errors/codes (sut/validate [:alt :string :int] ["a" 1]))))
(is (nil? (sut/validate [:altn [:s :string] [:i :int]] ["a"])))
(is (nil? (sut/validate [:altn [:s :string] [:i :int]] [1])))
(is (= [:malli/required :malli/required] (errors/codes (sut/validate [:altn [:s :string] [:i :int]] []))))
(is (= [:malli/input-remaining] (errors/codes (sut/validate [:altn [:s :string] [:i :int]] ["a" 1])))))
(testing "malli.core/base-schemas"
(is (nil? (sut/validate [:re "a+"] "a")))
(is (nil? (sut/validate [:re "abc"] "abcdef")))
(is (= [:malli/invalid] (errors/codes (sut/validate [:re #"a+"] nil))))
(is (= [:malli/invalid] (errors/codes (sut/validate [:re #"a+"] ""))))
(is (= [:malli/invalid] (errors/codes (sut/validate [:re #"^abc$"] "abcdef"))))
(is (nil? (sut/validate [:enum "a" "b"] "a")))
(is (= [:malli/enum] (errors/codes (sut/validate [:enum "a" "b"] nil))))
(is (= [:malli/enum] (errors/codes (sut/validate [:enum "a" "b"] "c"))))
(is (nil? (sut/validate [:map [:a :boolean]] {:a true})))
(is (nil? (sut/validate [:map [:a :string]] {:a "a"})))
(is (nil? (sut/validate [:map [:a :int]] {:a 1})))
(is (nil? (sut/validate [:map {:closed true} [:a :int]] {:a 1})))
(is (= [:malli/required] (errors/codes (sut/validate [:map [:a :boolean]] {}))))
(is (= [:malli/required] (errors/codes (sut/validate [:map [:a :string]] {}))))
(is (= [:malli/required] (errors/codes (sut/validate [:map [:a :int]] {}))))
(is (= [:malli/extra-key] (errors/codes (sut/validate [:map {:closed true} [:a :int]] {:a 1 :b 2}))))
(is (= [:malli/int] (errors/codes (sut/validate [:map [:a :int]] {:a "abc"}))))
(is (= [:malli/string] (errors/codes (sut/validate [:map [:a :string]] {:a 1}))))
(is (= [:malli/invalid-type] (errors/codes (sut/validate [:map [:a :int]] nil))))
(is (nil? (sut/validate [:map-of :keyword :string] {})))
(is (nil? (sut/validate [:map-of :keyword :string] {:a "a"})))
(is (nil? (sut/validate [:map-of :keyword :string] {:a "a" :b "b"})))
(is (= [:malli/string] (errors/codes (sut/validate [:map-of :keyword :string] {:a 1}))))
(is (= [:malli/keyword] (errors/codes (sut/validate [:map-of :keyword :string] {"a" "a"}))))
(is (nil? (sut/validate [:tuple :int :int] [1 2])))
(is (nil? (sut/validate [:tuple :int :string] [1 "a"])))
(is (= [:malli/int] (errors/codes (sut/validate [:tuple :int :int] ["a" 2]))))
(is (= [:malli/int :malli/string] (errors/codes (sut/validate [:tuple :int :string] ["a" 2]))))
(is (= [:malli/tuple-size] (errors/codes (sut/validate [:tuple :int :int] [1]))))
(is (nil? (sut/validate [:vector :int] [])))
(is (nil? (sut/validate [:vector {:min 1} :int] [1])))
(is (= [:malli/int] (errors/codes (sut/validate [:vector :int] ["a"]))))
(is (= [:malli/size] (errors/codes (sut/validate [:vector {:min 1} :int] []))))
(is (nil? (sut/validate [:set :int] #{})))
(is (nil? (sut/validate [:set {:min 1} :int] #{1})))
(is (= [:malli/int] (errors/codes (sut/validate [:set :int] #{"a"}))))
(is (= [:malli/size] (errors/codes (sut/validate [:set {:min 1} :int] #{}))))
(is (nil? (sut/validate [:sequential :int] [])))
(is (nil? (sut/validate [:sequential :int] '())))
(is (nil? (sut/validate [:sequential {:min 1} :int] [1])))
(is (nil? (sut/validate [:sequential {:min 1} :int] '(1))))
(is (= [:malli/int] (errors/codes (sut/validate [:sequential :int] ["a"]))))
(is (= [:malli/int] (errors/codes (sut/validate [:sequential :int] '("a")))))
(is (= [:malli/size] (errors/codes (sut/validate [:sequential {:min 1} :int] []))))
(is (= [:malli/size] (errors/codes (sut/validate [:sequential {:min 1} :int] '()))))
(is (nil? (sut/validate [:and :string [:re #"a+"]] "a")))
(is (= [:malli/required :malli/invalid] (errors/codes (sut/validate [:and :string [:re #"a+"]] nil))))
(is (= [:malli/invalid] (errors/codes (sut/validate [:and :string [:re #"a+"]] "b"))))
(is (nil? (sut/validate [:or pos-int? neg-int?] 1)))
(is (nil? (sut/validate [:or pos-int? neg-int?] -1)))
(is (= [:malli/positive-int :malli/negative-int] (errors/codes (sut/validate [:or pos-int? neg-int?] 0))))
(is (= [:malli/positive-int :malli/negative-int] (errors/codes (sut/validate [:or pos-int? neg-int?] nil))))
(is (nil? (sut/validate [:orn [:p pos-int?] [:n neg-int?]] 1)))
(is (nil? (sut/validate [:orn [:p pos-int?] [:n neg-int?]] -1)))
(is (= [:malli/positive-int :malli/negative-int] (errors/codes (sut/validate [:orn [:p pos-int?] [:n neg-int?]] 0))))
(is (= [:malli/positive-int :malli/negative-int] (errors/codes (sut/validate [:orn [:p pos-int?] [:n neg-int?]] nil))))
(is (nil? (sut/validate [:not :int] :a)))
(is (nil? (sut/validate [:not :int] "a")))
(is (= [:malli/not] (errors/codes (sut/validate [:not :int] 1))))
(is (nil? (sut/validate [:maybe :int] 1)))
(is (nil? (sut/validate [:maybe :int] nil)))
(is (= [:malli/int] (errors/codes (sut/validate [:maybe :int] "a"))))
(is (= [:malli/invalid] (errors/codes (sut/validate [:fn (constantly false)] {}))))
(is (= [:test.batch/invalid] (errors/codes (sut/validate [:fn {:error/code :test.batch/invalid}
(constantly false)]
{}))))
(is (nil? (sut/validate [:=> [:cat :double] :double] (fn ^double [^double x] x))))
(is (= [:malli/fn] (errors/codes (sut/validate [:=> [:cat :double] :double] 1.5))))
(is (nil? (sut/validate [:function
[:=> [:cat :double] :double]
[:=> [:cat :double :double] :double]]
(fn ^double
([^double x] x)
([^double x ^double y] (+ x y))))))
(is (= [:malli/fn] (errors/codes (sut/validate [:function
[:=> [:cat :int] :int]
[:=> [:cat :int :int] :int]]
1))))
(let [schema [:multi {:dispatch :type}
["A" [:map [:type :string] [:value :string]]]
["B" [:map [:type :string] [:value :int]]]]]
(is (nil? (sut/validate schema {:type "A" :value "a"})))
(is (nil? (sut/validate schema {:type "B" :value 1})))
(is (= [:malli/multi-dispatch-value] (errors/codes (sut/validate schema {:value 1234})))))
(is (nil? (sut/validate [:schema {:registry {::foobar [:and :string [:= "foobar"]]}}
::foobar]
"foobar")))
(is (= [:malli/equal]
(errors/codes (sut/validate [:schema {:registry {::foobar [:and :string [:= "foobar"]]}}
::foobar]
"a"))))
(is (nil? (sut/validate [:schema {:registry {::foobar [:and :string [:= "foobar"]]
::foobar-or-int [:or [:ref ::foobar] :int]}}
::foobar-or-int]
"foobar")))
(is (nil? (sut/validate [:schema {:registry {::foobar [:and :string [:= "foobar"]]
::foobar-or-int [:or [:ref ::foobar] :int]}}
::foobar-or-int]
1)))
(is (= [:malli/equal :malli/int]
(errors/codes (sut/validate [:schema {:registry {::foobar [:and :string [:= "foobar"]]
::foobar-or-int [:or [:ref ::foobar] :int]}}
::foobar-or-int]
"a"))))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment