Fork me on GitHub
#malli
<
2024-02-28
>
Tommi Martin08:02:28

Hello what would be your general tips to making schemas with malli? I've successfully made a few map schemas but i'm struggling to use them effectively as I get a "invalid-type" validation errors with large schema files and large values inserted to those. Making it difficult and slow to actually track down what went wrong. Adding a custom error to these only drops the entire schema and entire value into the error, nothing mory. Do you have any specific tips on how to structure the schema to get as accurate error messages as possible? My current schema is a map contain vectors, containing maps up to 5 levels. It's all defined as a single entity with the vector syntax alone, no registry.

ambrosebs19:02:48

FWIW it's not just you, this is an area for growth for malli. Some ideas with that in mind: You might want to https://github.com/metosin/malli/blob/875caae5ecbeecbe32d40af53c0732bec717f70a/src/malli/error.cljc#L29 the default invalid-type error message to be more specific. e.g., https://github.com/fluree/db/commit/4ff41ae12fe5588b0e64da93ff45ea14d72499cc#diff-5b7e602fc7bc937648bce72499d1c9c24ead635bf2e72468c224769b43f9263aR212-R219

::m/invalid-type
        {:error/fn (fn [{:keys [schema value]} _]
                     (if-let [expected-type (-> schema m/type)]
                       (str "should be a " (case expected-type
                                             (:map-of :map) "map"
                                             (:cat :catn :sequential) "sequence"
                                             :else (name type)))
                       (str "type of " (pr-str value) " does not match expected type")))}
Use :multi instead of :or.

👀 1
ikitommi06:03:44

agree, the default error is bad, should be easy to make better as Ambrose pointed out 🙂

Sardtok09:02:20

I just wanted to make sure I'm not doing something stupid. Is there a simpler way to require that fields are set, but only if one specific field is set to true?

[:multi {:dispatch :x}
        [true [:map [:x :boolean]
                    [:y :int]]]
        [::m/default [:map [:x {:optional true} [:maybe :boolean]]
                           [:y {:optional true} [:maybe :int]]]]]

ambrosebs18:02:02

I'd guess this is the most performant, might not be the simplest.

ambrosebs18:02:57

the alternative would be something like [:or big-map another-big-map] and a lot of time would be spent figuring out which schema to use.

Sardtok18:02:41

Using :or gave worse results for the errors with explain (and reitit's coercer). It would generate errors for both branches, which in the case of the coercer merged into a single set of humanized errors without the full path identifying the branch.

ambrosebs18:02:42

Yes, good point. :or is not good for error messages.

ikitommi06:03:39

declarative key/value relations are not simple, here is one try: https://github.com/bsless/malli-keys-relations

vemv17:02:46

> (malli.generator/generate [:map [:success? {:gen/return true} boolean?]])
{:success? false}
Looks like :gen/return isn't doing what I want to here - suggestions?

ambrosebs18:02:07

Perhaps try pushing it in? [boolean? {:gen/return true}]

ambrosebs18:02:02

hmm but there's a test case for this syntax in the malli test suite. I'm not sure.

vemv18:02:42

Hey there 👋 I had tried this variation as well, inspired by the readme

> (malli.generator/generate [:map [:success? [:and {:gen/return true} boolean?]]])
{:success? false}

ambrosebs18:02:59

Hey! Curious, does :gen/elements [true] work?

vemv18:02:59

Certainly does, thanks!

ambrosebs18:02:34

Alright! Looks like :gen/return is a newer feature, maybe it's missing something.

vemv18:02:58

Unrelated, lately I've been really wanting a Schema -style zero-cost defprotocol for Malli. Maybe one day I'll find the time.

ambrosebs18:02:52

Yes, I think I told you that I was working on a common library extracting out the implementation details of s/defprotocol. I got stuck, just made it public, maybe you'll find it inspiring? https://github.com/frenchy64/instrument-defprotocol

vemv18:02:53

oh, interesting :) > I got stuck What's the lib state?

ambrosebs18:02:55

Pretty rough, unreleased, but it at least separates out the main ideas of what's needed to instrument protocols visually. I think if you read https://github.com/frenchy64/instrument-defprotocol/blob/main/src/com/ambrosebs/instrument_defprotocol/clj.clj file you should get a good idea of what's involved. IIRC I found it difficult to decide how leaky I wanted to make the abstraction, because there were performance benefits for being more flexible but also potentially it made it easier to shoot yourself in the foot.

👍 1
vemv18:02:25

Did the Schema impl play out nicely long term? Perhaps I'd be better off with a non-generic solution

ambrosebs18:02:31

Either no-one used it or it worked perfectly 🙂

ambrosebs18:02:03

Works on the latest Clojure master based on the cron CI. Not sure if I test the latest CLJS though.

vemv18:02:24

Thanks! Hope I can hack something with it this year. Should be pretty low-risk as one can always replace custom/defprotocol with defprotocol - nothing else should be adapted

ambrosebs18:02:12

But I agree, a non-generic solution is probably best.

radhika19:02:13

@U45T93RA6 hm, which version of malli were you using with :gen/return?

vemv19:02:44

[metosin/reitit-malli "0.5.18" :exclusions [[org.clojure/tools.reader] [org.clojure/core.rrb-vector]]]
   [metosin/malli "0.8.2"]
Didn't realise this project had such an old version :) that tag is from Feb 14, 2022 Will update + retry, thanks!

💡 1
vemv19:02:17

That was it 🤝

👍 2
🎉 1
vemv21:02:47

Can I use malli.util/transform-entries or the like to transform the case of my schema keys? e.g. apply ->kebab-case over each key