zod
zod copied to clipboard
add `ZodURL` to validate parts of a url.
Hey 👋
Currently we got z.string().url(). It remains a string after the check.
There are more things we could validate in a url, but it'll feel really awkward to put url-specific methods on ZodString.
Now that we have z.coerce & z.pipe, might be a good idea to add the following options:
z.url(); // native `URL` instance.
z.coerce.url(); // coerce to native `URL` instance.
z.string().transform(value => new URL(value)).pipe(z.url()); // pipe to native `URL` instance for further checks.
and have checks like:
z.url().protocol('https');
z.url().port(1234);
z.url().search({
age: z.coerce.number().int().min(1),
}); // alternatively could be called `searchParams`.
z.url().username('admin');
z.url().password('123456789');
z.url().pathname(/^\/users\/\d+\/settings$/);
// etc. etc..
I'd love to open a PR if this sounds good..
I like this.
But I also think this has a very strict use case since you could be validating all that with a string regex.
But again, I like this. If I were you I would go ahead and submit a PR. Let's see what you come up with. Tag me on the PR so I can also help if you want.
But I also think this has a very strict use case since you could be validating all that with a string regex.
There are 2 downsides to doing it with a regex:
- regexes, unless stupidly simple, add complexity - harder to understand for some.
- search params can be partial, ordered in various ways, or multi-value.
i. a
.refine/.superRefinewill probably fit this problem better, but at the cost of an ugly block of code that consumer has to maintain. ii. the cost of supporting more search params gets much bigger than simply adding a field to a shape.
But again, I like this. If I were you I would go ahead and submit a PR. Let's see what you come up with. Tag me on the PR so I can also help if you want.
Wanted to hear from @colinhacks, and maybe have a discussion, since this is not a small one and potentially doesn't align with zod's goals/plans.
Thanks for the offer! 🥇
~~I'll start something but not go fully into it until this is "greenlit" I guess..~~
I like a lot of these ideas, but this is a big API surface with a lot of potential headaches.
Some open questions:
Will .pathname(), .user(), .password() etc. support regexes? Arrays of strings? Zod schemas?
z.url().password(z.string().min(10))
If a method exists, people will eventually ask for all of these things. But the end result is a method with a ton of overloads, and the DX starts feeling a little chaotic. There aren't a lot of methods like that in Zod (by design).
I like .protocol() but we may want to steer clear of .username/.password/.port/.pathname for that reason. Of course its a bit weird that only one property of URL would have its own method.
The search params stuff is a big can of worms. If the return type of z.url() is URL then how to we return the parsed/typed search params to the user?
const schema = z.url().search({
names: z.string().transform(val => val.split(","))
});
const result = schema.parse("http://localhost?names=jim,bob");
result; // => URL
result.searchParams; // => URLSearchParams
// how to retrieve the post-parse search params as { names: string[] }?
The result could just be URL & { params: <inferred search params type> }?
Other URLSearchParams things:
- Do we let users specify whether a field in the
URLSearchParamsshould be considered a multi-value (.getAll()) or single value (.get())? - Should there be an equivalent of
.strict()and.passthrough()for the returned search params?
URLSearchParams and FormData (which are very similar in their API) may get a separate API entirely: https://github.com/colinhacks/zod/issues/1630
Sorry, brain dump. There's a lot to think about here, and we should definitely hash out details before anyone starts on implementation.
@colinhacks thanks for the detailed comment!
I like a lot of these ideas, but this is a big API surface with a lot of potential headaches.
Some open questions:
Will .pathname(), .user(), .password() etc. support regexes? Arrays of strings? Zod schemas?
z.url().password(z.string().min(10))If a method exists, people will eventually ask for all of these things. But the end result is a method with a ton of overloads, and the DX starts feeling a little chaotic. There aren't a lot of methods like that in Zod (by design).
Could we limit these to just z.string() or any zod types that input & output a string, to avoid "overload hell"?
z.url().pathname(z.string().regex(regex)); // the regex case.
z.url().username(z.enum(['admin', 'internal'])); // the array of strings case.
z.url().username(z.literal('admin').or(z.literal('internal'))); // another option for the array of strings case.
This opens up the possibility of .transform, but URL allows to assign new values to these fields, so we can return a new instance with parsed values without adding url building logic to the codebase.
I like .protocol() but we may want to steer clear of .username/.password/.port/.pathname for that reason. Of course its a bit weird that only one property of URL would have its own method.
Having the type, and some (but not all) ways to validate it, is a start.
The search params stuff is a big can of worms. If the return type of z.url() is URL then how to we return the parsed/typed search params to the user?
const schema = z.url().search({ names: z.string().transform(val => val.split(",")) }); const result = schema.parse("http://localhost/?names=jim,bob"); result; // => URL result.searchParams; // => URLSearchParams // how to retrieve the post-parse search params as { names: string[] }?The result could just be
URL & { params: <inferred search params type> }?
Yeah, didn't think about the part where the consumer wants his parsed search params back. 😄
I think providing the parsed object as an extra field is a good idea.
Other
URLSearchParamsthings:
- Do we let users specify whether a field in the
URLSearchParamsshould be considered a multi-value (.getAll()) or single value (.get())?
Under the hood we could always use .getAll(). If consumer didn't provide an array type and getAll().length > 1 that's an error.
- Should there be an equivalent of
.strict()and.passthrough()for the returned search params?
I think that's a good idea.
URLSearchParamsandFormData(which are very similar in their API) may get a separate API entirely: https://github.com/colinhacks/zod/issues/1630
Seems like URLSearchParams should wait for FormData to be introduced.
I've got a pretty good working version of something that can handle searchParams in #1697.
I decided that if the schema is expecting an array then we'll treat it like an array, but if it's not it'll be invalid if there's more than one value. That seems reasonable to me.
I took kent's idea and expanded on it here: #1770.
Thanks @kentcdodds for the idea. I hope I'm not stealing your thunder.
Please steal the thunder!