typst-community / valkyrie

Type safe type safety for Typst
Other
25 stars 2 forks source link

Mutually exclusive choices in `either` #27

Closed mindbound closed 3 weeks ago

mindbound commented 1 month ago

Is it possible to set either to have mutually exclusive choices, e.g. where only one of

let schema = val.either(
        val.dictionary((
            dynamic: val.boolean()
        )),
        val.dictionary((
            seed: val.integer()
        ))
)

can be the case but not both?

I might be doing something wrong but this schema seems to pass even when there's a dictionary with both fields present.

JamesxX commented 1 month ago

The design of either is that it will match the first schema that validates the object. Dictionary is also a bit softer now and doesn't complain about unknown entries by default. So strictly speaking, this behaviour is correct, but I agree that it's counterintuitive at best and undesirable at worst.

Just at work at the moment but I'll have a play around so see what solution might work best, though I'd very much appreciate your input.

My first thought is a mutual exclusivity parameter on either, but I think I might need to add per-schema z-ctx management to change dictionary too. I'll post some proofs of concept later, if you can let me know which one "feels" best.

mindbound commented 1 month ago

Sure, I'll comment on your proposed proofs of concept, this seems like a logical extension to the either behaviour.

JamesxX commented 1 month ago

Right so having untangled the bug a bit, here's what was happening:

Either accepts the first schema that validates the object as being the correct one, and it does so by asking if its validation step returned anything (or, not none). Dictionaries are kind of special in that they return an empty dictionary even when they fail because it makes things a bit easier on the end programmer. Specifically, they return either an empty dictionary, or the input dictionary. A step in a dictionary validation is insert child fields, which come from the validation step of those children (themselves sometimes returning none if validation fails). It means you got failed validations that returned non-empty dictionaries.

Phew.

This lead to a few big changes, which I'll outline below:

Changes are made available on this development branch: https://github.com/typst-community/valkyrie/tree/27-mutually-exclusive-choices-in-either

For reference, this is how your code would look:

#let schema = z.either(
  strict: true,
  z.dictionary((
    seed: z.integer(),
  )),
  z.dictionary((
    dynamic: z.boolean(),
  )),
)
mindbound commented 1 month ago

LGTM! I'll test this and let you know if anything unexpected comes up.

emilyyyylime commented 1 month ago

I could also see a distinct z.object() or something, referring to a strict dictionary

mindbound commented 1 month ago

Everything seems to be working fine for me.