malli

https://github.com/metosin/malli :malli:
Shuai Lin 2020-08-17T09:15:01.394400Z

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 Lin 2020-08-17T09:17:30.394600Z

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

ikitommi 2020-08-17T09:45:35.398700Z

@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

ikitommi 2020-08-17T09:46:44.400200Z

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

Shuai Lin 2020-08-17T09:48:08.403300Z

@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

ikitommi 2020-08-17T09:50:09.405100Z

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
ikitommi 2020-08-17T09:51:24.407400Z

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.

ikitommi 2020-08-17T09:52:13.408400Z

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

ikitommi 2020-08-17T09:53:55.410800Z

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

Shuai Lin 2020-08-17T09:57:23.411400Z

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

Casey 2020-08-17T10:18:51.411900Z

I'm just getting started exploring malli

Casey 2020-08-17T10:19:35.412600Z

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?

ikitommi 2020-08-17T10:24:39.416Z

@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))]])

ikitommi 2020-08-17T10:25:23.417Z

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

ikitommi 2020-08-17T10:26:07.418100Z

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

ikitommi 2020-08-17T10:28:37.420200Z

the raw impl would look much like :string: https://github.com/metosin/malli/blob/master/src/malli/core.cljc#L772-L805

Casey 2020-08-17T10:28:40.420700Z

ah great, i see

Casey 2020-08-17T10:28:40.420800Z

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

Casey 2020-08-17T10:29:31.422200Z

> 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 ? ๐Ÿ™‚

ikitommi 2020-08-17T10:29:38.422400Z

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

ikitommi 2020-08-17T10:29:43.422700Z

should be added ๐Ÿ™‚

ikitommi 2020-08-17T10:30:32.423600Z

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

Casey 2020-08-17T10:33:55.425100Z

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?

Casey 2020-08-17T10:35:39.426100Z

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

ikitommi 2020-08-17T10:44:37.426700Z

(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))]]]

ikitommi 2020-08-17T10:46:06.427500Z

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

๐Ÿ‘ 1
Casey 2020-08-17T11:15:49.428700Z

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)?

ikitommi 2020-08-17T11:24:25.434600Z

โ€ข 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.

ikitommi 2020-08-17T11:26:09.436100Z

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)

ikitommi 2020-08-17T11:28:48.437800Z

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

ikitommi 2020-08-17T12:40:41.438400Z

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

Casey 2020-08-17T14:35:41.439600Z

@steveb8n I've been playing with your demo (https://github.com/stevebuik/fork-malli-ideas) , it's a really nice approach to form validation.

Lu 2020-08-19T07:32:27.454400Z

Yeah pretty cool!! ๐Ÿ˜Ž

Lu 2020-08-19T12:44:20.455600Z

in 2.1.4 you can add a keywordize-keys true option to work exclusivley with keywords/namespaced keywords instead of strings, also in the validation

Casey 2020-08-17T14:36:05.440300Z

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

๐Ÿ‘ 1
ikitommi 2020-08-17T14:38:20.440900Z

merged in master

Casey 2020-08-17T14:39:14.442500Z

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

ikitommi 2020-08-17T14:39:57.443100Z

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)}))

ikitommi 2020-08-17T14:40:09.443300Z

:thinking_face:

ikitommi 2020-08-17T14:40:23.443700Z

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

ikitommi 2020-08-17T14:40:41.444200Z

could you link some external wisdom for that?

Casey 2020-08-17T14:40:58.444600Z

https://clojuredocs.org/clojure.spec.alpha/int-in-range_q is exclusive

ikitommi 2020-08-17T14:42:55.445400Z

JSON Schema has inclusive: https://json-schema.org/understanding-json-schema/reference/numeric.html

Casey 2020-08-17T14:43:06.445500Z

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

Casey 2020-08-17T14:43:49.446100Z

json schema supports inclusive and exclusive options ๐Ÿ˜›

Casey 2020-08-17T14:46:48.447500Z

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,

Casey 2020-08-17T14:47:10.448Z

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)

ikitommi 2020-08-17T14:48:59.449300Z

thanks. will read those.

Casey 2020-08-17T14:50:09.450800Z

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
Shuai Lin 2020-08-17T15:26:34.450900Z

Thx! I will.

Casey 2020-08-17T15:40:18.451500Z

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.

ikitommi 2020-08-17T17:25:59.453400Z

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,&amp;ยต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

steveb8n 2020-08-17T22:14:55.453900Z

@lucio nice to see some interest

๐Ÿ‘ 1