malli

https://github.com/metosin/malli :malli:
robert-stuttaford 2021-03-23T06:21:25.181400Z

thanks all, i'll report back on how it goes!

ikitommi 2021-03-23T08:41:01.182400Z

@raymcdermott I can look your thing today/tomorrow.

1
ikitommi 2021-03-23T11:33:56.184Z

@d.ian.b no inbuilt mapping for fn?. But, you can:

(m/validate [:fn fn?] (partial +))
; => true

2021-03-23T11:34:06.184500Z

yeah

ikitommi 2021-03-23T11:34:07.184600Z

also:

(m/explain
  [:=> [:cat [:* [:int {:gen/min -1000, :gen/max 1000}]]] :int] 
  (partial +)
  {::m/function-checker mg/function-checker})
; => nil

2021-03-23T11:34:18.184900Z

I've made it right now haha

👍 1
ikitommi 2021-03-23T11:34:49.185400Z

(m/explain
  [:=> [:cat [:* [:int {:gen/min -1000, :gen/max 1000}]]] [:int {:min 1}]]
  (partial +)
  {::m/function-checker mg/function-checker})
;{:schema [:=> [:cat [:* [:int #:gen{:min -1000, :max 1000}]]] [:int {:min 1}]],
; :value #object[clojure.core$_PLUS_ 0x6edca2b1 "clojure.core$_PLUS_@6edca2b1"],
; :errors (#Error{:path [],
;                 :in [],
;                 :schema [:=> [:cat [:* [:int #:gen{:min -1000, :max 1000}]]] [:int {:min 1}]],
;                 :value #object[clojure.core$_PLUS_ 0x6edca2b1 "clojure.core$_PLUS_@6edca2b1"],
;                 :check {:total-nodes-visited 0,
;                         :depth 0,
;                         :pass? false,
;                         :result false,
;                         :result-data nil,
;                         :time-shrinking-ms 0,
;                         :smallest [()],
;                         :malli.generator/explain-output {:schema [:int {:min 1}],
;                                                          :value 0,
;                                                          :errors (#Error{:path [],
;                                                                          :in [],
;                                                                          :schema [:int {:min 1}],
;                                                                          :value 0})}}})}

2
2021-03-23T11:35:07.185700Z

nice!

2021-03-23T11:57:22.186700Z

How I can use a register of the (m/function-schemas) registry, onto m/validate?

2021-03-23T16:06:41.187800Z

(get-in (m/function-schemas) [(quote *ns*) 'foo :schema])

ikitommi 2021-03-23T16:29:40.190400Z

@d.ian.b good question, atm, no easy way. Idea with the function registry is that there will be a instrument kinda thing, that will wrap a) some b) all registered function schmaas like Orchestra - running input & output validation. Could be also used to emit generated data based only on the function definitions. Ideas and PRs welcome on that.

ikitommi 2021-03-23T16:30:59.192Z

also, did a spike on infferring schemas from normal vars. you get useful guesses pretty easily, better with tools.analyzer & clj-kondo and I guess. really good with core.typed.

ikitommi 2021-03-23T16:36:00.197200Z

the new kw-varargs thing would work nicely with global registry, my guess is that the core team will plug into that with spec. e.g. given a function:

(defn doit [& {:domain.user/keys [id name]}] [id name])
… running (m/collect #'doit) would infer a [:map :domain.user/id :domain.user/name] out of it, register it as a malli function schma, after (m/instrument my-registry) saying:
(doit :domain.user/id 1, :domain.user/name "kikka")
would cause it to run validation.

1
ikitommi 2021-03-23T16:38:04.198600Z

given there is few hours extra time, I would write a sample code so that I could prove a point that it’s doable and :awesome:.

1
raymcdermott 2021-03-23T16:45:39.199200Z

I don't seem to be able to merge maps with [:and] constraints

raymcdermott 2021-03-23T16:47:33.199900Z

(def x [:and [:map
              [:start int?]
              [:end int?]]
        [:fn (fn [{:keys [start end]}]
               (< start end))]])
=> #'user/x
(def y [:map
        [:here int?]
        [:there int?]])
=> #'user/y
(require '[malli.util :as mu])
=> nil
(mu/merge y x)
=> [:and [:map [:start int?] [:end int?]] [:fn #object[user$fn__4910 0x40fa91ef "user$fn__4910@40fa91ef"]]]
(mu/merge x y)
=> [:map [:here int?] [:there int?]]

raymcdermott 2021-03-23T16:47:43.200100Z

is this expected?

borkdude 2021-03-23T16:54:15.200600Z

and should merge combine the predicate in another predicate which ands those if both maps have predicates?

raymcdermott 2021-03-23T16:55:34.201600Z

it should invoke both functions ... maybe the order would not be predictable but I'll take that

borkdude 2021-03-23T16:55:45.201800Z

probably the order of merge args

borkdude 2021-03-23T16:56:45.202300Z

this could lead to funny problems, like what if you merge in a closed map in an open map, probably the resulting map should be open?

borkdude 2021-03-23T16:59:19.203Z

or should merge consider predicates and other properties like metadata, which is ignored in merge args?

borkdude 2021-03-23T17:00:18.203700Z

(with-properties-of (merge x y) y)

raymcdermott 2021-03-23T17:54:01.204900Z

merge takes the last as the 'winner' so I think that would be the most idiomatic

raymcdermott 2021-03-23T17:54:37.205400Z

but I agree that merging things that are not maps is tricky

ikitommi 2021-03-23T19:19:15.212300Z

Plumatic dropped s/both in favour of s/constrained just because the first is not a good idea: “apple and fruit and a car” please. Currently :and already kinda means “the first thing constrained with the rest” as we pick the generator from first and then constraint with the rest using gen/such-that. Given that, we could make :and mergable, would merge with the first and keep the rest as extra leaves of :and? e.g.

[:map ::x]
[:map ::y] 
; => [:map ::x ::y]

[:map ::x]
[:and [:map ::y] map?] 
; => [:and [:map ::x ::y] map?]

[:and [:map ::x] map?]
[:map ::y] 
; => [:and [:map ::x ::y] map?]

[:and [:map ::x] map?]
[:and [:map ::y] map?] 
; => [:and [:map ::x ::y] map? map?]

[:and [:map ::x]]
map? 
; => map?
would that be … more correct?

ikitommi 2021-03-23T19:21:03.213100Z

having [:and [:map …] [:fn …]] is quite common, having it non-mergable is a bummer.

😬 1
raymcdermott 2021-03-24T21:19:07.229300Z

if both maps have [:and [:map ..][fn...] it could be rejected. If one has an [:and ...] and the other doesn't can't you still merge the maps? I am probably under thinking it 🙂

Panel 3000 2021-03-23T22:07:22.214600Z

Anyway to have a dynamic default ? Like if I want a default timestamp to be generated when runing decode with default-value-transformer ?

Panel 3000 2021-03-23T23:17:39.214700Z

(malli/decode
 [:map {:registry
        {:inst (m/-simple-schema
                {:type :inst
                 :pred inst?})}}
  [:time :inst]
  [:id1 :uuid]
  [:id2 :uuid]]
 {}
 (mt/default-value-transformer
  {:defaults {:inst (constantly (rand-int 100))
              :uuid (constantly (char (rand-int 100)))}}))
Found something that works for me, but the default are generated once per type so for example if you need two different uuid it won't work.

Panel 3000 2021-03-23T23:34:22.215300Z

(mu/optional-keys
 [:map {:registry
        {:inst (m/-simple-schema
                {:type :inst
                 :pred inst?})}}
  [:time :inst]
  [:id :uuid]])
This throw a java.lang.StackOverflowError