# 错误处理

URL: https://caijiao.org/zod/errors
Source: docs/zod/errors.md
Description: 错误处理和自定义错误消息的完整指南，包括错误捕获、格式化和本地化

Zod 提供了全面的错误处理功能，帮助你处理验证过程中的错误。

## 基本错误处理

```ts
import { z } from "zod";

const schema = z.string();

try {
  schema.parse(42); // 抛出 ZodError
} catch (error) {
  if (error instanceof z.ZodError) {
    console.log(error.issues);
    // [
    //   {
    //     code: "invalid_type",
    //     expected: "string",
    //     received: "number",
    //     path: [],
    //     message: "Expected string, received number"
    //   }
    // ]
  }
}
```

## 自定义错误消息

```ts
// 在创建模式时设置错误消息
const User = z.object({
  username: z.string({
    required_error: "用户名是必需的",
    invalid_type_error: "用户名必须是字符串",
  }),
  age: z.number({
    required_error: "年龄是必需的",
    invalid_type_error: "年龄必须是数字",
  }),
});

// 使用 .refine 添加自定义验证和错误消息
const ageSchema = z.number()
  .refine(n => n >= 0, {
    message: "年龄不能为负数",
  })
  .refine(n => n <= 120, {
    message: "年龄不能超过120岁",
  });
```

## 错误格式化

```ts
const schema = z.object({
  name: z.string(),
  age: z.number(),
  email: z.string().email(),
});

try {
  schema.parse({
    name: 42,
    age: "not a number",
    email: "invalid-email",
  });
} catch (error) {
  if (error instanceof z.ZodError) {
    // 扁平化错误消息
    console.log(error.flatten());
    // {
    //   formErrors: [],
    //   fieldErrors: {
    //     name: ["Expected string, received number"],
    //     age: ["Expected number, received string"],
    //     email: ["Invalid email"]
    //   }
    // }
    
    // 获取所有错误消息
    console.log(error.errors);
    // [
    //   { path: ["name"], message: "Expected string, received number" },
    //   { path: ["age"], message: "Expected number, received string" },
    //   { path: ["email"], message: "Invalid email" }
    // ]
  }
}
```

## 错误映射

```ts
// 全局错误映射
const customErrorMap: z.ZodErrorMap = (issue, ctx) => {
  if (issue.code === z.ZodIssueCode.invalid_type) {
    if (issue.expected === "string") {
      return { message: "这个字段必须是字符串" };
    }
  }
  if (issue.code === z.ZodIssueCode.too_small) {
    return { message: "值太小了" };
  }
  return { message: ctx.defaultError };
};

z.setErrorMap(customErrorMap);

// 特定模式的错误映射
const schema = z.string().transform((val) => val.toUpperCase(), {
  errorMap: (issue, ctx) => ({
    message: "转换失败：" + ctx.defaultError,
  }),
});
```

## 异步验证错误

```ts
const asyncSchema = z.string().refine(async (val) => {
  const response = await fetch(`/api/check-username/${val}`);
  return response.ok;
}, {
  message: "用户名已被使用",
});

try {
  await asyncSchema.parseAsync("username");
} catch (error) {
  if (error instanceof z.ZodError) {
    console.log(error.issues);
  }
}
```

## 错误处理最佳实践

```ts
// 1. 使用类型守卫
function isZodError(error: unknown): error is z.ZodError {
  return error instanceof z.ZodError;
}

// 2. 创建错误处理工具函数
function handleZodError(error: unknown) {
  if (isZodError(error)) {
    return {
      success: false,
      errors: error.flatten().fieldErrors,
    };
  }
  return {
    success: false,
    errors: { _errors: ["发生未知错误"] },
  };
}

// 3. 在 API 路由中使用
async function handleRequest(data: unknown) {
  try {
    const validData = await schema.parseAsync(data);
    // 处理验证后的数据
    return { success: true, data: validData };
  } catch (error) {
    return handleZodError(error);
  }
}

// 4. 组合多个验证
const combinedSchema = z.object({
  user: User,
  preferences: Preferences,
}).refine(
  (data) => {
    // 复杂的跨字段验证
    return true;
  },
  {
    message: "验证失败",
    path: ["user", "preferences"], // 指定错误路径
  }
);
```
