Open chriskuech opened 5 months ago
Is this what you are looking for?
const schema = z.union( [
z.string().url(),
z.string().transform( x => x.startsWith( 'https://' ) ? x : `https://${ x }` )
] )
console.log( schema.parse( 'foo.com/bar/baz' ) ) // https://foo.com/bar/baz
If you found my answer satisfactory, please consider supporting me. Even a small amount is greatly appreciated. Thanks friend! 🙏 https://github.com/sponsors/JacobWeisenburger
I understand the desire for something like this and your use case makes sense. For now I recommend using pipe.
z.string().transform(normalizeUrlLikeString).pipe(z.string().url())
Hopefully it's clear why you can't call .url()
after .transform()
, since your transform could return a value of any data type, not just strings.
Zod could to support some way to mutate input in a non-type-transforming way. This would differ from transform, which lets you transform the input at will and return a value of any type. The .mutate
method would return an instance of the same class, instead of a ZodTransform.
z.string().mutate(val => {
return val.startsWith("http") ? val : `http://${val}`
})
// => returns a ZodString instance
Mutations would differ from transforms in that they can't modify the inferred type. Zod would enforce this statically. The only argument against is that I think this distinction would be lost on most people and there's potential for confusion.
It would perhaps be less confusing to somehow support this inside superRefine
. You could return the new value from .superRefine
, for instance. Or .superRefine
could provide a method like resolve
on the ctx
object (similar to the notion of Promise
's resolve.)
z.string().superRefine((val, ctx) => {
ctx.val; // access input data
ctx.addIssue(..); // add issues (similar to super refine)
ctx.resolve(val.startsWith("http") ? val : `http://${val}`);
});
Still weighing these options. Leaving open for discussion.
Maybe this could just be solved with an option passed to the .url()
? Like z.string().url({ protocolOption: true })
? Best is probably just to handle it via pipe
though, as I think the way this is handled is very custom depending on the case.
Problem
Users typically do not include the protocol when hand-typing links. As such, applying
z.string().url()
leads to unintuitive use experience when validating user input. Further a custom transformation (ex:z.string().transform(normalizeUrlLikeString).url()
) is not supported by Zod.Proposed solution
A
.toUrl()
transformation function that will--https://
if the string does not include a protocol