Added AI skills
This commit is contained in:
152
.agents/skills/zod/references/refine-vs-superrefine.md
Normal file
152
.agents/skills/zod/references/refine-vs-superrefine.md
Normal file
@@ -0,0 +1,152 @@
|
||||
---
|
||||
title: Choose refine() vs superRefine() Correctly
|
||||
impact: MEDIUM
|
||||
impactDescription: refine() only reports one error; superRefine() enables multiple issues and custom error codes
|
||||
tags: refine, superRefine, validation, custom
|
||||
---
|
||||
|
||||
## Choose refine() vs superRefine() Correctly
|
||||
|
||||
`.refine()` is for simple single-condition validation returning boolean. `.superRefine()` gives you a context object to add multiple issues with custom error codes and paths. Choose based on your error reporting needs.
|
||||
|
||||
**Incorrect (using refine for multiple checks):**
|
||||
|
||||
```typescript
|
||||
import { z } from 'zod'
|
||||
|
||||
// refine can only report one error at a time
|
||||
const passwordSchema = z.string().refine(
|
||||
(password) => {
|
||||
// Checks all conditions but only reports first failure
|
||||
if (password.length < 8) return false // Only this error shown
|
||||
if (!/[A-Z]/.test(password)) return false
|
||||
if (!/[0-9]/.test(password)) return false
|
||||
return true
|
||||
},
|
||||
{ message: 'Password does not meet requirements' }
|
||||
)
|
||||
|
||||
passwordSchema.parse('weak')
|
||||
// Only shows: "Password does not meet requirements"
|
||||
// User doesn't know WHICH requirements failed
|
||||
```
|
||||
|
||||
**Correct (using superRefine for multiple issues):**
|
||||
|
||||
```typescript
|
||||
import { z } from 'zod'
|
||||
|
||||
const passwordSchema = z.string().superRefine((password, ctx) => {
|
||||
if (password.length < 8) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: 'Password must be at least 8 characters',
|
||||
})
|
||||
}
|
||||
|
||||
if (!/[A-Z]/.test(password)) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: 'Password must contain an uppercase letter',
|
||||
})
|
||||
}
|
||||
|
||||
if (!/[0-9]/.test(password)) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: 'Password must contain a number',
|
||||
})
|
||||
}
|
||||
|
||||
if (!/[!@#$%^&*]/.test(password)) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: 'Password must contain a special character',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
passwordSchema.safeParse('weak')
|
||||
// Shows ALL failures:
|
||||
// - "Password must be at least 8 characters"
|
||||
// - "Password must contain an uppercase letter"
|
||||
// - "Password must contain a number"
|
||||
// - "Password must contain a special character"
|
||||
```
|
||||
|
||||
**When to use refine():**
|
||||
|
||||
```typescript
|
||||
// Simple boolean condition with one error message
|
||||
const adultSchema = z.number().refine(
|
||||
(age) => age >= 18,
|
||||
{ message: 'Must be 18 or older' }
|
||||
)
|
||||
|
||||
// Cross-field validation with single outcome
|
||||
const formSchema = z.object({
|
||||
password: z.string(),
|
||||
confirmPassword: z.string(),
|
||||
}).refine(
|
||||
(data) => data.password === data.confirmPassword,
|
||||
{ message: 'Passwords must match', path: ['confirmPassword'] }
|
||||
)
|
||||
|
||||
// Async validation
|
||||
const emailSchema = z.string().email().refine(
|
||||
async (email) => {
|
||||
const exists = await checkEmailExists(email)
|
||||
return !exists
|
||||
},
|
||||
{ message: 'Email already registered' }
|
||||
)
|
||||
```
|
||||
|
||||
**When to use superRefine():**
|
||||
|
||||
```typescript
|
||||
// Multiple independent checks on same value
|
||||
// Cross-field validation with multiple possible errors
|
||||
// Need custom error codes for i18n or client handling
|
||||
// Need to add issues at specific paths
|
||||
|
||||
const orderSchema = z.object({
|
||||
items: z.array(z.object({
|
||||
productId: z.string(),
|
||||
quantity: z.number(),
|
||||
})),
|
||||
promoCode: z.string().optional(),
|
||||
}).superRefine(async (order, ctx) => {
|
||||
// Check each item's availability
|
||||
for (let i = 0; i < order.items.length; i++) {
|
||||
const item = order.items[i]
|
||||
const available = await checkInventory(item.productId, item.quantity)
|
||||
|
||||
if (!available) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ['items', i, 'quantity'], // Specific path
|
||||
message: `Only ${available} units available`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Validate promo code
|
||||
if (order.promoCode) {
|
||||
const valid = await validatePromoCode(order.promoCode)
|
||||
if (!valid) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ['promoCode'],
|
||||
message: 'Invalid or expired promo code',
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**When NOT to use this pattern:**
|
||||
- Simple single-condition checks (use refine for simplicity)
|
||||
- Transform needed instead of validation (use transform)
|
||||
|
||||
Reference: [Zod API - refine/superRefine](https://zod.dev/api#refine)
|
||||
Reference in New Issue
Block a user