Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:22:35 +08:00
commit 6fcffca9b0
35 changed files with 8235 additions and 0 deletions

View File

@@ -0,0 +1,175 @@
# Validation Error Handling
## Basic Error Handling
### Using safeParse
```typescript
const result = UserSchema.safeParse(data);
if (result.success) {
const user: User = result.data;
console.log(user.name);
} else {
console.error("Validation failed:", result.error);
}
```
### Using parse (throwing)
```typescript
try {
const user = UserSchema.parse(data);
console.log(user.name);
} catch (error) {
if (error instanceof z.ZodError) {
console.error("Validation failed:", error.issues);
}
}
```
## Processing Validation Issues
### Extracting Error Details
```typescript
if (!result.success) {
const issues = result.error.issues.map(issue => ({
path: issue.path.join("."),
message: issue.message,
code: issue.code
}));
throw new ValidationError("Invalid user data", issues);
}
```
### User-Friendly Error Messages
```typescript
function formatValidationError(error: z.ZodError): string {
return error.issues
.map(issue => {
const field = issue.path.join(".");
return `${field}: ${issue.message}`;
})
.join(", ");
}
try {
const validated = validateLoginForm(form);
await login(validated);
} catch (error) {
if (error instanceof z.ZodError) {
const message = formatValidationError(error);
showError(message);
}
}
```
## Error Recovery Strategies
### Fallback Values
```typescript
const result = ConfigSchema.safeParse(data);
const config = result.success
? result.data
: getDefaultConfig();
```
### Partial Success
```typescript
function validateItemsWithPartialSuccess<T>(
items: unknown[],
schema: z.ZodType<T>
): { valid: T[], invalid: { item: unknown, error: z.ZodError }[] } {
const valid: T[] = [];
const invalid: { item: unknown, error: z.ZodError }[] = [];
for (const item of items) {
const result = schema.safeParse(item);
if (result.success) {
valid.push(result.data);
} else {
invalid.push({ item, error: result.error });
}
}
return { valid, invalid };
}
```
## Custom Error Classes
```typescript
class ValidationError extends Error {
constructor(
message: string,
public readonly issues: Array<{ path: string; message: string }>
) {
super(message);
this.name = "ValidationError";
}
}
function validateOrThrow<T>(data: unknown, schema: z.ZodType<T>): T {
const result = schema.safeParse(data);
if (!result.success) {
const issues = result.error.issues.map(issue => ({
path: issue.path.join("."),
message: issue.message
}));
throw new ValidationError("Validation failed", issues);
}
return result.data;
}
```
## Logging Validation Errors
```typescript
function validateWithLogging<T>(
data: unknown,
schema: z.ZodType<T>,
context: string
): T | null {
const result = schema.safeParse(data);
if (!result.success) {
logger.error({
context,
error: "Validation failed",
issues: result.error.issues,
data: sanitizeForLogging(data)
});
return null;
}
return result.data;
}
```
## API Error Responses
```typescript
app.post("/api/users", (req, res) => {
const result = UserSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({
error: "Invalid request body",
details: result.error.issues.map(issue => ({
field: issue.path.join("."),
message: issue.message
}))
});
}
const user = result.data;
});
```

View File

@@ -0,0 +1,211 @@
# Validation Performance Optimization
## Schema Reuse
**BAD: Creating schemas repeatedly**
```typescript
function validateUser(data: unknown) {
const UserSchema = z.object({
id: z.string(),
name: z.string()
});
return UserSchema.parse(data);
}
```
**GOOD: Reusing schemas**
```typescript
const UserSchema = z.object({
id: z.string(),
name: z.string()
});
function validateUser(data: unknown) {
return UserSchema.parse(data);
}
```
## Boundary Validation
Validate once at system boundaries, not internally:
```typescript
async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
const data: unknown = await response.json();
return UserSchema.parse(data);
}
function processUser(user: User) {
const fullName = formatName(user);
return fullName;
}
```
## Lazy Schema Definition
For schemas with circular dependencies:
```typescript
const CategorySchema: z.ZodType<Category> = z.lazy(() => z.object({
id: z.string(),
name: z.string(),
parent: CategorySchema.optional()
}));
```
## Selective Validation
Validate only what's needed:
```typescript
const FullUserSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
profile: z.object({
bio: z.string(),
avatar: z.string().url()
}),
settings: z.object({
theme: z.enum(["light", "dark"]),
notifications: z.boolean()
})
});
const BasicUserSchema = FullUserSchema.pick({
id: true,
name: true,
email: true
});
```
## Batch Validation
For arrays, validate the array once rather than each item:
```typescript
const UsersSchema = z.array(UserSchema);
async function fetchAllUsers(): Promise<User[]> {
const response = await fetch("/api/users");
const data: unknown = await response.json();
return UsersSchema.parse(data);
}
```
## Early Validation
Fail fast on critical fields:
```typescript
const CriticalFirstSchema = z.object({
apiKey: z.string().uuid(),
timestamp: z.number()
}).strict();
const FullRequestSchema = CriticalFirstSchema.extend({
payload: z.unknown()
});
```
## Caching Parsed Results
```typescript
const schemaCache = new WeakMap<object, User>();
function getCachedUser(data: unknown): User {
if (typeof data === "object" && data !== null) {
const cached = schemaCache.get(data);
if (cached) return cached;
}
const user = UserSchema.parse(data);
if (typeof data === "object" && data !== null) {
schemaCache.set(data, user);
}
return user;
}
```
## Avoiding Redundant Validation
```typescript
class UserService {
private validated = new WeakSet<object>();
async processUser(data: unknown): Promise<void> {
if (typeof data !== "object" || data === null) {
throw new Error("Invalid data");
}
if (!this.validated.has(data)) {
UserSchema.parse(data);
this.validated.add(data);
}
const user = data as User;
}
}
```
## Performance Monitoring
```typescript
async function validateWithMetrics<T>(
data: unknown,
schema: z.ZodType<T>,
name: string
): Promise<T> {
const start = performance.now();
try {
const result = schema.parse(data);
const duration = performance.now() - start;
metrics.record(`validation.${name}.success`, duration);
return result;
} catch (error) {
const duration = performance.now() - start;
metrics.record(`validation.${name}.failure`, duration);
throw error;
}
}
```
## Alternative Libraries for Performance
For maximum performance in hot paths:
**TypeBox** (compile-time optimized):
```typescript
import { Type, Static } from "@sinclair/typebox";
import { Value } from "@sinclair/typebox/value";
const UserType = Type.Object({
id: Type.String(),
name: Type.String(),
email: Type.String({ format: "email" })
});
type User = Static<typeof UserType>;
const user = Value.Parse(UserType, data);
```
**Valibot** (smaller bundle size):
```typescript
import * as v from "valibot";
const UserSchema = v.object({
id: v.string(),
name: v.string(),
email: v.pipe(v.string(), v.email())
});
type User = v.InferOutput<typeof UserSchema>;
```

View File

@@ -0,0 +1,241 @@
# Zod Patterns Reference
## Advanced Schema Composition
### Union Types
```typescript
const SuccessResponseSchema = z.object({
status: z.literal("success"),
data: z.unknown()
});
const ErrorResponseSchema = z.object({
status: z.literal("error"),
error: z.string(),
code: z.number()
});
const ApiResponseSchema = z.discriminatedUnion("status", [
SuccessResponseSchema,
ErrorResponseSchema
]);
type ApiResponse = z.infer<typeof ApiResponseSchema>;
```
### Transform and Coerce
```typescript
const DateSchema = z.string().datetime().transform(str => new Date(str));
const UserWithDatesSchema = z.object({
id: z.string(),
name: z.string(),
createdAt: DateSchema,
updatedAt: DateSchema
});
type UserWithDates = z.infer<typeof UserWithDatesSchema>;
const CoerceNumberSchema = z.coerce.number();
const QueryParamsSchema = z.object({
page: CoerceNumberSchema.int().positive().default(1),
limit: CoerceNumberSchema.int().min(1).max(100).default(20),
sort: z.enum(["asc", "desc"]).default("asc")
});
```
### Custom Refinements
```typescript
const PasswordSchema = z
.string()
.min(8)
.refine(
password => /[A-Z]/.test(password),
"Password must contain at least one uppercase letter"
)
.refine(
password => /[a-z]/.test(password),
"Password must contain at least one lowercase letter"
)
.refine(
password => /[0-9]/.test(password),
"Password must contain at least one number"
);
const SignupSchema = z.object({
email: z.string().email(),
password: PasswordSchema,
confirmPassword: z.string()
}).refine(
data => data.password === data.confirmPassword,
{
message: "Passwords don't match",
path: ["confirmPassword"]
}
);
```
### Partial and Optional Schemas
```typescript
const UpdateUserSchema = UserSchema.partial();
type UpdateUser = z.infer<typeof UpdateUserSchema>;
async function updateUser(id: string, updates: unknown): Promise<User> {
const validated = UpdateUserSchema.parse(updates);
const response = await fetch(`/api/users/${id}`, {
method: "PATCH",
body: JSON.stringify(validated)
});
return UserSchema.parse(await response.json());
}
```
### Nested Objects
```typescript
const AddressSchema = z.object({
street: z.string(),
city: z.string(),
state: z.string().length(2),
zipCode: z.string().regex(/^\d{5}(-\d{4})?$/)
});
const UserWithAddressSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
address: AddressSchema,
billingAddress: AddressSchema.optional()
});
type UserWithAddress = z.infer<typeof UserWithAddressSchema>;
```
### Array Validation
```typescript
const TagSchema = z.string().min(1).max(20);
const PostSchema = z.object({
id: z.string(),
title: z.string().min(1).max(200),
content: z.string(),
tags: z.array(TagSchema).min(1).max(10),
metadata: z.record(z.string(), z.unknown())
});
type Post = z.infer<typeof PostSchema>;
async function fetchPosts(): Promise<Post[]> {
const response = await fetch("/api/posts");
const data: unknown = await response.json();
const PostsSchema = z.array(PostSchema);
return PostsSchema.parse(data);
}
```
## Generic Validation Helpers
```typescript
async function apiCall<T>(
endpoint: string,
dataSchema: z.ZodType<T>
): Promise<T> {
const response = await fetch(endpoint);
const rawData: unknown = await response.json();
const apiResponse = ApiResponseSchema.parse(rawData);
if (apiResponse.status === "error") {
throw new Error(`API Error ${apiResponse.code}: ${apiResponse.error}`);
}
return dataSchema.parse(apiResponse.data);
}
```
## Validation Middleware
```typescript
import { Request, Response, NextFunction } from "express";
function validateBody<T>(schema: z.ZodType<T>) {
return (req: Request, res: Response, next: NextFunction) => {
const result = schema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({
error: "Validation failed",
issues: result.error.issues
});
}
req.body = result.data;
next();
};
}
app.post("/users", validateBody(UserSchema), (req, res) => {
const user: User = req.body;
});
```
## Safe JSON Parse
```typescript
function safeJsonParse<T>(
json: string,
schema: z.ZodType<T>
): T {
try {
const data: unknown = JSON.parse(json);
return schema.parse(data);
} catch (error) {
if (error instanceof SyntaxError) {
throw new Error("Invalid JSON");
}
throw error;
}
}
```
## Configuration Validation
```typescript
const ConfigSchema = z.object({
port: z.number().int().positive().default(3000),
database: z.object({
host: z.string(),
port: z.number().int().positive(),
name: z.string()
}),
redis: z.object({
url: z.string().url()
}).optional()
});
type Config = z.infer<typeof ConfigSchema>;
function loadConfig(): Config {
const data: unknown = process.env;
return ConfigSchema.parse({
port: data.PORT,
database: {
host: data.DB_HOST,
port: data.DB_PORT,
name: data.DB_NAME
},
redis: data.REDIS_URL ? { url: data.REDIS_URL } : undefined
});
}
```