Fork me on GitHub
#malli
<
2021-04-29
>
ikitommi04:04:59

@eoliphant What happens is: • :testfoo is not a qualified key, it’s contents are inlined • ::testbaz is a qualified = schema reference, instead of the inlined contents, you get the reference back. References are internally of type :malli.core/schema , which can also have properties. To get the actual schema behind the reference, you can m/deref the schema. • analogy is to Vars in Clojure, e.g. you get #'inc (var) back, not inc (function value)

(def registry*
  (merge
    (m/default-schemas)
    {:testfoo [:string {:a :b}]
     :test/bar [:string {:c :d}]
     ::testbaz [:string {:a :b}]}))

(m/type :testfoo {:registry registry*})
; => :string

(m/type ::testbaz {:registry registry*})
; => :malli.core/schema

(-> ::testbaz
    (m/schema {:registry registry*})
    (m/deref))
; => [:string {:a :b}]

(-> ::testbaz
    (m/schema {:registry registry*})
    (m/deref)
    (m/properties))
; => {:a :b}

ikitommi04:04:45

that said, not happy how the references work atm, in most malli-based codebases I’ve seen, there is a lot of manual m/deref / m/deref-all calls to inline things. Also, the current behavior is not documented properly. Not sure what would be a correct way to handle these. Ideas welcome.

ikitommi04:04:33

compared to spec, any lookup to a registry pulls the actual value, but when the reference is part of some other specs form, it’s kept as it is;

(require '[clojure.alpha.spec :as s])

(s/def ::kikka int?)

(s/form ::kikka)
; => clojure.core/int?

(s/form (s/tuple ::kikka))
; => (clojure.alpha.spec/tuple :user/kikka)

ikitommi05:04:17

this sums the current functionality:

(m/schema [:tuple :testfoo ::testbaz] {:registry registry*})
; => [:tuple [:string {:a :b}] :user/testbaz]

3
eoliphant16:04:59

ah it was just that? lol. yeah, for now i think just a bit in the docs, maybe in the areas that ref using qualified keys, custom/local registries would be a start just so folks are aware. I just happened to catch Arne’s quick vid on Malli and data modeling, and so gonna just take his approach of my own ‘schema’ name space that limits the surface area, and I can just jack in handling this. will think about what might be a better approach longer term. it does ‘feel’ that something like (m/properties :test vs ::test …) should ‘just work’ from the average user/client’s perspective, returning the same kind of representation. maybe an ‘easy’ 🙂 namespace like reitit?

ikitommi18:04:33

need to think what would be a good resolution for this. today, my 2 cents are to make it work like spec: • by default defer eager references on m/schema, and add an option not to do that (used in form creation) would be a breaking change.

ikitommi18:04:11

unrelated, but, content-dependent collection schemas on coming up. not sure how useful, but possible:

(def List
  (m/-collection-schema
    (fn [properties [child]]
      {:type 'List
       :pred list?
       :empty '()
       :type-properties {:error/message "should be a list"
                         :gen/schema [:vector properties child]
                         :gen/fmap #(or (list* %) '())}})))

(m/validate [List :int] '(1 2))
; => true

(-> (m/explain [List :boolean] [1 2 3])
    (me/humanize))
; => ["should be a list"]

(mg/sample [List {:gen/min 4, :gen/max 10} :int])
;((-1 0 -1 -1 -1 -1 -1 0)
; (-1 -1 -1 0 -1)
; (0 0 1 -2 -1)
; (-1 0 -1 1 -1 0 -4 -1)
; (-2 0 0 -2 1 0)
; (-2 7 2 3 2 -1)
; (-7 -3 -1 7 -1)
; (0 -12 0 8)
; (-5 25 0 -2 -5 1 -1 -25 -10 1)
; (53 221 0 15 1 -42))

(m/form [List :int])
; => [List :int]

(mu/assoc [List :int] 0 :boolean)
; => [List :boolean]