Fork me on GitHub
#malli
<
2020-08-17
>
Shuai Lin09:08:01

is it possible for my transformer/decode to know the current path when decoding? e.g. in the below example, I'd like to replace the <location> with :k1

(ma/decode
 [:map 
  [:k1 [:map {:decode/foo #(assoc % :location :<location?>)}
        [:x int?]]]]
 {:k1 {:x 1}}
 (mt/transformer
  {:name :foo}))
;; => {:k1 {:x 1, :location :<location?>}}

Shuai Lin09:08:30

the background story is I want to generate a unique key for each such map based on its path in the whole tree

ikitommi09:08:35

@linshuai2012 not at the moment, but you can do the following: 1) use m/walk to transform the schemas by adding a :in property to schemas (Schema :path and value :in are available for walkers) 2) create a decoder that uses the interceptor :compile hook to access the Schema at decoder creation time

ikitommi09:08:44

there are few pending PRs around walkers, not 100% sure what is in the current master.

Shuai Lin09:08:08

@ikitommi thx! I'll take a look at this approach. Currently I'm trying a solution that 1. leaves a place holder when decoding, and then 2. after decoding the whole tree, walk my tree on my own (and accumulate the path), and for each such map, use clojure.walk/prewalk to replace the placeholder with the current path

ikitommi09:08:09

pretty sure it's not very performant that way. There is an example of attaching a generated sample value to all schemas in the readme.

1
ikitommi09:08:24

It uses the m/schema-walker , you need the plain walker to access the :in data. But otherwise, the 1) should be copyable from that.

Shuai Lin12:08:25

Just realized it's not what I want, the schema walker walks the schema, not the actual data. I still need to walk the actual data to get the pat to generate the unique key for each map in the tree based on its actual location. Also, my schema is recursive - think about a file system, where there are directories as maps, files as leaves, and sub-directories as child maps.

ikitommi12:08:41

walk + decode would work for non-recursive schemas. Please write an issue, I'll think about the solution.

Shuai Lin15:08:34

Thx! I will.

ikitommi09:08:13

also, the interceptor :compile hook could publish more information about the context to the transformers, last call to break the api before freezing things. Please write an issue if you want that

ikitommi09:08:55

It is now a callback fn with args of schema value, could be schema value path in root-schema for example

Shuai Lin09:08:23

the m/walker apporach looks promising, I'll try it next

Casey10:08:51

I'm just getting started exploring malli

Casey10:08:35

https://github.com/metosin/malli#mallicorebase-schemas seem to indicate if I want a :int or :int-in schema, I'd have to register it myself, is that correct?

ikitommi10:08:39

@ramblurr you can just create a function that returns a form for it:

(defn int-in [min max]
 [:and int? [:fn '(fn [x] (< min x max))]])

ikitommi10:08:23

but you should add a :gen/gen for it too. I think there should be a built-in for that (and for dates too)

ikitommi10:08:07

[:int {:min 1, :max 10}]
[:date {:min "2020-08-16", :max "2020-10-10"}]
kinda things

Casey10:08:40

ah great, i see

Casey10:08:40

is there an aggregated list of built ins somewhere (with docs as to their properties)

Casey10:08:31

> I think there should be a built-in for that (and for dates too) Do you mean you think this built-in already exists, or you had the idea that it should be added ? 🙂

ikitommi10:08:38

(m/default-schemas) gives a list of all. the source code is currently the best source of descriptions.

ikitommi10:08:43

should be added 🙂

ikitommi10:08:32

the properties - will add description of those using malli (eat your own..), but not there yet.

Casey10:08:55

Using the int-in function def you gave an example for above.. if you added it to a registry {:int-in (m/fn-schema :int-in int-in)} , how could it be consumed later? In fact, this wouldn't work right?

Casey10:08:39

You could do something like {:int-in-0-10 (m/fn-schema :int-in-0-10 (partial int-in 0 10))} I suppose

ikitommi10:08:37

(defn int-in [min max]
  [:and int? [:fn `(fn [x] (< ~min x ~max))]])

(def int-in-1-10 (int-in 1 10))

(validate [:tuple int-in-1-10 (int-in 10 100)] [2 12])
; => true

(form [:tuple int-in-1-10 (int-in 10 100)])
;[:tuple
; [:and int? [:fn (clojure.core/fn [malli.core/x] (clojure.core/< 1 malli.core/x 10))]]
; [:and int? [:fn (clojure.core/fn [malli.core/x] (clojure.core/< 10 malli.core/x 100))]]]

ikitommi10:08:06

if you write an issue about the :int as built-in, happy to add that. much cleaner

👍 1
Casey11:08:49

Am I correct that you can't use the generic :int-in as defined above in a registry, because the "registered" specs must be predicate predicate functions (single value as input)?

ikitommi11:08:25

• registered schemas are one of: 1) IntoSchema instance (e.g. something that take the schema syntax and return a Schema. 2) Schema instance, 3) Schema syntax. • m/fn-schema just takes a predicate fn, which is only used to build a validator for the schema • there could be more helpers to build custom schemas easier, something between m/fn-schema (here: too simple) and writing IntoSchema impl (here: too much work) by hand, but currently, there is not.

ikitommi11:08:09

I think I’ll extract the code from :string so that :date, :number, :date-time etc can reuse most of it (e.g. the :min + :max handling of via properties, effecting both validation and value generation)

ikitommi11:08:48

also, you could build your own things with it easily: {:registry {:bigdec (m/-ranged-pred-schema {:pred bigdec?, :range-pred …, :range-gen ...})}}

Casey14:08:05

The keyword <-> string transformations are clever too, though don't work when namespaced keys are used

👍 1
Casey15:08:18

I'm working around this using (defn unkeyword [m] (map-keys #(subs (str %) 1) m)) at the end of validator-for-humans to turn :foo/attr-> "foo/attr" .. and of course using "foo/attr" as the name in the form form. Works well enough, if a little dirty.

ikitommi14:08:20

merged in master

Casey14:08:14

first look comment: the max range should be exclusive, not inclusive. That's a pretty standard trope across all range checks in almost any language I know

ikitommi14:08:57

with m/-simple-schema it should be easy to add new schemas that use properties in validation:

(-simple-schema {:type :double, :pred double?, :property-pred (-min-max-pred identity)}))

ikitommi14:08:09

:thinking_face:

ikitommi14:08:23

spec has that, test.check uses inclusive for both.

ikitommi14:08:41

could you link some external wisdom for that?

Casey14:08:06

and so s/int-in and s/double-in are exclusive, https://clojuredocs.org/clojure.core/range is exclusive,

Casey14:08:49

json schema supports inclusive and exclusive options 😛

Casey14:08:48

to be clear, i'm advocating only the max be exclusive, look at pretty much and langugage: python's list slice operator, java's IntStreams,

Casey14:08:10

here's an argument why: http://wrschneider.github.io/2014/01/07/time-intervals-and-other-ranges-should.html (starting off about time, but then at the end mentions integer ranges)

ikitommi14:08:59

thanks. will read those.

Casey14:08:09

That said, I don't need to die on this hill 🙂 Just sharing my experience that generally when I see a range in an api, I assume (and assumed others did too!) that it was inclusive start and exclusive end. As long as it's documented, it'll be ok either way

👍 1
ikitommi17:08:59

meanwhile:

(mg/generate
  [:map
   [:string :string]
   [:int :int]
   [:double :double]
   [:boolean :boolean]
   [:keyword :keyword]
   [:symbol :symbol]
   [:qualified-keyword :qualified-keyword]
   [:qualified-symbol :qualified-symbol]]
  {:size 42, :seed 42})
;{:string "¦®GÏVá@£°5o,&µ7\rØã",
; :int -1251,
; :double -0.03125,
; :boolean true,
; :keyword :WD_VS_-r,
; :symbol k5K_2i,
; :qualified-keyword :M8qL/u?RAmf,
; :qualified-symbol x/y0T}
related: https://github.com/metosin/malli/issues/25