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

Proposal: Ternary operator #1009

Open
BerkliumBirb opened this issue Jan 9, 2025 · 6 comments
Open

Proposal: Ternary operator #1009

BerkliumBirb opened this issue Jan 9, 2025 · 6 comments
Assignees
Labels
enhancement New feature or request

Comments

@BerkliumBirb
Copy link

BerkliumBirb commented Jan 9, 2025

As discussed in #994 would be great to have a ternary validation operator that enables branching of validation.

It declaration should look something like this:

function ternary<
  selectorSchema extends v.Schema,
  positiveSchema extends v.Schema,
  negativeSchema extends v.Schema
>(
  selectorSchema: selectorSchema,
  positiveSchema: positiveSchema,
  negativeSchema: negativeSchema
)

type TernaryInput = v.InferInput<selectorSchema> | v.InferInput<negativeSchema>
type TernaryOutput = v.InferOutput<positiveSchema> | v.InferOutput<negativeSchema>

type TernaryIssues = v.InferIssue<positiveSchema> | v.InferIssue<negativeSchema>

When applied operator follows algorithm:

  • positive ::= v.is(selectorSchema, input)
  • if positive, then
    • apply positiveSchema
    • return output and any issues ecountered by applying positiveSchema
  • else if not positive, then
    • apply negativeSchema
    • return output and any issues encountered by applying negativeSchema

would be used something like this:

const fileLocationSchema = v.ternary(
  v.url(),
  // positiveSchema:
  v.pipe(
    v.startsWith('http', 'Only http based URLs are accepted'),
    v.check(hasPort, 'URL should explicitly provide port to be used')
  ),
  // negativeSchema:
  v.pipe(
    v.check(isPathLike, 'Should be path or URL'),
    v.check(isAbsolutePath, 'Only absolute path is allowed')
  )
)

v.parse(fileLocationSchema, 'ftp://@')
// ^ Error: Only http based URLs are accepted
v.parse(fileLocationSchema, './something')
// ^ Error: Only absolute path is allowed
v.parse(fileLocationSchema, '  some thing  !!! ### ')
// ^ Error: Should be path or URL
@fabian-hiller
Copy link
Owner

Thank you for the great issue description. pipe requires a schema as its first argument (see our mental model guide). Therefore, I would probably change the API to:

const Schema = v.pipe(
  v.string(),
  v.ternary(
    v.url(),
    // positive actions:
    [
      v.startsWith('http', 'Only http based URLs are accepted'),
      v.check(hasPort, 'URL should explicitly provide port to be used'),
    ],
    // negative actions:
    [
      v.check(isPathLike, 'Should be path or URL'),
      v.check(isAbsolutePath, 'Only absolute path is allowed'),
    ]
  )
);

Another approach to the ternary API could be something similar to variant called strain (name is just an example), which decides which validation to choose based on a discriminator validation:

const Schema = v.pipe(
 v.string(),
 v.strain(
   [v.email(), [v.endsWith('@org.com'), v.check(isEmailAvailable)]],
   [v.check(isPhoneNumber), [v.check(isPhoneAvailable)]],
   'Fallback error message'
 )
);

@fabian-hiller fabian-hiller self-assigned this Jan 9, 2025
@fabian-hiller fabian-hiller added the enhancement New feature or request label Jan 9, 2025
@BerkliumBirb
Copy link
Author

Yep, strain looks much more versetile and "Fallback error" as a separate argument is much better than "Should be path or URL" in negative branch in my initial suggestion!

Yet, I don't think that we can really scale v.strain with arrays as arguments, because it'll quickly go out of hand with hundreds of Ts in generic declaration for 2 axes. So I would suggest something like this instead:

const Schema = v.pipe(
  v.string(),
  v.strain(
    v.strainBranch(
      v.email(),
      v.endsWith('@org.com'),
      v.check(isEmailAvailable)
    ),
    v.strainBranch(
      v.check(isPhoneNumber),
      v.check(isPhoneAvailable)
    ),
    'Fallback error'
  )
)

@fabian-hiller
Copy link
Owner

I will probably focus on our v1 release before coming back to this issue. Feel free to explore the API design further (including completely different ideas) and share your findings. In the meantime, since Valibot is modular, you can always write your own schemas, actions and methods to support such a feature in your own codebase.

@bmthd
Copy link

bmthd commented Jan 22, 2025

The ternary and strain features are exactly what I was looking for.
If these functionalities were implemented as standard APIs, users would not be confused.
Just as if and switch serve different use cases, I believe both should be implemented.
I hope these features will be included as standard APIs in a future release!

@fabian-hiller
Copy link
Owner

Thanks for your feedback! Which API do you prefer? Do you have alternative solutions we should consider?

@bmthd
Copy link

bmthd commented Jan 22, 2025

Thank you for the great issue description. pipe requires a schema as its first argument (see our mental model guide). Therefore, I would probably change the API to:

const Schema = v.pipe(
v.string(),
v.ternary(
v.url(),
// positive actions:
[
v.startsWith('http', 'Only http based URLs are accepted'),
v.check(hasPort, 'URL should explicitly provide port to be used'),
],
// negative actions:
[
v.check(isPathLike, 'Should be path or URL'),
v.check(isAbsolutePath, 'Only absolute path is allowed'),
]
)
);
Another approach to the ternary API could be something similar to variant called strain (name is just an example), which decides which validation to choose based on a discriminator validation:

const Schema = v.pipe(
v.string(),
v.strain(
[v.email(), [v.endsWith('@org.com'), v.check(isEmailAvailable)]],
[v.check(isPhoneNumber), [v.check(isPhoneAvailable)]],
'Fallback error message'
)
);

This function signature is consistent and looks very good!

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

No branches or pull requests

3 participants