Fork me on GitHub
#malli
<
2022-12-30
>
Lucy Wang07:12:18

Hello, how should I achieve "use default value for a field when validation fails and the value is not compatible with the schema "? e.g.

(def MySchema
  [:map
   [:some-int-field {:default 30} :int]
   [:some-boolean-field {:default false} :boolean]])

(m/decode
  MySchema
  {:some-int-field "typo" :some-boolean-field "another-typo"}
  what-transformer-to-put-here--i-dont-know?)
The builtin default-value-transformer would do nothing when the field is present but invalid.

ikitommi11:12:09

Good Question! There is no such thing atm but doable with the current transformers: • on :leave, validate the value, if not valid, replace with "the default". You can access the schemas (and the properties) in the transformation using the :compile hook code would be 90% identical to the default-value-transformer , maybe could be just an option to it :thinking_face:

ikitommi11:12:09

good case to try to learn how the transforformations work. Want to give it a try?

Lucy Wang02:12:18

Awesome! I actually ended with something exactly the same as you suggested (got some inspiration from https://github.com/metosin/malli/issues/143 )

(def fix-with-default-transformer
  (mt/transformer
    {:name :fix-with-default
     :default-decoder
     {:compile
      (fn [schema _]
        (if-let [{:keys [default]} (m/properties schema)]
          (fn [x]
            (if (m/validate schema x)
              x
              default))
          identity))}}))

  (def my-transformers (mt/transformer
                       mt/string-transformer
                       fix-with-default-transformer))
  (m/decode [:int {:default 100}] "99" my-transformers)
  ;; => 99
  (m/decode [:int {:default 100}] "what a typo!" my-transformers)
  ;; => 100

Lucy Wang03:12:22

One more thing I want is to be able to log the map field name that has this invalid value detected and replaced, e.g.

[:map
   [:number-of-adults {:default 1} [:int {:min 1}]]
   [:number-of-babies {:default 0} [:int {:min 0}]]]
and when I receive invalid input {:number-of-adults "foo"} I'd like to log this down
[WARN] Got invalid input for :number-of-adults "foo", use default value '1' instead
But as I search around it looks like the path information is not available to the transformers. So I think the only way to achieve that is to use m/walk to walk the schema and add the field name to the properties, e.g. the above schema would be enriched to be:
[:map
   [:number-of-adults {:default 1 :report/name :number-of-adults} [:int {:min 1}]]
   [:number-of-babies {:default 0 :report/name :number-of-babies} [:int {:min 0}]]]
This shall work, albeit being a bit verbose

ikitommi08:12:48

Looks good. Quick comments: • it’s better to apply the transformation on :leave so it’s postwalk. If you just return a function from :compile, it maps to :enter phase, so it’s a prewalk. If you use nested defaults, the top-level might fail on enter, as the children are not transformed. • returning nil from :compile is better than identity -> the transformation engine knows “there is nothing to do” • you can create the (pure) validator just once, much faster • so:

(defn fix-with-default-transformer []
  (mt/transformer
   {:name :fix-with-default
    :default-decoder
    {:compile
     (fn [schema _]
       (if-let [{:keys [default]} (m/properties schema)]
         (let [valid? (m/validator schema)]
           {:leave (fn [x] (if (valid? x) x default))})))}}))
• for the paths… what would you expect as a warning path from this:
(m/decode 
 [:map 
  [:x [:map 
       [:y [:int {:default 100}]]]]] 
 {:x {:y "what a typo!"}} 
 my-transformers)
;; => {:x {:y 100}}

👍 1
Lucy Wang13:12:40

Thanks! The validator tip is very useful. > for the paths… what would you expect as a warning path from this: I'll expect something like [:x :y] would be perfect.

escherize17:12:09

I might have missed this, but how do you pronounce malli? 🙂

ikitommi17:12:12

... and, will check the describe-PR tomorrow

🙏 1
escherize19:12:40

No rush, I have a copy pasted namespace in the project now. But looking forward to a version bump for a few reasons 🙂

ikitommi08:12:54

This is really good. Merged.

(is (= "one of <:dog = a map where {:x -> <integer>} | :cat = anything> dispatched by the type of animal"
         (med/describe [:multi {:dispatch :type
                                :dispatch-description "the type of animal"}
                    [:dog [:map [:x :int]]]
                    [:cat :any]])))

  (is (= "one of <:dog = a map where {:x -> <integer>} | :cat = anything> dispatched by :type"
         (med/describe [:multi {:dispatch :type}
                    [:dog [:map [:x :int]]]
                    [:cat :any]])))

escherize00:01:28

happy new year 🙂

ilmo08:01:14

IPA: /ˈmɑ.lːi/

2