fabian-hiller / valibot

The modular and type safe schema library for validating structural data 🤖
https://valibot.dev
MIT License
6.32k stars 204 forks source link

[feat]: add `not[Undefined | Null | Nullish]Value` actions that narrow output types in a pipeline #916

Closed EltonLobo07 closed 1 week ago

EltonLobo07 commented 2 weeks ago

Let's assume I want to convert a string to a positive integer using Valibot.

These are the possible solutions I have think of:

Note: The examples shown in this entire message block do not cover all of the edge cases, they are just meant to explain the idea.

Solution 1:

const StrToPositiveIntSchema = v.pipe(
  v.string(),
  v.trim(),
  // I have to check the input to make sure the transformation does not fail
  v.regex(/^\+?\d+$/u, 'The input does not represent a positive integer'),
  v.transform(Number),
);

Solution 2:

const notPositiveIntMsg = 'The input does not represent a positive integer'; 

const StrToPositiveIntSchema = v.pipe(
  v.string(),
  v.trim(),
  v.transform(Number),
  // I have to check the output to make sure the transformed value is valid
  v.integer(notPositiveIntMsg),
  v.minValue(0, notPositiveIntMsg),
);

I have to check the input or output values. This is okay for simple checks but sometimes checking the input or output values might be difficult. notUndefinedValue and related actions can help improve the experience. Users can return a nullish value to indicate invalid transformation and then use one of the proposed actions to let Valibot know the transformation was not valid like in this example:

const StrToPositiveIntSchema = v.pipe(
  v.string(),
  v.trim(),
  v.transform((input) => {
      const inputNum = Number(input);
      return !Number.isInteger(inputNum) || inputNum < 0
      ? undefined
      : inputNum;
  }),
  v.notUndefinedValue('The input does not represent a positive integer'),
  // Notice `value: number` instead of `value: number | undefined`?
  // It would be better if the action removed the `undefined` type
  // as the above action has already tested `undefined`
  v.check((value: number) => true)
);

One possible solution right now is to use the check action to test undefined. But this action does not narrow types. Even if it did, I think the proposed actions can still be useful to improve the overall experience as nullish values are pretty common.

fabian-hiller commented 2 weeks ago

The idea is great, but I need to think about it. notUndefinedValue is basically a mix of a validation and a transformation action that works similar to a schema function. It validates the input and returns an issue if necessary, but it also transforms the output type.

Another solution is to use a schema function after the transformation to ensure the output type of it:

const StringToNumber = v.pipe(
  v.string(),
  v.transform(Number),
  v.number(),
);
EltonLobo07 commented 1 week ago

Yes, I realized notUndefinedValue works similar to a schema function. We already have nonOptionalSchema and related, so maybe we shouldn't add notUndefinedValue or something similar just yet. I think checking the input or output of a transformation is better than adding a similar schema (notUndefinedValue similar to nonOptionalSchema) for cases described in the description. Feel free to close the issue.

fabian-hiller commented 1 week ago

True. Totally forgot nonOptional, nonNullable and nonNullish. 🙈