asatilloalisherov029-eng/Cefrcenter
GitHub: asatilloalisherov029-eng/Cefrcenter
将 Zod 校验错误包装为用户友好的可读消息的 TypeScript 工具库,同时保留原始错误细节供开发者调试使用。
Stars: 0 | Forks: 0
# zod-validation-error
将 zod 校验错误包装为用户友好的可读消息。
[](https://github.com/causaly/zod-validation-error/actions/workflows/ci.yml) [](https://www.npmjs.com/package/zod-validation-error)
#### 特性
- 用户友好的可读错误消息,提供丰富的配置选项;
- 保留原始错误细节,可通过 `error.details` 访问;
- 提供自定义 error map 以实现更好的用户友好消息;
- 同时支持 Zod v3 和 v4。
**_注意:_** 当前版本的 `zod-validation-error` 适用于 zod v4。如果您正在寻找 zod v3 的支持,请参阅 [v3 文档](./README.v3.md)
## 安装
```
npm install zod-validation-error
```
#### 环境要求
- Node.js v.18+
- TypeScript v.4.5+
## 快速开始
```
import { z as zod } from 'zod';
import { fromError, createErrorMap } from 'zod-validation-error';
// configure zod to use zod-validation-error's error map
// this is optional, you may also use your own custom error map or zod's native error map
// we recommend using zod-validation-error's error map for better user-friendly messages
// see https://zod.dev/error-customization for further details
zod.config({
customError: createErrorMap(),
});
// create zod schema
const zodSchema = zod.object({
id: zod.int().positive(),
email: zod.email(),
});
// parse some invalid value
try {
zodSchema.parse({
id: 1,
email: 'coyote@acme', // note: invalid email
});
} catch (err) {
const validationError = fromError(err);
// the error is now readable by the user
// you may print it to console
console.log(validationError.toString());
// or return it as an actual error
return validationError;
}
```
## 动机
Zod 错误对于最终用户来说难以直接使用。本库将 Zod 校验错误包装为用户友好的可读消息,可以暴露给外部,同时将原始错误保留在数组中以供 _开发_ 使用。
### 示例
#### 输入(来自 Zod)
```
[
{
"origin": "number",
"code": "too_small",
"minimum": 0,
"inclusive": false,
"path": ["id"],
"message": "Number must be greater than 0 at \"id\""
},
{
"origin": "string",
"code": "invalid_format",
"format": "email",
"pattern": "/^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$/",
"path": ["email"],
"message": "Invalid email at \"email\""
}
]
```
#### 输出
```
Validation error: Number must be greater than 0 at "id"; Invalid email at "email"
```
## API
- [ValidationError(message[, options])](#validationerror)
- [createErrorMap(options)](#createErrorMap)
- [createMessageBuilder(options)](#createMessageBuilder)
- [isValidationError(error)](#isvalidationerror)
- [isValidationErrorLike(error)](#isvalidationerrorlike)
- [isZodErrorLike(error)](#iszoderrorlike)
- [fromError(error[, options])](#fromerror)
- [fromZodIssue(zodIssue[, options])](#fromzodissue)
- [fromZodError(zodError[, options])](#fromzoderror)
- [toValidationError([options]) => (error) => ValidationError](#tovalidationerror)
### ValidationError
主要的 `ValidationError` 类,继承了 JavaScript 原生的 `Error`。
#### 参数
- `message` - _string_;错误消息(必填)
- `options` - _ErrorOptions_;根据 [JavaScript 定义](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Error#options) 的错误选项(可选)
- `options.cause` - _any_;可用于保存原始的 zod 错误(可选)
#### 示例 1:使用 `message` 构造新的 ValidationError
```
import { ValidationError } from 'zod-validation-error';
const error = new ValidationError('foobar');
console.log(error instanceof Error); // prints true
```
#### 示例 2:使用 `message` 和 `options.cause` 构造新的 ValidationError
```
import { z as zod } from 'zod';
import { ValidationError } from 'zod-validation-error';
const error = new ValidationError('foobar', {
cause: new zod.ZodError([
{
origin: 'number',
code: 'too_small',
minimum: 0,
inclusive: false,
path: ['id'],
message: 'Number must be greater than 0 at "id"',
input: -1,
},
]),
});
console.log(error.details); // prints issues from zod error
```
### createErrorMap
创建 zod-validation-error 的 `errorMap`,用于将 issue 格式化为用户友好的错误消息。
我们认为 zod 原生的 error map 对用户不够友好,因此我们提供了自己的实现,将 issue 格式化为人类可读的消息。
注意:zod-validation-error 的 `errorMap` 与其他所有 error map 一样,也可以直接在 `zod` 中使用(有关更多详细信息,请参阅 https://zod.dev/error-customization),例如:
#### 参数
- `options` - _Object_;格式化选项(可选)
##### createErrorMap 选项
| 名称 | 类型 | 描述 |
| ------------------------------- | :-------------------------------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `displayInvalidFormatDetails` | `boolean` | 指示是否在错误消息中显示无效格式的详细信息(例如 regexp pattern)(可选,默认为 `false`) |
| `maxAllowedValuesToDisplay` | `number` | 要显示的允许值的最大数量(可选,默认为 `10`)。超出此限制的允许值将被隐藏。 |
| `allowedValuesSeparator` | `string` | 用于在消息中连接允许值的分隔符(可选,默认为 `", "`) |
| `allowedValuesLastSeparator` | `string \| undefined` | 用于在消息中连接最后一个允许值的分隔符(可选,默认为 `" or "`)。设置为 `undefined` 可禁用。 |
| `wrapAllowedValuesInQuote` | `boolean` | 指示是否用引号将允许值包裹起来(可选,默认为 `true`)。请注意,这仅适用于字符串值。 |
| `maxUnrecognizedKeysToDisplay` | `number` | 要在错误消息中显示的无法识别的 key 的最大数量(可选,默认为 `5`) |
| `unrecognizedKeysSeparator` | `string` | 用于在消息中连接无法识别的 key 的分隔符(可选,默认为 `", "`) |
| `unrecognizedKeysLastSeparator` | `string \| undefined` | 用于在消息中连接最后一个无法识别的 key 的分隔符(可选,默认为 `" and "`)。设置为 `undefined` 可禁用。 |
| `wrapUnrecognizedKeysInQuote` | `boolean` | 指示是否用引号将无法识别的 key 包裹起来(可选,默认为 `true`)。请注意,这仅适用于字符串 key。 |
| `dateLocalization` | `boolean \| Intl.LocalesArgument` | 指示是否对日期值进行本地化(可选,默认为 `true`)。如果设置为 `true`,将使用环境的默认区域设置。您也可以传入 `Intl.LocalesArgument` 来指定自定义区域设置。 |
| `numberLocalization` | `boolean \| Intl.LocalesArgument` | 指示是否对数值进行本地化(可选,默认为 `true`)。如果设置为 `true`,将使用环境的默认区域设置。您也可以传入 `Intl.LocalesArgument` 来指定自定义区域设置。 |
#### 示例
```
import { z as zod } from 'zod';
import { createErrorMap } from 'zod-validation-error';
zod.config({
customError: createErrorMap({
// default values are used when not specified
displayInvalidFormatDetails: true,
}),
});
```
### createMessageBuilder
创建 zod-validation-error 的默认 `MessageBuilder`,用于生成用户友好的错误消息。
旨在作为选项传递给 [fromError](#fromerror)、[fromZodIssue](#fromzodissue)、[fromZodError](#fromzoderror) 或 [toValidationError](#tovalidationerror)。
#### 参数
- `options` - _Object_;格式化选项(可选)
##### createMessageBuilder 选项
| 名称 | 类型 | 描述 |
| -------------------- | :-------------------: | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `maxIssuesInMessage` | `number` | 包含在用户友好消息中的最大 issue 数量(可选,默认为 `99`) |
| `issueSeparator` | `string` | 用于在用户友好消息中连接 issue 的分隔符(可选,默认为 `";"`) |
| `unionSeparator` | `string` | 用于在用户友好消息中连接 union-issue 的分隔符(可选,默认为 `" or "`) |
| `prefix` | `string \| undefined` | 在用户友好消息中使用的前缀(可选,默认为 `"Validation error"`)。传入 `undefined` 可完全禁用前缀。 |
| `prefixSeparator` | `string` | 用于将前缀与用户友好消息的其余部分连接起来的分隔符(可选,默认为 `": "`)。当 `prefix` 为 `undefined` 时不使用。 |
| `includePath` | `boolean` | 指示是否在错误消息中包含出错的属性 key(可选,默认为 `true`) |
| `forceTitleCase` | `boolean` | 指示是否将单个 issue 消息转换为 title case(可选,默认为 `true`)。 |
#### 示例
```
import { createMessageBuilder } from 'zod-validation-error';
const messageBuilder = createMessageBuilder({
maxIssuesInMessage: 3,
includePath: false,
});
```
### isValidationError
一个[类型保护](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates)工具函数,基于 `instanceof` 比较。
#### 参数
- `error` - 错误实例(必填)
#### 示例
```
import { z as zod } from 'zod';
import { ValidationError, isValidationError } from 'zod-validation-error';
const err = new ValidationError('foobar');
isValidationError(err); // returns true
const invalidErr = new Error('foobar');
isValidationError(err); // returns false
```
### isValidationErrorLike
一个[类型保护](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates)工具函数,基于 _启发式_ 比较。
_既然我们可以使用简单的 `instanceof` 比较,为什么还需要启发式方法?_ 因为多版本不一致性的问题。例如,某个依赖项可能在内部使用了较旧版本的 `zod-validation-error`。在这种情况下,`instanceof` 比较将产生无效结果,因为模块去重不适用于 npm/yarn 层面,并且原型不同。
长话短说:如果您不确定,最好使用 `isValidationErrorLike` 而不是 `isValidationError`。
#### 参数
- `error` - 错误实例(必填)
#### 示例
```
import { ValidationError, isValidationErrorLike } from 'zod-validation-error';
const err = new ValidationError('foobar');
isValidationErrorLike(err); // returns true
const invalidErr = new Error('foobar');
isValidationErrorLike(err); // returns false
```
### isZodErrorLike
一个[类型保护](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates)工具函数,基于 _启发式_ 比较。
_既然我们可以使用简单的 `instanceof` 比较,为什么还需要启发式方法?_ 因为多版本不一致性的问题。例如,某个依赖项可能在内部使用了较旧版本的 `zod`。在这种情况下,`instanceof` 比较将产生无效结果,因为模块去重不适用于 npm/yarn 层面,并且原型不同。
#### 参数
- `error` - 错误实例(必填)
#### 示例
```
import { z as zod } from 'zod';
import { ValidationError, isZodErrorLike } from 'zod-validation-error';
const zodValidationErr = new ValidationError('foobar');
isZodErrorLike(zodValidationErr); // returns false
const genericErr = new Error('foobar');
isZodErrorLike(genericErr); // returns false
const zodErr = new zod.ZodError([
{
origin: 'number',
code: 'too_small',
minimum: 0,
inclusive: false,
path: ['id'],
message: 'Number must be greater than 0 at "id"',
input: -1,
},
]);
isZodErrorLike(zodErr); // returns true
```
### fromError
将错误转换为 `ValidationError`。
_`fromError` 和 `fromZodError` 有什么区别?_ `fromError` 函数是 `fromZodError` 的一个较宽松版本。它可以接受未知错误并尝试将其转换为 `ValidationError`。
#### 参数
- `error` - _unknown_;一个错误(必填)
- `options` - _Object_;格式化选项(可选)
- `messageBuilder` - _MessageBuilder_;一个接受 `zod.ZodIssue` 对象数组并返回 `string` 形式的用户友好错误消息的函数(可选)。
#### 注意
或者,您可以直接将 [createMessageBuilder 选项](#createmessagebuilder-options) 作为 `options` 传递。这些将用作在内部创建 `MessageBuilder` 实例的参数。
### fromZodIssue
将单个 zod issue 转换为 `ValidationError`。
#### 参数
- `zodIssue` - _zod.ZodIssue_;一个 ZodIssue 实例(必填)
- `options` - _Object_;格式化选项(可选)
- `messageBuilder` - _MessageBuilder_;一个接受 `zod.ZodIssue` 对象数组并返回 `string` 形式的用户友好错误消息的函数(可选)。
#### 注意
或者,您可以直接将 [createMessageBuilder 选项](#createmessagebuilder-options) 作为 `options` 传递。这些将用作在内部创建 `MessageBuilder` 实例的参数。
### fromZodError
将 zod 错误转换为 `ValidationError`。
_`ZodError` 和 `ZodIssue` 有什么区别?_ `ZodError` 是 1 个或多个 `ZodIssue` 实例的集合。这是您在调用 `zodSchema.parse()` 时得到的结果。
#### 参数
- `zodError` - _zod.ZodError_;一个 ZodError 实例(必填)
- `options` - _Object_;格式化选项(可选)
- `messageBuilder` - _MessageBuilder_;一个接受 `zod.ZodIssue` 对象数组并返回 `string` 形式的用户友好错误消息的函数(可选)。
#### 注意
或者,您可以直接将 [createMessageBuilder 选项](#createmessagebuilder-optionscreateMessageBuilder) 作为 `options` 传递。这些将用作在内部创建 `MessageBuilder` 实例的参数。
### toValidationError
`fromZodError` 的柯里化版本,专为 FP(函数式编程)设计。请注意,如果需要,它首先接受 options 对象,并返回一个将 `zodError` 转换为 `ValidationError` 对象的函数。
```
toValidationError(options) => (zodError) => ValidationError
```
#### 使用 fp-ts 的示例
```
import * as Either from 'fp-ts/Either';
import { z as zod } from 'zod';
import { toValidationError, ValidationError } from 'zod-validation-error';
// create zod schema
const zodSchema = zod
.object({
id: zod.int().positive(),
email: zod.email(),
})
.brand<'User'>();
export type User = zod.infer;
export function parse(
value: zod.input
): Either.Either {
return Either.tryCatch(() => schema.parse(value), toValidationError());
}
```
## 常见问题
### zod-validation-error 和 zod 自带的 [prettifyError](https://zod.dev/error-formatting#zprettifyerror) 有什么区别?
虽然两个库都旨在提供 zod 错误的人类可读字符串表示形式,但它们在几个方面有所不同...
1. **关注最终用户**:zod-validation-error 提供了固执己见的、用户友好的错误消息,旨在直接在表单或 API 响应中显示给最终用户。
2. **自定义选项**:zod-validation-error 提供了丰富的消息格式化配置,例如控制路径包含、允许值显示、本地化等。
3. **错误处理**:zod-validation-error 在通过 ValidationError 类提供干净、一致的接口的同时,保留了原始错误细节。
4. **集成灵活性**:除了格式化之外,zod-validation-error 还提供了适用于各种架构模式(例如函数式编程)的错误检测和转换工具函数。
免责声明:根据此[评论](https://github.com/causaly/zod-validation-error/issues/455#issuecomment-2811895152),我们无意与 zod 对抗。事实上,如果符合社区的最大利益,我们很乐意废弃此模块。就目前而言,似乎 `od-validation-error` 和 `prettifyError` 都有各自的生存空间,这也是基于 Colin McDonnell 的[回复](https://github.com/causaly/zod-validation-error/issues/455#issuecomment-2814466019)。
### 我需要使用 `zod-validation-error` 的 error map 吗?
不需要,如果您愿意,可以使用 zod 原生的 error map。但是,我们建议使用 `zod-validation-error` 的 error map 以获得更好的用户友好消息。
如果您有特定需求(例如国际化),也可以使用您自己的自定义 error map。
### 我在哪里可以看到 `zod-validation-error` 的 error map 格式化是如何工作的?
了解 `zod-validation-error` 的 error map 工作原理的最简单方法是查看[测试](./lib/v4/errorMap/errorMap.test.ts)。它们涵盖了各种场景,并展示了 error map 如何将 issue 格式化为用户友好的消息。
### 如何区分不同的错误
使用 `isValidationErrorLike` 类型保护。
#### 示例
场景:区分 `ValidationError` 和普通的 `Error`,以便分别响应 400 和 500 HTTP 状态码。
```
import { isValidationErrorLike } from 'zod-validation-error';
try {
func(); // throws Error - or - ValidationError
} catch (err) {
if (isValidationErrorLike(err)) {
return 400; // Bad Data (this is a client error)
}
return 500; // Server Error
}
```
### 如何在 `zod` 之外使用 `ValidationError`
可以在 `zod` 之外实现自定义校验逻辑并抛出 `ValidationError`。
#### 示例 1:传递自定义消息
```
import { ValidationError } from 'zod-validation-error';
import { Buffer } from 'node:buffer';
function parseBuffer(buf: unknown): Buffer {
if (!Buffer.isBuffer(buf)) {
throw new ValidationError('Invalid argument; expected buffer');
}
return buf;
}
```
#### 示例 2:传递自定义消息和原始错误作为 cause
```
import { ValidationError } from 'zod-validation-error';
try {
// do something that throws an error
} catch (err) {
throw new ValidationError('Something went deeply wrong', { cause: err });
}
```
### 如何将 `ValidationError` 与自定义 "error map" 结合使用
Zod 支持通过提供自定义 "error map" 来自定义错误消息。您可以将其与 `zod-validation-error` 结合使用以生成用户友好的消息。
#### 示例:使用 `customError` 属性生成用户友好的错误消息
如果您只需要生成用户友好的错误消息,可以使用 `customError` 属性。
```
import { z as zod } from 'zod';
import { createErrorMap } from 'zod-validation-error';
zod.config({
customError: createErrorMap({
includePath: true,
}),
});
```
当设置了该属性时,`zod-validation-error` 将遵循 `customError` 属性,无需进一步配置。
### `zod-validation-error` 支持 CommonJS 吗
是的,`zod-validation-error` 开箱即用地支持 CommonJS。您只需要使用 `require` 导入它即可。
#### 示例
```
const { ValidationError } = require('zod-validation-error');
```
## 贡献
非常欢迎贡献源代码。请提交 PR,确保 linter 通过并且所有测试都已通过。
#### 我们正在招聘
Causaly 正在使用 TypeScript、React 和 Node.js 等技术构建世界上最大的生物医学知识平台。在 https://jobs.ashbyhq.com/causaly 了解更多关于我们的招聘职位。
## 许可证
MIT
标签:MITM代理, Syscall, TypeScript, Web开发, Zod, 安全插件, 数据可视化, 数据验证, 暗色界面, 错误处理