mcpField
S
safe-action-validation-errors

safe-action-validation-errors

github.com/next-safe-action开发11.2k 次安装8 周前更新
代码仓库

关于 safe-action-validation-errors

面向 next-safe-action 验证错误处理的参考技能,介绍自动与手动错误的结构,以及在客户端展示格式化或扁平化错误的方式。

主要功能

  1. 01自动生成 Schema 验证错误
  2. 02通过 returnValidationErrors() 添加手动错误
  3. 03格式化与扁平化错误结构
  4. 04展示表单级和字段级错误

适用场景

  1. 01在表单中展示字段错误
  2. 02报告整个表单的验证失败
  3. 03处理服务端验证规则

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/next-safe-action/skills --skill safe-action-validation-errors

安装后在 Claude Code 中输入/safe-action-validation-errors确认已加载。

附带文件custom-errors.mderror-shapes.md

详细介绍

next-safe-action 验证错误

验证错误的两个来源

  1. Schema 验证 — 输入不符合 .inputSchema() 时自动执行
  2. 手动验证 — 通过服务器代码中的 returnValidationErrors() 执行(例如“邮箱已被占用”)

两者在客户端生成相同的错误结构。

默认错误形状(格式化后)

与 Schema 结构一致,每一层都包含 _errors 数组:

// For schema: z.object({ email: z.string().email(), address: z.object({ city: z.string() }) })
{
  _errors: ["Form-level error"],                    // root errors
  email: { _errors: ["Invalid email address"] },    // field errors
  address: {
    _errors: ["Address section error"],
    city: { _errors: ["City is required"] },        // nested field errors
  },
}

returnValidationErrors

会抛出 ActionServerValidationError,由框架捕获并作为 result.validationErrors 返回。它不会返回 — 而是始终抛出。

对于预期的非验证错误(“缺货”“未找到”),同级辅助函数 returnServerError(payload) 的工作方式相同(在内部抛出,绝不返回),但会将 result.serverError 设置为带类型的负载,并绕过 handleServerError。请参阅 safe-action-client 技能。

"use server";

import { z } from "zod";
import { returnValidationErrors } from "next-safe-action";
import { actionClient } from "@/lib/safe-action";

const registerSchema = z.object({
  email: z.string().email(),
  username: z.string().min(3),
});

export const register = actionClient
  .inputSchema(registerSchema)
  .action(async ({ parsedInput }) => {
    // Check business rules after schema validation passes
    const existingUser = await db.user.findByEmail(parsedInput.email);
    if (existingUser) {
      returnValidationErrors(registerSchema, {
        email: { _errors: ["This email is already registered"] },
      });
    }

    const existingUsername = await db.user.findByUsername(parsedInput.username);
    if (existingUsername) {
      returnValidationErrors(registerSchema, {
        username: { _errors: ["This username is taken"] },
      });
    }

    // Both checks passed — create the user
    const user = await db.user.create(parsedInput);
    return { id: user.id };
  });
根级错误

如需显示针对整个表单的错误,请在顶层使用 _errors:

returnValidationErrors(schema, {
  _errors: ["You can only create 5 posts per day"],
});

参考文档

显示验证错误

// Formatted shape (default)
{result.validationErrors?.email?._errors?.map((error) => (
  <p key={error} className="text-red-500">{error}</p>
))}

// Root-level errors
{result.validationErrors?._errors?.map((error) => (
  <p key={error} className="text-red-500">{error}</p>
))}
// Flattened shape
{result.validationErrors?.fieldErrors?.email?.map((error) => (
  <p key={error} className="text-red-500">{error}</p>
))}

// Form-level errors (flattened)
{result.validationErrors?.formErrors?.map((error) => (
  <p key={error} className="text-red-500">{error}</p>
))}

评论

支持 Markdown