metosin / malli

High-performance data-driven data specification library for Clojure/Script.
Eclipse Public License 2.0
1.44k stars 204 forks source link

:and has different behavior when using a registry #993

Closed marceloschreiber closed 5 months ago

marceloschreiber commented 5 months ago

Problem

When not using a registry m/explain does not execute the second statement on a [:and ...] schema. It just return that scheam error message [1]. When using a registry m/explain executes the second statement and it throws [2].

Expectation

I'd expect the call using registry to behave in the same one as the one that doesn't. Returning ["should be a string" "should not be blank"].

Example

(require '[malli.core :as m])
(require '[malli.error :as me])

(def not-blank? #(not (str/blank? %)))
(def registry (merge (m/default-schemas)
                     {:not-blank (m/-simple-schema {:type            :not-blank
                                                    :pred            not-blank?
                                                    :type-properties {:error/message "must not be blank"}})}))

;; 1. Without using the registry
(me/humanize (m/explain [:and
                         :string
                         [:fn {:error/message "should not be blank"} not-blank?]] :keyword))
;; => ["should be a string" "should not be blank"]

;; 2. With registry
(me/humanize (m/explain [:and
                         :string
                         :not-blank] :keyword {:registry registry}))
;; it tries to call (str/blank :keyword)
;; => Execution error (ClassCastException) at core/not-blank? (REPL:35).
;;    class clojure.lang.Keyword cannot be cast to class java.lang.CharSequence (clojure.lang.Keyword is in unnamed module of loader 'app'; java.lang.CharSequence is in module java.base of loader 'bootstrap')

Tangential

Depending on the validation it doesn't make sense to display all error messages. Consider:

(-> [:and
     [:vector :any]
     [:fn {:error/message "should be distinct"} #(= % (distinct %))]]
    (m/explain :invalid)
    (me/humanize))
; => ["invalid type" "should be distinct"]

But in other cases it might, such as a password field:

I don't have a suggestion and I'd need some hammock time pondering about this problem.

ikitommi commented 5 months ago

Good Morning.

:fn wraps the predicates with m/-safe-pred which doesn't throw. With custom schemas, you need to do this yourself, so:

(m/-simple-schema {:type :not-blank
                   :pred (m/-safe-pred not-blank?)
                   :type-properties {:error/message "must not be blank"}})

this should fix your orignal issue, but IMO the Tangential could be resolved too.

ikitommi commented 5 months ago

created separate ticket for the Tangential (#1001). Closing this one as resolved.

marceloschreiber commented 5 months ago

Thank you!