zod-error-map 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -0
- package/package.json +51 -0
- package/src/error-codes.js +32 -0
- package/src/error-mapper.js +87 -0
- package/src/index.d.ts +77 -0
- package/src/index.js +22 -0
- package/src/message-builders.js +108 -0
- package/src/types.js +37 -0
- package/src/zod-integration.js +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# zod-error-map
|
|
2
|
+
|
|
3
|
+
Type-safe, customizable error message mapping for Zod validation.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install zod-error-map
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Basic Usage (Zod v4)
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
import { z } from 'zod'
|
|
17
|
+
import { setZodErrorMap } from 'zod-error-map'
|
|
18
|
+
|
|
19
|
+
setZodErrorMap(z)
|
|
20
|
+
|
|
21
|
+
const schema = z.object({
|
|
22
|
+
email: z.string().email(),
|
|
23
|
+
password: z.string().min(8),
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
schema.parse({ email: 'invalid', password: '123' })
|
|
27
|
+
// Error: The 'email' must be a valid email address
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Custom Configuration
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
import { z } from 'zod'
|
|
34
|
+
import { setZodErrorMap, ErrorCode } from 'zod-error-map'
|
|
35
|
+
|
|
36
|
+
setZodErrorMap(z, {
|
|
37
|
+
defaultError: 'Validation failed',
|
|
38
|
+
formatMessages: {
|
|
39
|
+
email: (label) => `Please enter a valid email for ${label.quoted}`,
|
|
40
|
+
},
|
|
41
|
+
builders: {
|
|
42
|
+
[ErrorCode.TOO_SMALL]: (issue, label) =>
|
|
43
|
+
`${label.bare} needs at least ${issue.minimum} characters`,
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Using the Error Mapper Directly
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
import { createErrorMapper } from 'zod-error-map'
|
|
52
|
+
|
|
53
|
+
const mapper = createErrorMapper({
|
|
54
|
+
defaultError: 'Invalid input',
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const message = mapper.format({
|
|
58
|
+
code: 'invalid_type',
|
|
59
|
+
path: ['email'],
|
|
60
|
+
input: undefined,
|
|
61
|
+
expected: 'string',
|
|
62
|
+
})
|
|
63
|
+
// "The email is required"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Zod v3 Compatibility
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
import { z } from 'zod'
|
|
70
|
+
import { createZodErrorMap } from 'zod-error-map'
|
|
71
|
+
|
|
72
|
+
z.setErrorMap(createZodErrorMap())
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## API
|
|
76
|
+
|
|
77
|
+
### `setZodErrorMap(z, config?)`
|
|
78
|
+
|
|
79
|
+
Sets the global Zod error map using `z.config()` (Zod v4).
|
|
80
|
+
|
|
81
|
+
### `createZodErrorMap(config?)`
|
|
82
|
+
|
|
83
|
+
Creates a Zod error map compatible with `z.setErrorMap()` (Zod v3).
|
|
84
|
+
|
|
85
|
+
### `createErrorMapper(config?)`
|
|
86
|
+
|
|
87
|
+
Creates an error mapper instance with custom configuration.
|
|
88
|
+
|
|
89
|
+
### Configuration Options
|
|
90
|
+
|
|
91
|
+
| Option | Type | Description |
|
|
92
|
+
|--------|------|-------------|
|
|
93
|
+
| `defaultError` | `string` | Default error message when no builder matches |
|
|
94
|
+
| `builders` | `Record<string, MessageBuilder>` | Custom message builders by error code |
|
|
95
|
+
| `formatMessages` | `Record<string, (label) => string>` | Custom messages for format validation errors |
|
|
96
|
+
|
|
97
|
+
### Error Codes
|
|
98
|
+
|
|
99
|
+
- `ErrorCode.INVALID_TYPE` - Type mismatch or missing required field
|
|
100
|
+
- `ErrorCode.TOO_SMALL` - Value below minimum length/value
|
|
101
|
+
- `ErrorCode.TOO_BIG` - Value above maximum length/value
|
|
102
|
+
- `ErrorCode.INVALID_FORMAT` - Invalid format (email, uuid, url, etc.)
|
|
103
|
+
- `ErrorCode.CUSTOM` - Custom validation errors
|
|
104
|
+
|
|
105
|
+
### Format Types
|
|
106
|
+
|
|
107
|
+
`FormatType.EMAIL`, `FormatType.UUID`, `FormatType.URL`, `FormatType.REGEX`, `FormatType.CUID`, `FormatType.CUID2`, `FormatType.ULID`, `FormatType.IP`, `FormatType.DATE`, `FormatType.DATETIME`, `FormatType.TIME`
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "zod-error-map",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Type-safe, customizable error message mapping for Zod validation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"types": "src/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./src/index.js",
|
|
11
|
+
"types": "./src/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"src/**/*.js",
|
|
16
|
+
"src/**/*.d.ts",
|
|
17
|
+
"!src/**/*.test.js"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "node --test 'tests/**/*.test.js'",
|
|
21
|
+
"typecheck": "tsc"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"zod",
|
|
25
|
+
"validation",
|
|
26
|
+
"error",
|
|
27
|
+
"error-map",
|
|
28
|
+
"error-messages",
|
|
29
|
+
"typescript"
|
|
30
|
+
],
|
|
31
|
+
"author": "Manoel Lopes",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/manoel-lopes/zod-error-map.git"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/manoel-lopes/zod-error-map/issues"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/manoel-lopes/zod-error-map#readme",
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"zod": "^3.0.0 || ^4.0.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"typescript": "^5.0.0",
|
|
46
|
+
"zod": "^4.1.0"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=18"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod error codes
|
|
3
|
+
* @readonly
|
|
4
|
+
* @enum {string}
|
|
5
|
+
*/
|
|
6
|
+
export const ErrorCode = /** @type {const} */ ({
|
|
7
|
+
INVALID_TYPE: 'invalid_type',
|
|
8
|
+
TOO_SMALL: 'too_small',
|
|
9
|
+
TOO_BIG: 'too_big',
|
|
10
|
+
INVALID_FORMAT: 'invalid_format',
|
|
11
|
+
CUSTOM: 'custom',
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Format types for invalid_format errors
|
|
16
|
+
* @readonly
|
|
17
|
+
* @enum {string}
|
|
18
|
+
*/
|
|
19
|
+
export const FormatType = /** @type {const} */ ({
|
|
20
|
+
EMAIL: 'email',
|
|
21
|
+
UUID: 'uuid',
|
|
22
|
+
URL: 'url',
|
|
23
|
+
REGEX: 'regex',
|
|
24
|
+
CUID: 'cuid',
|
|
25
|
+
CUID2: 'cuid2',
|
|
26
|
+
ULID: 'ulid',
|
|
27
|
+
IP: 'ip',
|
|
28
|
+
EMOJI: 'emoji',
|
|
29
|
+
DATE: 'date',
|
|
30
|
+
DATETIME: 'datetime',
|
|
31
|
+
TIME: 'time',
|
|
32
|
+
})
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { createDefaultBuilders } from './message-builders.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('./types.js').RawIssue} RawIssue
|
|
5
|
+
* @typedef {import('./types.js').Label} Label
|
|
6
|
+
* @typedef {import('./types.js').MessageBuilder} MessageBuilder
|
|
7
|
+
* @typedef {import('./types.js').ErrorMapConfig} ErrorMapConfig
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const DEFAULT_ERROR = 'Invalid input'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Checks if the issue is a valid RawIssue
|
|
14
|
+
* @param {unknown} issue
|
|
15
|
+
* @returns {issue is RawIssue}
|
|
16
|
+
*/
|
|
17
|
+
function isRawIssue(issue) {
|
|
18
|
+
return (
|
|
19
|
+
typeof issue === 'object' &&
|
|
20
|
+
issue !== null &&
|
|
21
|
+
'code' in issue
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Gets the label from the issue path
|
|
27
|
+
* @param {RawIssue} issue
|
|
28
|
+
* @returns {Label}
|
|
29
|
+
*/
|
|
30
|
+
function getLabel(issue) {
|
|
31
|
+
const path = issue.path ?? []
|
|
32
|
+
const field = path[path.length - 1]
|
|
33
|
+
if (typeof field === 'string') {
|
|
34
|
+
return { bare: field, quoted: `'${field}'` }
|
|
35
|
+
}
|
|
36
|
+
return { bare: 'value', quoted: 'value' }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Creates a Zod error mapper with customizable message builders
|
|
41
|
+
* @param {ErrorMapConfig} [config]
|
|
42
|
+
* @returns {{ format: (issue: unknown) => string, createErrorMap: () => (issue: unknown) => string }}
|
|
43
|
+
*/
|
|
44
|
+
export function createErrorMapper(config = {}) {
|
|
45
|
+
const defaultError = config.defaultError ?? DEFAULT_ERROR
|
|
46
|
+
const defaultBuilders = createDefaultBuilders(defaultError, config.formatMessages)
|
|
47
|
+
const builders = { ...defaultBuilders, ...config.builders }
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Builds the error message for a Zod issue
|
|
51
|
+
* @param {RawIssue} issue
|
|
52
|
+
* @returns {string}
|
|
53
|
+
*/
|
|
54
|
+
function buildMessage(issue) {
|
|
55
|
+
const label = getLabel(issue)
|
|
56
|
+
const builder = builders[issue.code]
|
|
57
|
+
if (builder) {
|
|
58
|
+
return builder(issue, label)
|
|
59
|
+
}
|
|
60
|
+
return issue.message || defaultError
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Formats a Zod issue into a human-readable message
|
|
65
|
+
* @param {unknown} issue
|
|
66
|
+
* @returns {string}
|
|
67
|
+
*/
|
|
68
|
+
function format(issue) {
|
|
69
|
+
if (!isRawIssue(issue)) return defaultError
|
|
70
|
+
return buildMessage(issue)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Creates an error map function for Zod
|
|
75
|
+
* @returns {(issue: unknown) => string}
|
|
76
|
+
*/
|
|
77
|
+
function createErrorMap() {
|
|
78
|
+
return format
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return { format, createErrorMap }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Default error mapper instance
|
|
86
|
+
*/
|
|
87
|
+
export const defaultErrorMapper = createErrorMapper()
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
export interface Label {
|
|
4
|
+
bare: string
|
|
5
|
+
quoted: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface RawIssue {
|
|
9
|
+
code: string
|
|
10
|
+
path: Array<string | number>
|
|
11
|
+
input?: unknown
|
|
12
|
+
expected?: string
|
|
13
|
+
minimum?: number
|
|
14
|
+
maximum?: number
|
|
15
|
+
format?: string
|
|
16
|
+
message?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type MessageBuilder = (issue: RawIssue, label: Label) => string
|
|
20
|
+
|
|
21
|
+
export type FormatMessageBuilder = (label: Label) => string
|
|
22
|
+
|
|
23
|
+
export interface ErrorMapConfig {
|
|
24
|
+
defaultError?: string
|
|
25
|
+
builders?: Record<string, MessageBuilder>
|
|
26
|
+
formatMessages?: Record<string, FormatMessageBuilder>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ErrorMapper {
|
|
30
|
+
format: (issue: unknown) => string
|
|
31
|
+
createErrorMap: () => (issue: unknown) => string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export declare const ErrorCode: {
|
|
35
|
+
readonly INVALID_TYPE: 'invalid_type'
|
|
36
|
+
readonly TOO_SMALL: 'too_small'
|
|
37
|
+
readonly TOO_BIG: 'too_big'
|
|
38
|
+
readonly INVALID_FORMAT: 'invalid_format'
|
|
39
|
+
readonly CUSTOM: 'custom'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export declare const FormatType: {
|
|
43
|
+
readonly EMAIL: 'email'
|
|
44
|
+
readonly UUID: 'uuid'
|
|
45
|
+
readonly URL: 'url'
|
|
46
|
+
readonly REGEX: 'regex'
|
|
47
|
+
readonly CUID: 'cuid'
|
|
48
|
+
readonly CUID2: 'cuid2'
|
|
49
|
+
readonly ULID: 'ulid'
|
|
50
|
+
readonly IP: 'ip'
|
|
51
|
+
readonly EMOJI: 'emoji'
|
|
52
|
+
readonly DATE: 'date'
|
|
53
|
+
readonly DATETIME: 'datetime'
|
|
54
|
+
readonly TIME: 'time'
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export declare function createErrorMapper(config?: ErrorMapConfig): ErrorMapper
|
|
58
|
+
export declare const defaultErrorMapper: ErrorMapper
|
|
59
|
+
|
|
60
|
+
export declare function getInputDescription(input: unknown): string
|
|
61
|
+
export declare const defaultFormatMessages: Record<string, (label: Label) => string>
|
|
62
|
+
export declare function buildInvalidTypeMessage(issue: RawIssue, label: Label): string
|
|
63
|
+
export declare function buildTooSmallMessage(issue: RawIssue, label: Label): string
|
|
64
|
+
export declare function buildTooBigMessage(issue: RawIssue, label: Label): string
|
|
65
|
+
export declare function createFormatMessageBuilder(
|
|
66
|
+
formatMessages?: Record<string, (label: Label) => string>
|
|
67
|
+
): MessageBuilder
|
|
68
|
+
export declare function createCustomMessageBuilder(defaultError: string): MessageBuilder
|
|
69
|
+
export declare function createDefaultBuilders(
|
|
70
|
+
defaultError: string,
|
|
71
|
+
formatMessages?: Record<string, (label: Label) => string>
|
|
72
|
+
): Record<string, MessageBuilder>
|
|
73
|
+
|
|
74
|
+
export declare function setZodErrorMap(z: typeof import('zod').z, config?: ErrorMapConfig): void
|
|
75
|
+
export declare function createZodErrorMap(
|
|
76
|
+
config?: ErrorMapConfig
|
|
77
|
+
): (issue: unknown, ctx: unknown) => { message: string }
|
package/src/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export { ErrorCode, FormatType } from './error-codes.js'
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
createErrorMapper,
|
|
5
|
+
defaultErrorMapper,
|
|
6
|
+
} from './error-mapper.js'
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
getInputDescription,
|
|
10
|
+
defaultFormatMessages,
|
|
11
|
+
buildInvalidTypeMessage,
|
|
12
|
+
buildTooSmallMessage,
|
|
13
|
+
buildTooBigMessage,
|
|
14
|
+
createFormatMessageBuilder,
|
|
15
|
+
createCustomMessageBuilder,
|
|
16
|
+
createDefaultBuilders,
|
|
17
|
+
} from './message-builders.js'
|
|
18
|
+
|
|
19
|
+
export {
|
|
20
|
+
setZodErrorMap,
|
|
21
|
+
createZodErrorMap,
|
|
22
|
+
} from './zod-integration.js'
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { ErrorCode, FormatType } from './error-codes.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('./types.js').RawIssue} RawIssue
|
|
5
|
+
* @typedef {import('./types.js').Label} Label
|
|
6
|
+
* @typedef {import('./types.js').MessageBuilder} MessageBuilder
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Gets a human-readable description of the input type
|
|
11
|
+
* @param {unknown} input
|
|
12
|
+
* @returns {string}
|
|
13
|
+
*/
|
|
14
|
+
export function getInputDescription(input) {
|
|
15
|
+
if (input === undefined) return 'undefined'
|
|
16
|
+
if (input === null) return 'null'
|
|
17
|
+
if (Array.isArray(input)) return 'array'
|
|
18
|
+
return typeof input
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Default format error messages
|
|
23
|
+
* @type {Record<string, (label: Label) => string>}
|
|
24
|
+
*/
|
|
25
|
+
export const defaultFormatMessages = {
|
|
26
|
+
[FormatType.EMAIL]: (label) => `The ${label.quoted} must be a valid email address`,
|
|
27
|
+
[FormatType.UUID]: (label) => `The ${label.quoted} must be a valid UUID`,
|
|
28
|
+
[FormatType.URL]: (label) => `The ${label.quoted} must be a valid URL`,
|
|
29
|
+
[FormatType.REGEX]: (label) => `The ${label.quoted} has an invalid format`,
|
|
30
|
+
[FormatType.CUID]: (label) => `The ${label.quoted} must be a valid CUID`,
|
|
31
|
+
[FormatType.CUID2]: (label) => `The ${label.quoted} must be a valid CUID2`,
|
|
32
|
+
[FormatType.ULID]: (label) => `The ${label.quoted} must be a valid ULID`,
|
|
33
|
+
[FormatType.IP]: (label) => `The ${label.quoted} must be a valid IP address`,
|
|
34
|
+
[FormatType.DATE]: (label) => `The ${label.quoted} must be a valid date`,
|
|
35
|
+
[FormatType.DATETIME]: (label) => `The ${label.quoted} must be a valid datetime`,
|
|
36
|
+
[FormatType.TIME]: (label) => `The ${label.quoted} must be a valid time`,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Builds error message for invalid_type errors
|
|
41
|
+
* @type {MessageBuilder}
|
|
42
|
+
*/
|
|
43
|
+
export function buildInvalidTypeMessage(issue, label) {
|
|
44
|
+
const received = getInputDescription(issue.input)
|
|
45
|
+
if (received === 'undefined') {
|
|
46
|
+
return `The ${label.bare} is required`
|
|
47
|
+
}
|
|
48
|
+
return `Expected ${issue.expected} for ${label.quoted}, received ${received}`
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Builds error message for too_small errors
|
|
53
|
+
* @type {MessageBuilder}
|
|
54
|
+
*/
|
|
55
|
+
export function buildTooSmallMessage(issue, label) {
|
|
56
|
+
const min = Number(issue.minimum) || 0
|
|
57
|
+
return `The ${label.quoted} must contain at least ${min} characters`
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Builds error message for too_big errors
|
|
62
|
+
* @type {MessageBuilder}
|
|
63
|
+
*/
|
|
64
|
+
export function buildTooBigMessage(issue, label) {
|
|
65
|
+
const max = Number(issue.maximum) || 0
|
|
66
|
+
return `The ${label.quoted} must contain at most ${max} characters`
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Creates a format message builder with custom format messages
|
|
71
|
+
* @param {Record<string, (label: Label) => string>} [formatMessages]
|
|
72
|
+
* @returns {MessageBuilder}
|
|
73
|
+
*/
|
|
74
|
+
export function createFormatMessageBuilder(formatMessages = defaultFormatMessages) {
|
|
75
|
+
return (issue, label) => {
|
|
76
|
+
const format = issue.format || ''
|
|
77
|
+
const messageBuilder = formatMessages[format]
|
|
78
|
+
if (messageBuilder) {
|
|
79
|
+
return messageBuilder(label)
|
|
80
|
+
}
|
|
81
|
+
return `The ${label.quoted} has an invalid format`
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Builds error message for custom errors
|
|
87
|
+
* @param {string} defaultError
|
|
88
|
+
* @returns {MessageBuilder}
|
|
89
|
+
*/
|
|
90
|
+
export function createCustomMessageBuilder(defaultError) {
|
|
91
|
+
return (issue, _label) => issue.message || defaultError
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Creates the default message builders map
|
|
96
|
+
* @param {string} defaultError
|
|
97
|
+
* @param {Record<string, (label: Label) => string>} [formatMessages]
|
|
98
|
+
* @returns {Record<string, MessageBuilder>}
|
|
99
|
+
*/
|
|
100
|
+
export function createDefaultBuilders(defaultError, formatMessages) {
|
|
101
|
+
return {
|
|
102
|
+
[ErrorCode.INVALID_TYPE]: buildInvalidTypeMessage,
|
|
103
|
+
[ErrorCode.TOO_SMALL]: buildTooSmallMessage,
|
|
104
|
+
[ErrorCode.TOO_BIG]: buildTooBigMessage,
|
|
105
|
+
[ErrorCode.INVALID_FORMAT]: createFormatMessageBuilder(formatMessages),
|
|
106
|
+
[ErrorCode.CUSTOM]: createCustomMessageBuilder(defaultError),
|
|
107
|
+
}
|
|
108
|
+
}
|
package/src/types.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {object} Label
|
|
3
|
+
* @property {string} bare - The field name without quotes
|
|
4
|
+
* @property {string} quoted - The field name with quotes
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {object} RawIssue
|
|
9
|
+
* @property {string} code - The error code from Zod
|
|
10
|
+
* @property {Array<string | number>} path - The path to the field
|
|
11
|
+
* @property {unknown} [input] - The input value that caused the error
|
|
12
|
+
* @property {string} [expected] - The expected type
|
|
13
|
+
* @property {number} [minimum] - Minimum value/length constraint
|
|
14
|
+
* @property {number} [maximum] - Maximum value/length constraint
|
|
15
|
+
* @property {string} [format] - Format type (email, uuid, url, etc.)
|
|
16
|
+
* @property {string} [message] - Custom error message
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @callback MessageBuilder
|
|
21
|
+
* @param {RawIssue} issue - The raw Zod issue
|
|
22
|
+
* @param {Label} label - The field label
|
|
23
|
+
* @returns {string} The formatted error message
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {(label: Label) => string} FormatMessageBuilder
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {object} ErrorMapConfig
|
|
32
|
+
* @property {string} [defaultError] - Default error message
|
|
33
|
+
* @property {Record<string, MessageBuilder>} [builders] - Custom message builders by error code
|
|
34
|
+
* @property {Record<string, FormatMessageBuilder>} [formatMessages] - Custom messages for format errors
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
export {}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('./types.js').ErrorMapConfig} ErrorMapConfig
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { createErrorMapper } from './error-mapper.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Sets the global Zod error map using z.config()
|
|
9
|
+
* @param {import('zod')} z - The Zod instance
|
|
10
|
+
* @param {ErrorMapConfig} [config] - Optional configuration
|
|
11
|
+
* @returns {void}
|
|
12
|
+
*/
|
|
13
|
+
export function setZodErrorMap(z, config) {
|
|
14
|
+
const mapper = createErrorMapper(config)
|
|
15
|
+
z.config({
|
|
16
|
+
customError: (issue) => mapper.format(issue),
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Creates a Zod error map function compatible with z.setErrorMap() (Zod v3)
|
|
22
|
+
* @param {ErrorMapConfig} [config] - Optional configuration
|
|
23
|
+
* @returns {(issue: unknown, ctx: unknown) => { message: string }}
|
|
24
|
+
*/
|
|
25
|
+
export function createZodErrorMap(config) {
|
|
26
|
+
const mapper = createErrorMapper(config)
|
|
27
|
+
return (issue, _ctx) => {
|
|
28
|
+
const message = mapper.format(issue)
|
|
29
|
+
return { message }
|
|
30
|
+
}
|
|
31
|
+
}
|