valibot icon indicating copy to clipboard operation
valibot copied to clipboard

Proposal: Ternary operator

Open BerkliumBirb opened this issue 10 months ago • 9 comments

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

BerkliumBirb avatar Jan 09 '25 13:01 BerkliumBirb

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 avatar Jan 09 '25 17:01 fabian-hiller

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'
  )
)

BerkliumBirb avatar Jan 10 '25 07:01 BerkliumBirb

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.

fabian-hiller avatar Jan 11 '25 16:01 fabian-hiller

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!

bmthd avatar Jan 22 '25 00:01 bmthd

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

fabian-hiller avatar Jan 22 '25 00:01 fabian-hiller

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!

bmthd avatar Jan 22 '25 14:01 bmthd

Hi, @BerkliumBirb. I'm Dosu, and I'm helping the Valibot team manage their backlog. I'm marking this issue as stale.

Issue Summary:

  • You suggested adding a ternary validation operator for more flexible logic.
  • Fabian-hiller proposed an alternative API design called strain.
  • You agreed with the strain approach but suggested a different implementation.
  • Fabian-hiller mentioned focusing on the v1 release before revisiting this feature.
  • bmthd expressed strong support for both ternary and strain functionalities.

Next Steps:

  • Please let me know if this issue is still relevant to the latest version of the Valibot repository by commenting here.
  • If there is no further activity, I will automatically close this issue in 30 days.

Thank you for your understanding and contribution!

dosubot[bot] avatar Jun 16 '25 16:06 dosubot[bot]

The strain API seems similar to what we have in mind with and and or in issue #835. I think we will introduce such an API in the long run but some more research is required.

fabian-hiller avatar Jun 17 '25 02:06 fabian-hiller

Hi, @BerkliumBirb. I'm Dosu, and I'm helping the Valibot team manage their backlog and am marking this issue as stale.

Issue Summary:

  • You proposed adding a ternary validation operator to simplify complex validation schemas.
  • Maintainer fabian-hiller suggested an alternative API called strain.
  • You found strain versatile but proposed refinements for scalability.
  • Another user supported both ternary and strain as standard APIs.
  • Fabian-hiller plans to focus on the v1 release first and revisit this feature later after more research.

Next Steps:

  • Please let me know if this feature is still relevant to the latest version of Valibot by commenting on this issue.
  • If I don’t hear back within 30 days, the issue will be automatically closed.

Thanks for your understanding and contribution!

dosubot[bot] avatar Sep 16 '25 16:09 dosubot[bot]