Add a label function as like yup
I have used YUP which provides a label for changing the value of key. like address:
yup.string().required().max(150).label('Address')
But Zod has not any simple function as like that.
Maybe you could use .describe()? It's not in the docs, but there's an open issue for that #1443
https://github.com/jquense/yup#schemalabellabel-string-schema
Schema.label(label: string): SchemaOverrides the key name which is used in error messages.
https://github.com/jquense/yup#tuple
import { tuple, string, number, InferType } from 'yup'; let schema = tuple([ string().label('name'), number().label('age').positive().integer(), ]); await schema.validate(['James', 3]); // ['James', 3] await schema.validate(['James', -24]); // => ValidationError: age must be a positive number InferType<typeof schema> // [string, number] | undefined
I'm not sure what .describe() does, but I don't think it's the same.
I don't see age in the error
const schema = z.tuple( [
z.string().describe( 'name' ),
z.number().describe( 'age' ).positive().int(),
] )
const result = schema.safeParse( [ 'James', -24 ] )
!result.success && console.log( result.error.issues[ 0 ] )
// {
// code: 'too_small',
// minimum: 0,
// type: 'number',
// inclusive: false,
// exact: false,
// message: 'Number must be greater than 0',
// path: [ 1 ]
// }
Ah, no, that's my bad, I misunderstood the question. While .describe() would be analog to "description" in JSON schema I guess this would be more like "title" along with the added function of shaping the default error messages. Neat idea.
@JacobWeisenburger do you mind assigning this to me i have created a first draft for this label as well
I have tried to add that label function in ZodType but it doesn't seem to be the best way since .label() has to be first in the chain z.string().label().min(2) - labels will work for min condition but not for invalid_type or required condition
I have made a draft where I add a label in RawCreateParams and then it gets available in this._def.label which can be passed down further addIssueToContext -> makeIssue -> defaultErrorMap labels can be used here
one problem tho label has to be passed everywhere in addIssueToContext
I am sorry if I did something wrong I hope you overlook it since it's my first commit in open source
current draft : https://github.com/RG7279805/zod/tree/label-in-raw-params will create a PR after confirmation
PRs are always welcome. No need to wait for confirmation.
@JacobWeisenburger i need your opinion before i continue with this PR as i mentioned should the label be as a method to class or its ok as RawCreateParams ?
I don't know that I have enough experience to make a decision about this. Perhaps @colinhacks would be a better person to ask.
@JacobWeisenburger any idea what would be the best way to approach him? he hasn't responded to my last issue as well on https://github.com/colinhacks/zod/issues/1784#issuecomment-1379492840
Perhaps discord? https://discord.gg/RcG33DQJdf
@JacobWeisenburger i have Messaged him on discord but i don't think he is gonna respond Nonetheless i will wait
Sorry. I wish I had a better answer for you.
I don't love this API - I think it makes the process of "computing" error messages even more inscrutable. What does it even mean to call this method on a ZodObject schema? Or a ZodEffects?
I don't mean to say that this idea has no value. But I think limiting labels to primitive schemas would be reasonable. Which means defining it in params instead of with .label method.
z.string({ label: "Name" })
Even then I'd like to see a proposal for how this information would be used in the defaultErrorMap before anyone starts on implementation.
Though it'll have to be a markedly superior experience to the current approach, which is to specify custom error messages at the time of declaration:
z.string({ required_error: "Name must be defined", invalid_type_error: "Name must be a string" })
Which can be made less verbose with a helper function that returns { required_error: string; invalid_type_error: string }. It's already pretty clean and straightforward IMO so I'm hesistant to introduce additional complexity here unless there's a dramatic DX improvement to be had.
@colinhacks, as you asked i, have made a working draft https://github.com/colinhacks/zod/pull/1902 (i messed up linking PR to this issue)
z.object({
first: z.string({ label: "First Name", }).min(3),
})
it's only working for z.object({ first: z.string({ label: "First Name", }).min(3), }) at the moment but you will get an idea of how this is gonna + I have also added labeled messages in "en.ts" (all the messages are not good)
In answering your question about DX if we add labeled errors I think it will be great. when I used Zod for the first time this was a single feature I thought must exist to make this library complete
Without Labeled error
z.object({
first: z.string().min(3),
}).safeParse({ first: "AB" })
// error message
// {
// "code": "too_small",
// "minimum": 3,
// "type": "string",
// "inclusive": true,
// "exact": false,
// "message": "Invalid input",
// "path": [
// "first"
// ]
// }
we cant send it to a client or show it to a user directly we have to add customized errors everywhere even though if its a really simple message like "First name is Required" since "Invalid input" cant be sent
z.object({
first: z.string({ invalid_type_error: "first name should be of String", required_error: "First name is required" }).min(3, { message: "first name should be more than 3 char" }),
})
just to add really simple messages it becomes mandatory to include a "message" for every check
With Labeled Errors
if the label is passed all those messages can be removed and that error can be passed directly to the client/user even if they want to customize the message further they can do so
z.object({
first: z.string({ label: "First Name" }).min(3),
}).safeParse({ first: "AB" })
// {
// "code": "too_small",
// "minimum": 3,
// "type": "string",
// "inclusive": true,
// "exact": false,
// "message": "First Name must be at least 3 character(s)",
// "label": "First Name",
// "path": [
// "first"
// ]
// }
if you think this feature is worth added do you mind assigning it to me
Which can be made less verbose with a helper function that returns
{ required_error: string; invalid_type_error: string }. It's already pretty clean and straightforward IMO so I'm hesistant to introduce additional complexity here unless there's a dramatic DX improvement to be had.
I agree that it is better to point out custom errors wherever possible. But this is very difficult in an internationalized application. In that vein, it is convenient to have a global error translation and the ability to specify key labels.
@colinhacks @RG7279805 any updates on this proposal?
Currently I'm using a rather brittle workaround for the lack of this feature:
/**
* Stop-gap until https://github.com/colinhacks/zod/pull/1902 is merged.
* @param schema Schema to label
* @param label Label to apply
* @returns Schema with label
*/
export function LabelModel<TSchema extends z.ZodTypeAny>(
label: string,
schema: TSchema
) {
return Object.assign(schema, { _label: label }) as TSchema;
}
/**
* Get the inner zod type through zod arrays, objects, optional, etc.
* @param schema Schema to traverse
* @returns Inner zod type
*/
export function getSchemaInnerType<T extends z.ZodTypeAny>(
schema: T
): z.ZodTypeAny {
if (schema instanceof z.ZodArray) {
return getSchemaInnerType(schema._def.type);
}
if (schema instanceof z.ZodBranded) {
return getSchemaInnerType(schema._def.type);
}
if (schema instanceof z.ZodNullable) {
return getSchemaInnerType(schema._def.innerType);
}
if (schema instanceof z.ZodOptional) {
return getSchemaInnerType(schema._def.innerType);
}
return schema;
}
/**
* Retrieve the label from the model.
* @param schema Schema to retrieve label from
*/
export function getModelLabel<T extends z.ZodTypeAny>(schema: T): string {
// Cast as untyped here to trawl for the metadata.
const modelInfo = schema as any;
// Retrieve Model Label, this works in z.array too.
return (
modelInfo._label ??
(getSchemaInnerType(schema) as any)?._label ??
"Unknown Model"
);
}
This workaround has the limited scope of determining what model is failing to parse in a distributed system, however, information is lost if you chain additional modifiers onto the schema, so ideally, there was a better way to do it.
@WilliamABradley i have made a draft PR but haven't got any response back from colinhack
Any updates on this one?
Really wish we could have this feature, or a method to insert metadata directly into the schema
Internationalization is the main concern: