Fork me on GitHub
#malli
<
2022-09-13
>
Martynas Maciulevičius07:09:27

Is there a decoder for malli schema itself from JSON? For instance if I have this schema: [:map [:key {:optional true} :string]] Can I parse it from this JSON?

["map",
	["key", { "optional": true }, "string"]
]

ikitommi10:09:38

[:map ["key" {:optional true} :string]] is also a valid schema, so one can’t guess from the JSON example that the "key" should be handled as a keyword.

ikitommi10:09:45

but, if someone would write schemas of all malli schema syntaxes, one could do everything else (but not guess the string->kw things)

Martynas Maciulevičius10:09:56

Ok. That means that a dumbed-down version would work :thinking_face:

ikitommi12:09:10

yes. If you want all maps to have keyword keys, you can: 1. read in from json 2. clojure-walk all vectors to have first arg as keyword 3. m/schema 4. m/walk and convert all map keys into keyword 5. profit

Martynas Maciulevičius13:09:34

There is also a difference between keyword and symbol . And that's also significant because :inst? ,`inst?` and "inst?" are different :thinking_face:

Martynas Maciulevičius15:09:20

I was thinking about dumbing down the schema and making everything a keyword or a number :thinking_face: I don't need all power.

juhoteperi10:09:02

If you know all your map keys are going to be keywords, you could use walker and transform to change map keys to keywords from strings, in the read schema.

juhoteperi10:09:39

Or you if just write limited Malli schema for the Malli schemas you need to support, you can use :keyword there to read the JSON strings as keys for the map keys.

Martynas Maciulevičius10:09:02

I was thinking about making a subset of malli by doing something similar to a custom parser. And then use :inst instead of inst? and :number instead of number? to make it all very uniform for the reader and parser. And after parsing I would do clojure.walk/postwalk or something similar to replace into the functions that malli understands. So I should probably implement parsing for all basic types, :map , :set and some basic options like :optional. I think I don't need seq regex matching in this specific use as this will only be used to define a data structure and sequences, maybe lists with order too but there shouldn't be tuples.

Anders Eknert14:09:30

Hey! Just discovered malli, and it looks like it does a lot of the things I need it for 😃 Could be I’ve missed something in the README, but one thing that isn’t immediately apparent to me, is if/how I can do validation of values that reference other attributes? So say that I have a map like:

{
  "min": 10,
  "max": 20
}
and I want to ensure that the min value is never greater than the value for max , etc

Anders Eknert15:09:24

Ooohh, that's very nice! Thanks very much 😃

lread15:09:35

You are most welcome! I find it a great place to explore.

Anders Eknert15:09:35

For sure! For whatever reason, I hadn't registered that was a thing before. Much appreciated 👍

Anders Eknert09:09:24

Back again 😅 While the custom validator function works for validation, I’ve found it breaks the default value transformer. My code looks something like this:

(def Conf
  [:map
   [:polling
    [:and
     [:map {:default {}}
      [:min-delay-seconds [int? {:default 60}]]
      [:max-delay-seconds [int? {:default 120}]]]
     [:fn
      {:error/message "max polling delay must be >= min polling delay"}
      (fn [{:keys [min-delay-seconds max-delay-seconds]}]
        (> min-delay-seconds max-delay-seconds))]]]])
Without the :and + :fn :
(m/decode Conf {} mt/default-value-transformer)

=> {:polling {:min-delay-seconds 60, :max-delay-seconds 120}}
And once that’s been added, the map comes back empty:
(m/decode Conf {} mt/default-value-transformer)

=> {}
I assume I’m doing something wrong, but I struggle with understanding what 🙂

lread14:09:34

Not at my dev box right now, but maybe due to {:default {}}?

Anders Eknert14:09:24

Doesn’t make a difference I’m afraid… I’ve tried a large number of combinations, but apparently not the right one 😅

lread14:09:27

I don’t want to to think I know malli well at all, so there might be other ways. Moving the :default {} up seems to work:

(require '[malli.core :as m]
         '[malli.transform :as mt])

(def Conf
  [:map
   [:polling {:default {}}
    [:and
     [:map 
      [:min-delay-seconds [int? {:default 60}]]
      [:max-delay-seconds [int? {:default 120}]]]
     [:fn
      {:error/message "max polling delay must be >= min polling delay"}
      (fn [{:keys [min-delay-seconds max-delay-seconds]}]
        (> min-delay-seconds max-delay-seconds))]]]])

(m/decode Conf {} mt/default-value-transformer)
;; => {:polling {:min-delay-seconds 60, :max-delay-seconds 120}}

(m/decode Conf {:polling {:min-delay-seconds 1231}} mt/default-value-transformer)
;; => {:polling {:min-delay-seconds 1231, :max-delay-seconds 120}}

Anders Eknert14:09:11

Wow, it does! I must have tried pretty much any combination but that one facepalm Thanks again, Lee!

lread14:09:12

My pleasure, more experienced mallites might chime in, but the above looks ok to me.

🙌 1
marciol17:09:30

Hey, I have some code that have dependencies on spec, something that I cannot address in the near term, so I still need to duplicate malli and spec schemas everywhere. I'm just wondering if there is a lib that allows some sort of translation of malli schemas into spec schemas, in a way that we can make other spec dependent code thinks that a malli schema is a spec schema. Does anyone knows about some think like this?

marciol18:09:18

I will track this, can be a point where I can start. Thanks

pithyless18:09:44

I also remember this presentation showing how to generate domain models from malli (similar to the above repo thoughts). It's not directly related to clojure.spec, but perhaps you may gleam some insights if you go down the road of actually implementing this kind of generator. https://www.youtube.com/watch?v=ww9yR_rbgQs

pithyless18:09:45

I think both of these things came about before malli introduced https://github.com/metosin/malli#qualified-keys-in-a-map - perhaps with some clever use of macros and custom malli registries, you can get 80% there without much effort?

marciol18:09:58

Lets see, there is a lot of duplication going on so I need to solve it through some clever strategy

dvingo19:09:55

Yea, I paused working on that, but the good news is that since then malli added malli.core/ast which makes it a lot easier to parse and walk schemas. I have a transform for taking malli schemas describing a hashmap and producing pathom output vectors that is working: https://gist.github.com/dvingo/213633acfdd520bddcdc91fc1c7b9e44 you should be able to do something similar to output clojure specs. The Kondo output has a more complete set of schema types: https://github.com/metosin/malli/blob/master/src/malli/clj_kondo.cljc

escherize21:09:36

I want to use malli for the clj-kondo type-mismatch stuff on a large clojure project. But I’d rather not add it as a dependency. Is there a way to make malli.core/=> annotate functions in other namespaces? Then I can put all my m/=>’s into an un-committed namespace and still get the benefits of (). I looked at the code, and adding the feature doesn’t seem too tough. But I may be missing another approach

dvingo22:09:03

yea that should be possible with a little custom code. Underneath => just calls -register-function-schema https://github.com/metosin/malli/blob/bf92680ad76e57697261dd45c52dd31ffa9a8e1e/src/malli/instrument.clj#L41 which takes an ns symbol, a name symbol, a schema and a metadata map

ikitommi06:09:18

maybe m/=> should allow qualified symbols too? e.g.

(m/=> my.domain/foo ...)

👍 1
escherize21:09:12

How do I make a schema for a vector that has 3 items: int, nil, string? e.g. [1 nil "a"] I can make a vector that is homogenous, or I can make a list using sequence schema. But I can’t figure this one out

ikitommi04:09:47

try [:tuple :int :nil :string]

escherize17:09:21

Exactly right! In hindsight I could have also:

(mp/provide
 [[1 nil "x"]
  [2 nil "y"]
  [1 nil "z"]]
 {:malli.provider/tuple-threshold 3})

👍 1
Anders Eknert09:09:24

Back again 😅 While the custom validator function works for validation, I’ve found it breaks the default value transformer. My code looks something like this:

(def Conf
  [:map
   [:polling
    [:and
     [:map {:default {}}
      [:min-delay-seconds [int? {:default 60}]]
      [:max-delay-seconds [int? {:default 120}]]]
     [:fn
      {:error/message "max polling delay must be >= min polling delay"}
      (fn [{:keys [min-delay-seconds max-delay-seconds]}]
        (> min-delay-seconds max-delay-seconds))]]]])
Without the :and + :fn :
(m/decode Conf {} mt/default-value-transformer)

=> {:polling {:min-delay-seconds 60, :max-delay-seconds 120}}
And once that’s been added, the map comes back empty:
(m/decode Conf {} mt/default-value-transformer)

=> {}
I assume I’m doing something wrong, but I struggle with understanding what 🙂