Fork me on GitHub
#malli
<
2022-05-23
>
jprudent06:05:10

Hello, I've a question about the behaviour of :and. Here is a schema with a valid input:

(m/explain [:and
              [:sequential any?]
              [:multi {:dispatch 'first}
               [:a [:sequential keyword?]]
               [::m/default any?]]]
             [:a :b])
=> nil

jprudent06:05:21

but with invalid input

(m/explain [:and
              [:sequential any?]
              [:multi {:dispatch 'first}
               [:a [:sequential keyword?]]
               [::m/default any?]]]
             1)
Execution error (IllegalArgumentException) at malli.core/-multi-schema$reify$reify$fn (core.cljc:1497).
Don't know how to create ISeq from: java.lang.Long
I was expecting that the branch that checks that the input is sequential would prevent execution of the second one

jprudent06:05:51

The solution I found to avoid an exception to be raised is to have smarter dispatch function

(m/explain [:multi {:dispatch (fn [x] (if (seq? x) (first x) ::error))}
              [:a [:sequential keyword?]]
              [::error [:sequential any?]]
              [::m/default any?]]
             1)
=>
{:schema ...,
 :value 1,
 :errors ({:path [:muguet.meta-schemas/error],
           :in [],
           :schema [:sequential any?],
           :value 1,

           :type :malli.core/invalid-type})}

jprudent06:05:58

Why checking branches in an :and is not lazy (could stop after one of the branch is invalid) ?

jprudent06:05:34

(I can live with current behaviour, no problem 🙂 just curious) Thanks

Alexis Schad10:05:35

Hi. I'm very not expert in malli, but maybe it's the use of explain: explain try to give all the reasons why it's not valid. Let's say you have a password that must have a specific length, some special characters, etc. You may want to display all the reasons at once instead of a "one by one" strategy. But a lazy version would be useful I think. (I didn't check but validators should already be lazy)

jprudent12:05:44

You're right, validate is "lazy"!

jprudent12:05:31

Your explanation makes sense, thanks.

rovanion11:05:29

Is it possible to remove a key-value-pair from a map during decode if the value is a specific thing? I've got a select with an entry that reads "all" which is functionally the same as not giving any value at all and I want to handle that case in the reitit/malli request param coercion stage.

rovanion11:05:47

I think I've found a relevant example:

(m/decode
  [string? {:decode/string {:enter 'str/upper-case}}]
  "kerran" mt/string-transformer)

rovanion11:05:58

; => "KERRAN"

rovanion11:05:56

Though that won't remove the key:

(malli.core/decode [:map [:a [:string {:decode/string #(if (= "all" %) nil %)}]]]
                   {:a "all"}
                   malli.transform/string-transformer)
;; => {:a nil}

rovanion12:05:01

Para on IRC gave me a solution:

(defn- remove-alla-odlingsplatser
  "Takes a query-params map and removes the key :odlingsplatser if its value is 'alla odlingsplatser'."
  [m]
  (if (= "alla odlingsplatser" (:odlingsplats m))
    (dissoc m :odlingsplats) 
    m))   

rovanion12:05:29

Welll, the solution he gave me was: (fn [m] (if (= "all" (m :a)) (dissoc m :a) m))

Will15:05:24

I want to use malli/decode on a map that has to contain at least one of :foo/id URI or :bar/id UUID. No matter how I twist and turn the schema, the only thing I can get working is the following:

(malli/decode [:or
                 [:map
                  [:foo/id uri?]
                  other-child
                  other-other-child]]
                 [:map
                  [:bar/id uuid?]
                  other-child
                  other-other-child]]]
    {:bar/id    #uuid "15a0bc27-5725-41d9-89c9-6f8d3966447a"
     :other-key :other-value}
    (mt/transformer mt/strip-extra-keys-transformer mt/string-transformer))
Is there a good way to handle this without having to duplicate the schema?

jprudent15:05:41

[:map
               [:foo/id [:or [uri?] [uuid?]]]
               [:attr1 :int]
               [:attr2 :int]]

jprudent15:05:53

could that work ?

Will16:05:54

Thanks for the suggestion! Unfortunately they are different keys, one of which always corresponds to a URI and the other to a UUID.

Will16:05:51

[:map
               [:or [:foo/id uri?]]
                    [:bar/id uuid?]]
               [:attr1 :int]
               [:attr2 :int]]
Something like the above is what I would like to achieve.

jprudent17:05:31

[:union
 [:or
         [:map [:foo/id uri?]]
         [:map [:bar/id uuid?]]]
 [:map
  [:attr1 :int]
  [:attr2 :int]]] 

Will17:05:12

This looks promising, will try it out and let you know, thanks!

jprudent17:05:29

[:and [:map
       [:foo/id {:optional true} uri?]
       [:bar/id {:optional true} uuid?]
       [:attr1 :int]
       [:attr2 :int]]
 [:fn (fn [m] (or (:foo/id m) (:bar/id m)))]] 

Will08:05:02

Forgot to get back to you, but the last solution worked like a charm, huge thanks!

simple_smile 1
👍 1