Fork me on GitHub
#malli
<
2023-02-28
>
Thomas Moerman10:02:15

Q: is there a recommended way to specify in Malli that something is to be a json-encoded string? Motivation: i want to use a json-encoded string as a GET query param using Reitit. It kinda works with a hacky workaround, specifying [:map [:filter [:maybe [:or [:map :str]]]]] as the schema, so that Swagger understands it as a map (but submits it as a string). Any other approach I might want to try? Cheers.

Thomas Moerman10:02:31

without :or :string I'm getting a coercion error server side

Ben Sless11:02:33

Add a decoder for this parameter which parses it before it is validated

Thomas Moerman13:02:49

This appears to work, found the pointer in another 🧵. Thanks!

(m/-simple-schema
    {:type :map
     :pred map?
     :type-properties
     {:decode/string #(try
                        (-> %
                            (json/read-str)
                            (w/keywordize-keys))
                        (catch Exception _ %))}})

Ben Sless13:02:24

No need to create a simple schema, just [:map {:decode/string ,,,}]

Ben Sless13:02:01

Then you can add any constraints you might have about the entries like you would a regular map, yes?

eggsyntax14:02:40

I was a bit surprised by this behavior. I initially expected :* and :+ to produce similar results to :vector (although maybe as eg a list rather than vector), but it's very different.

user> (mg/generate [:* [:* [:* int?]]] {:seed 1, :size 3})
(0 -2 0 -1)

user> (mg/generate [:vector [:vector [:vector int?]]] {:seed 1, :size 3})
[[] [[0 -2 0] [-1]] []]
My guess is that what's going on is that :* and :+ just mean 'some number of instances of a type' without any implication of containment, and that the only reason we're seeing the outer list in the :* case is that if generate generates > 1 item it has to throw them in a sequence just so that it's a single return val. Is that correct, or am I more fundamentally misunderstanding something?

Ben Sless15:02:02

It probably mimics the spec regex operators behavior which flatten by default

Ben Sless15:02:27

If you want to isolate them wrap them in a schema schema

👍 4
4
eggsyntax15:02:24

You know, I've used spec a bunch and I've somehow just never run across that flattening behavior. Oh well, always more corners to discover 😁

elarouss17:02:00

Hi, I am sorry if my question sounds stupid, I am not sure why mi/check is not working for me I have this function with it’s schema:

(defn get-user! [id])
(m/=> get-user! [:=> [:cat uuid?]
                 [:map
                  [:user/first-name string?]
                  [:user/email [:and
                                {:gen/elements ["" ""]}
                                string?]]]])
First I run:
(dev/start! {:gen mg/generate :report (pretty/reporter)})  
so then:
(get-user! (random-uuid)) ;; => #:user{:first-name "34Xdu9532PWK", :email ""}
  (get-user! "2") ;; => Schema error
But when I try:
(mi/check {:filters [(mi/-filter-var #{#'get-user!})]
             :gen     mg/generate
             :report  (pretty/reporter)})
mi/check does not generate a fake map value, but instead returns nil which causes the check to fail. am I using this wrong? my goal is to use generative testing, but I couldn’t find a working example that uses the function schemas with something like defspec.

ikitommi19:03:32

oh, this is unfortunate, mi/check has a separate branch in mi/-strument so it ignores the :gen option. Please write an issue of this

escherize22:02:10

overheard at work: is there a trick to get something like recursive seqexps with Malli?

escherize22:02:05

e.g. this schema works for generation, but not validation. is there a trick to get it working?

(let [sk [:schema {:registry {::filter
                              [:or
                               :boolean
                               [:cat [:= :and] [:* [:ref ::filter]]]]}}
          ::filter]]
  (mg/generate sk)
  ;; => (:and false (:and (:and) (:and) true false))

  (mc/validate sk true))

ikitommi13:03:32

Hi. recursive self-flattening regex schemas are not allowed. You can wrap the :ref with schema to make it work here:

(let [sk [:schema {:registry {::filter
                              [:or
                               :boolean
                               [:cat [:= :and] [:* [:schema [:ref ::filter]]]]]}}
          ::filter]]
  (mg/generate sk)
  ;; => (:and false (:and (:and) (:and) true false))

  (mc/validate sk true))

ikitommi13:03:55

not sure if that’s clearly described in the README. Documentation improvements most welcome!

ikitommi13:03:11

example from readme:

(def Hiccup
  [:schema {:registry {"hiccup" [:orn
                                 [:node [:catn
                                         [:name keyword?]
                                         [:props [:? [:map-of keyword? any?]]]
                                         [:children [:* [:schema [:ref "hiccup"]]]]]]
                                 [:primitive [:orn
                                              [:nil nil?]
                                              [:boolean boolean?]
                                              [:number number?]
                                              [:text string?]]]]}}
   "hiccup"])

(def parse-hiccup (m/parser Hiccup))

(parse-hiccup
  [:div {:class [:foo :bar]}
   [:p "Hello, world of data"]])
;[:node
; {:name :div
;  :props {:class [:foo :bar]}
;  :children [[:node
;              {:name :p
;               :props nil
;               :children [[:primitive [:text "Hello, world of data"]]]}]]}]

escherize20:03:43

Nice! Thanks. and Hi back 🙂