Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve performance and issues for nested variants #809

Merged
merged 2 commits into from
Sep 1, 2024

Conversation

fabian-hiller
Copy link
Owner

@fabian-hiller fabian-hiller commented Aug 31, 2024

Fix #794

I have implemented a completely new algorithm for variant that supports multiple nestes discriminator keys. The following describes how the algorithm works.

The algorithm searches for the correct object schema to use to validate the input based on the specified discriminator keys. It gathers information about the first deepest invalid discriminator, if any. Discriminators present in the input have priority over missing ones. An object schema is used for validation only if all specified discriminators are valid. If no object schema is found that matches the specified discriminators, variant will use the collected information about the first deepest invalid discriminator to return a useful issue.

@fabian-hiller fabian-hiller added the enhancement New feature or request label Aug 31, 2024
@fabian-hiller fabian-hiller self-assigned this Aug 31, 2024
@fabian-hiller fabian-hiller mentioned this pull request Aug 31, 2024
@fabian-hiller
Copy link
Owner Author

fabian-hiller commented Aug 31, 2024

@ZerNico the new algorithm returns the same result for both writing styles of nested variants:

import * as v from 'valibot';

const Schema1 = v.variant('kind', [
  // kind: 'A'
  v.variant('type', [
    v.object({
      kind: v.literal('A'),
      type: v.literal('A'),
    }),
    v.object({
      kind: v.literal('A'),
      type: v.literal('B'),
    }),
  ]),
  // kind: 'B'
  v.variant('type', [
    v.object({
      kind: v.literal('B'),
      type: v.literal('A'),
    }),
    v.object({
      kind: v.literal('B'),
      type: v.literal('B'),
    }),
  ]),
]);

const Schema2 = v.variant('kind', [
  v.variant('type', [
    // kind: 'A'
    v.object({
      kind: v.literal('A'),
      type: v.literal('A'),
    }),
    v.object({
      kind: v.literal('A'),
      type: v.literal('B'),
    }),
    // kind: 'B'
    v.object({
      kind: v.literal('B'),
      type: v.literal('A'),
    }),
    v.object({
      kind: v.literal('B'),
      type: v.literal('B'),
    }),
  ]),
]);

By also supporting an array as the first argument, we could even support a third writing style:

import * as v from 'valibot';

const Schema3 = v.variant(
  ['kind', 'type'],
  [
    // kind: 'A'
    v.object({
      kind: v.literal('A'),
      type: v.literal('A'),
    }),
    v.object({
      kind: v.literal('A'),
      type: v.literal('B'),
    }),
    // kind: 'B'
    v.object({
      kind: v.literal('B'),
      type: v.literal('A'),
    }),
    v.object({
      kind: v.literal('B'),
      type: v.literal('B'),
    }),
  ]
);

With a few small tweaks, the new algorithm could easily support the third option, making our API even more flexible. However, there are some drawbacks.

It increases the bundle size by about 15 to 20 bytes. This doesn't sound like much, but the new algorithm has already increased the bundle size by about 70 bytes. So we would increase it even more just to support an additional writing style. Another drawback is that if we support both a single discriminator key and a list of discriminator keys as the first argument, our API becomes more complicated and harder to learn and understand. We could force users to always specify an array even for a single discriminator key, but that would introduce a breaking change (which is ok since we are still in v0). I look forward to getting feedback on this.

Copy link

pkg-pr-new bot commented Sep 1, 2024

Open in Stackblitz

pnpm add https://pkg.pr.new/valibot@809

commit: 139d865

@fabian-hiller
Copy link
Owner Author

I will merge this now. However, you can still give me feedback.

@fabian-hiller fabian-hiller merged commit 0dfd1bd into main Sep 1, 2024
8 checks passed
@ZerNico
Copy link
Contributor

ZerNico commented Sep 2, 2024

@fabian-hiller sounds like a very good solution. Also thanks for the quick fix, you are awesome :)

For allowing multiple discriminators I am not sure I'd like the breaking change to force setting the keys as an array. Even if it's still v0 it might not be super wise to annoy users with breaking changes for features most will probably not use. Also makes the usage a bit more verbose for everyone.
Though I agree having both options way to write would make it more confusing. Might still be the best solution for this case. Would be nice if TypeScript would allow rest params for the first param too, would have been a nice solution.

@fabian-hiller
Copy link
Owner Author

Thanks for your feedback. Let's keep it as is for now, and in the long run I will consider supporting both a string and an array of strings for the first argument if more users request this API design.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Nested variants
2 participants