zod
zod copied to clipboard
Add an option to .refine() to abort early
As mentioned in https://github.com/colinhacks/zod#abort-early, chained refinements are all executed, which may not be desirable in all cases. That is why superRefine has a way of aborting early - preventing further refinements from being executed.
I propose adding this functionality to refine too.
Since refine currently does output type narrowing, but superRefine does not (#1602), this may be extra desirable in cases like #1598 where not aborting early may lead to unexpected exceptions.
Interestingly, the refinement method (not mentioned in docs) takes an IssueData parameter just like RefinementCtx.addIssue in superRefine, which allows the refinement to abort early using fatal.
const baseSchema = z.object({
type: z.literal('Staff'),
age: z.number(),
driverLicenseId: z.string(),
});
const refineSchema = baseSchema
.refine( // <--
({ type, age }) => {
switch (type) {
case 'Staff':
return age >= 18;
}
},
// Always z.ZodIssueCode.custom
{
path: ['age'],
message: 'Staff must at least 18.',
// No fatal option
}
)
// Continues validation
.refine(
({ age, driverLicenseId }) =>
driverLicenseId.slice(0, 2) === age.toString(),
{ path: ['driverLicenseId'], message: "Invalid driver's license." }
);
const refinementSchema = baseSchema
.refinement( // <--
({ type, age }) => {
switch (type) {
case 'Staff':
return age >= 18;
}
},
{
code: z.ZodIssueCode.custom, // specifiable error code
path: ['age'],
message: 'Staff must at least 18.',
fatal: true, // has the fatal field
}
)
.refine(
({ age, driverLicenseId }) =>
driverLicenseId.slice(0, 2) === age.toString(),
{ path: ['driverLicenseId'], message: "Invalid driver's license." }
);
const input = { type: 'Staff', age: 17, driverLicenseId: '18xxyy' };
const refineResult = refineSchema.safeParse(input);
const refinementResult = refinementSchema.safeParse(input);
console.log(!refineResult.success && refineResult.error.issues);
console.log(!refinementResult.success && refinementResult.error.issues);
[
{
code: 'custom',
path: [ 'age' ],
message: 'Staff must at least 18.'
},
{
code: 'custom',
path: [ 'driverLicenseId' ],
message: "Invalid driver's license."
}
]
[
{
code: 'custom',
path: [ 'age' ],
message: 'Staff must at least 18.',
fatal: true
}
]
@DanielBreiner thanks for the PR! Good observation re: refinement method. Given that there's now
refine()refinement()superRefine()- possibly
megaRefine()(just joking :D)
I wonder if the APIs are due for streamlining? There's a couple of differences, including that refine will take Promise<T> but refinement won't, among other details. My opinion on this is that you want
- simple, quick refinement (
refine) - heavy duty customizing (
superRefine)
Not sure how other folks are using refine or what's the idea going forward on this @colinhacks
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.