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

feature request; parse that only accepts compatible inputs #1024

Open
tuchandra opened this issue Jan 21, 2025 · 3 comments
Open

feature request; parse that only accepts compatible inputs #1024

tuchandra opened this issue Jan 21, 2025 · 3 comments
Assignees
Labels
enhancement New feature or request

Comments

@tuchandra
Copy link

tuchandra commented Jan 21, 2025

tldr—I'd like to see a function similar to parse, but that only accepts inputs known to be compatible with the schema.

// existing
parse<TSchema>(schema: TSchema, input: unknown): InferOutput<TSchema>;

// proposed
parse<TSchema>(schema: TSchema, input: InferInput<TSchema>): InferOutput<TSchema>;

Since parse (and safeParse, etc.) are all annotated with input: unknown, it is possible to pass an object that will never match the schema. The resulting code is statically correct, but will always fail to parse at runtime.

Simple example

playground link

I can produce a more realistic example if this isn't compelling, but my request here is essentially for the library to guard against this.

I know that a workaround is to annotate the input objects as v.InferInput<Schema>. But, me knowing myself, I don't think "trust that future-me will remember to do this every time" is a sustainable approach 🙂

Thank you for the great library; valibot is really a delight to use and I have already noticed it making my code clearer and safer.

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

Thank you for creating this issue! What is your use case? Why are you validating when you already know the data? I am asking to better understand the use case and make a better decision. How would you name such a function?

@tuchandra
Copy link
Author

Thank you for the reply! (I've revised this comment several times and am having a hard time figuring out whether or not it's coming across clearly, so apologies in advance if not ...)

But roughly, I have a codebase with plenty of types, but no schemas. In new code, I'll define schemas and then use v.safeParse at the boundary of my module. If successful, the rest of the code can take advantage of the other features of valibot like composing with other schemas. If not, I'll throw an error or return issues back to the caller.

This normally means that:

  • I have some data whose type I know statically
  • I have a schema that relates to the type of said data
  • I use valibot to parse the incoming data against my schema
type Result =
  | { status: 'SUCCESS'; data: SomeOtherType}
  | { status: 'ERROR'; issues: string[]};

const MySchema = v.object({ 
  id: v.number,
  name: v.string,
  // etc ...
});
type MySchema = v.InferOutput<MySchema>;

function processData(input: Result): MySchema {
  if (input.status === 'SUCCESS') {
    return v.parse(MySchema, input);  // will always throw!
  }

 // ... other code ...
}

The thing is—if I didn't use valibot at all, TypeScript will happily surface the error:

function processData(input: Result): {id: number; name: string} {
  if (input.status === 'SUCCESS') {
    return input;  // type checking error
  }

  // ... other code ...
}

Another way of framing this is that, with TypeScript alone as the boundary between other code and my own, issues like this will create static type errors like usual. When adding valibot, the parse / safeParse functions erase the statically-known information that the code will never be correct.

Does this help clarify?

@fabian-hiller
Copy link
Owner

Not quite sure I can follow you. Does this work for you?

type Result<T> =
  | { status: 'SUCCESS'; data: T }
  | { status: 'ERROR'; issues: string[] };

type Product = {
  id: number;
  name: string;
  // ...
};

function processProduct(input: Result<Product>): Product {
  if (input.status === 'SUCCESS') {
    return input.data;
  }
  throw new Error('...');
}

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

2 participants