Fork me on GitHub
#malli
<
2020-04-03
>
dcj00:04:34

OK, I have been yak shaving experimenting with Malli most of the day, having lots of fun! I have a bunch of questions: • After creating a new predicate, I want to assoc it into the existing predicate-registry map (creating my own). -register-var and -register are private. Obviously I can copy/paste these functions...suggestions? • Given my metadata-schema above, I want to transform a map via this schema. I get how m/decode is helpful, and I can specify key-transformer but I'm scratching my head about why/how I need to specify specific value transformers eg, string-transformer . Given that my schema has specified the type of each value (eg. int? ), then what I want to say is "coerce whatever you see into the specified type" I get that not all coercions can be pre-defined, and maybe there needs to be a way to add coercions....advice? • Again, given my metadata-schema above, let's say I want to obtain all the keys of that map schema. The top level form is a schema, and m/children gives me the list of children, but I can't figure out how to obtain the "name" of each child, without resorting to digging into the current implementation of that vector. In other words, sure I can first each child, but is there some better way to get that info/field? • Similar to the above, say I want to get each key in my map schema transformed in some way. When I want to transform a map, I do this: (m/decode my-schema my-map (mt/key-transformer {:decode csk/->snake_case_keyword}) because SQL column names are snake_case, but wouldn't it be better to include that transformer into my schema itself? Maybe I should define a new property at the map level :postgres/key-transformer and I (-> my-schema m/properties :postgres/key-transformer)? Thoughts? • Similar to the schema navigation/accessor questions above. when I first attempted to write the function to walk the schema and generate the DDL based on the :postgres/ properties, I tried to use m/accept with an function that would look at each [schema properties _] it encountered, but this didn't work because AFAICT the children are not schemas themselves. Finally I gave up and walked the schema using the obvious vector destructuring, but I don't feel this the right way to do it...

ikitommi05:04:07

• properties are open, there is a bunch of reserved keys (`:title` , :description, :default, :min, :max etc.) and some namespaces that are reserved for extensions (`json-schema`, gen, swagger, encode, decode etc.). Should be documented and I guess a good convention could be “new unqualified might be used by malli in the future, to be safe, use qualified keys to avoid future clashes”. Goal is to describe the properties with malli schemas to get good error reporting on those too

ikitommi05:04:55

-registerand -register-var could be made non-private, just undocumented. But in the end, it’s just assoc into the map. There is https://github.com/metosin/malli/pull/188 for discussion whether to support mutable registries oob

ikitommi05:04:58

• coercions, sorry, didn’t get the question.

ikitommi05:04:21

• all :map schemas implement MapSchema , which allows you to say (m/map-entries schema) to get the entries out

ikitommi05:04:24

• if you want to transform just the entities, you could create a new Transformer, that only mount to :maps which have the :postgres/schema defined, this way, it doesn’t effect all maps. You can add a custom encoder & decoder just like with key-transformer.

ikitommi05:04:43

• the m/accept… each IntoSchema is responsible for it’s own syntax so walking over schemas requires some knowledge of the schema in question. With maps, children is the sequence of entries, which are tuple3 of key-props-child.

ikitommi06:04:05

You can run (m/accept Schema m/map-syntax-visitor) to see the structure to be walked. With Java, the Visitor impl would have dispatch methods with different class signatures, with clojure, one can use multimethods for doing schema-based dispatch, see malli.generator or malli.json-schema for examples.

ikitommi06:04:07

Currently, only :map and :multi use custom syntax for it’s contents, later some variants of :or , :cat and :alt too (to support named branches). Could be others.

ikitommi06:04:58

There is also m/schema-visitor helper for walking.

ikitommi06:04:54

hope this helps @dcj

ikitommi06:04:04

the long? predicate… could be, or :long type schema to companion :date-time, :date etc

ikitommi06:04:24

(there is no long? in clojure core, for some reason)

mike_ananev06:04:38

@ikitommi any plans to release the malli? malli is awesome! without release I clone malli to my projects every month manually.

dcj06:04:08

@ikitommi Thank you for all the answers/advice/tips, I will try those tomorrow! I will also try to better explain my coercion question, but too tired to do it justice ATM...

ikitommi07:04:02

@mike1452 not going to do that yet, but soon. with deps, you can depend on th the latest commit directly. With Leiningen, there is [metosin/malli "0.0.1-20200305.102752-13"], could start pushing new SNAPSHOTS after each merge.

👍 4
ikitommi07:04:36

as soon as malli works fully with reitit, could push first alpha. some small hiccups still.

eskos08:04:02

edit: all of this is garbage 🙂 see below `long?` predicate might be missing from Clojure due to the original design decision of having transparently coercing number types based on number size (eg. short -&gt; int -&gt; long -&gt; BigInteger) which was then removed in was it 1.5 or smth? Or maybe it was even before that. Oh well. It might also be that it’s relatively hard (or so I’ve been told) to detect anything else than doubles on CLJS side.

eskos08:04:28

int? is true for Bytes, Shorts, Integers, Longs, while integer? is true for Integers, Longs, Clojure BigInts, BigIntegers, Shorts and Bytes. I don’t think there’s specifically benefit for checking if value is strictly Long, but YMMV.

teodorlu09:04:52

Differentiating between Integer and Long would allow generating precise Java source code from a schema

☝️ 8
👌 4
dcj17:04:04

@ikitommi m/map-entries and map-schema-entry structure as 3tuple of [key props child]is very helpful. For some reason, it makes me uneasy to just destructure the map-schema-entry 3tuple (instead of having accessor functions), but the good news is that you enforce that format, so even if the entry was specified without props, you provide the correct 3tuple, so destructuring works well! How would I test for a map schema? Is there a better way than: (= :map (m/name my-schema)) ?

dcj17:04:00

@ikitommi (m/accept Schema m/map-syntax-visitor) is an awesome way to understand how the walking works, thanks! ATM, for my current "database table schema" task, I am pretty happy with m/map-entriesand processing the returned map-schema-entries.... But I'm sure I'll want to walk schemas at some point and good to know this.

dcj17:04:11

@ikitommi I don't want to mutate the default registry, I just want to add a few more things to the default registry. My initial feeling is that I am happy to provide my registry as options map. Maybe there should be public functions to support users adding to the registry, to make their own variants I'll just copy/paste from -register and -register-var for now....

dcj18:04:09

@ikitommi WRT my earlier coercion question(s), I studied the README and transform.cljc further, looks like this machinery will do everything I want, I'll just need to provide/add my own encoding and decoding functions...I'll get to that soon enough

👍 4
dcj18:04:55

Regarding long? and/or :long This requires further thought. If I am specifying a key :epoch-millisconds then it feels like that should specified as a long, not an int, so transformers can provide the correct type. Currently:

(def +string-decoders+
  (merge
    +json-decoders+
    {'integer? string->long
     'int? string->long
So, ATM those transforms do produce longs, but this seems somewhat arbitrary and not as precise as it might be. If the schema doesn't care about the the specific numeric type, then specifying something that includes a variety of types is fine.

dcj23:04:55

More progress this afternoon: • Was able to add a new predicate to the default-registry, creating my-registry, and used it to define and validate a schema • Created a value transformer, and was able to get it invoked to transform a value via decode. Questions: • What is the meaning/use of the :name key in the mt/transformer input map? Can I put anything I want here? Should I use a namespaced keyword? • What other uses are there for the (optional) options map argument? (other than :registry)?