@theshelf/validation-driver-zod 0.4.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 +24 -0
- package/dist/Zod.d.ts +8 -0
- package/dist/Zod.js +142 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
|
|
2
|
+
# Validation Zod driver | The Shelf
|
|
3
|
+
|
|
4
|
+
This package contains the driver implementation for Zod. This driver can be used by the [core package](../../core/README.md) for performing the actual operations.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install @theshelf/validation @theshelf/validation-driver-zod
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## How to use
|
|
13
|
+
|
|
14
|
+
The basic set up looks like this.
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import Validator from '@theshelf/validation';
|
|
18
|
+
import { ZodDriver } from '@theshelf/validation-driver-zod';
|
|
19
|
+
|
|
20
|
+
const driver = new ZodDriver();
|
|
21
|
+
const validator = new Validator(driver);
|
|
22
|
+
|
|
23
|
+
// Perform operations with the validator instance
|
|
24
|
+
```
|
package/dist/Zod.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ValidationResult } from '@theshelf/validation';
|
|
2
|
+
import type { Driver, ValidationSchema } from '@theshelf/validation';
|
|
3
|
+
export default class Zod implements Driver {
|
|
4
|
+
#private;
|
|
5
|
+
constructor();
|
|
6
|
+
get name(): string;
|
|
7
|
+
validate(data: unknown, schema: ValidationSchema): ValidationResult;
|
|
8
|
+
}
|
package/dist/Zod.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ValidationResult, FieldTypes, MAX_EMAIL_LENGTH, MAX_URL_LENGTH, UnknownValidator } from '@theshelf/validation';
|
|
3
|
+
// Zod is so type heavy that we've chosen for inferred types to be used.
|
|
4
|
+
// This is a trade-off between readability and verbosity.
|
|
5
|
+
export default class Zod {
|
|
6
|
+
#validations = new Map();
|
|
7
|
+
constructor() {
|
|
8
|
+
this.#validations.set(FieldTypes.STRING, (value) => this.#validateString(value));
|
|
9
|
+
this.#validations.set(FieldTypes.NUMBER, (value) => this.#validateNumber(value));
|
|
10
|
+
this.#validations.set(FieldTypes.BOOLEAN, (value) => this.#validateBoolean(value));
|
|
11
|
+
this.#validations.set(FieldTypes.DATE, (value) => this.#validateDate(value));
|
|
12
|
+
this.#validations.set(FieldTypes.DATETIME, (value) => this.#validateDateTime(value));
|
|
13
|
+
this.#validations.set(FieldTypes.UUID, (value) => this.#validateUuid(value));
|
|
14
|
+
this.#validations.set(FieldTypes.EMAIL, (value) => this.#validateEmail(value));
|
|
15
|
+
this.#validations.set(FieldTypes.ARRAY, (value) => this.#validateArray(value));
|
|
16
|
+
this.#validations.set(FieldTypes.URL, (value) => this.#validateUrl(value));
|
|
17
|
+
this.#validations.set(FieldTypes.ENUM, (value) => this.#validateEnum(value));
|
|
18
|
+
}
|
|
19
|
+
get name() { return Zod.name; }
|
|
20
|
+
validate(data, schema) {
|
|
21
|
+
const validator = this.#buildValidator(schema);
|
|
22
|
+
const result = validator.safeParse(data);
|
|
23
|
+
if (result.success === false) {
|
|
24
|
+
const issues = result.error.issues;
|
|
25
|
+
const messages = this.#getMessages(issues, schema);
|
|
26
|
+
return new ValidationResult(true, messages);
|
|
27
|
+
}
|
|
28
|
+
return new ValidationResult(false);
|
|
29
|
+
}
|
|
30
|
+
#buildValidator(schema) {
|
|
31
|
+
return Object.entries(schema)
|
|
32
|
+
.reduce((partialSchema, [key, value]) => {
|
|
33
|
+
const fieldValidation = this.#getValidation(value);
|
|
34
|
+
return partialSchema.extend({ [key]: fieldValidation });
|
|
35
|
+
}, z.object({})).strict();
|
|
36
|
+
}
|
|
37
|
+
#getValidation(schema) {
|
|
38
|
+
for (const [key, validation] of Object.entries(schema)) {
|
|
39
|
+
if (key === 'message')
|
|
40
|
+
continue;
|
|
41
|
+
const validator = this.#validations.get(key.toLowerCase());
|
|
42
|
+
if (validator === undefined) {
|
|
43
|
+
throw new UnknownValidator(key);
|
|
44
|
+
}
|
|
45
|
+
return validator(validation);
|
|
46
|
+
}
|
|
47
|
+
return z.never();
|
|
48
|
+
}
|
|
49
|
+
#validateString(value) {
|
|
50
|
+
let validation = z.string();
|
|
51
|
+
if (value.minLength !== undefined)
|
|
52
|
+
validation = validation.min(value.minLength);
|
|
53
|
+
if (value.maxLength !== undefined)
|
|
54
|
+
validation = validation.max(value.maxLength);
|
|
55
|
+
if (value.pattern !== undefined)
|
|
56
|
+
validation = validation.regex(new RegExp(value.pattern));
|
|
57
|
+
return this.#checkRequired(value, validation);
|
|
58
|
+
}
|
|
59
|
+
#validateNumber(value) {
|
|
60
|
+
let validation = z.number();
|
|
61
|
+
if (value.minValue !== undefined)
|
|
62
|
+
validation = validation.min(value.minValue);
|
|
63
|
+
if (value.maxValue !== undefined)
|
|
64
|
+
validation = validation.max(value.maxValue);
|
|
65
|
+
return this.#checkRequired(value, validation);
|
|
66
|
+
}
|
|
67
|
+
#validateBoolean(value) {
|
|
68
|
+
const validation = z.boolean();
|
|
69
|
+
return this.#checkRequired(value, validation);
|
|
70
|
+
}
|
|
71
|
+
#validateDate(value) {
|
|
72
|
+
const validation = z.iso.date();
|
|
73
|
+
return this.#checkRequired(value, validation);
|
|
74
|
+
}
|
|
75
|
+
#validateDateTime(value) {
|
|
76
|
+
const validation = z.iso.datetime();
|
|
77
|
+
return this.#checkRequired(value, validation);
|
|
78
|
+
}
|
|
79
|
+
#validateUuid(value) {
|
|
80
|
+
const validation = z.uuid();
|
|
81
|
+
return this.#checkRequired(value, validation);
|
|
82
|
+
}
|
|
83
|
+
#validateEmail(value) {
|
|
84
|
+
const validation = z.email().max(MAX_EMAIL_LENGTH);
|
|
85
|
+
return this.#checkRequired(value, validation);
|
|
86
|
+
}
|
|
87
|
+
#validateArray(value) {
|
|
88
|
+
let validation = value.validations === undefined
|
|
89
|
+
? z.array(z.unknown())
|
|
90
|
+
: z.array(this.#getValidation(value.validations));
|
|
91
|
+
if (value.minLength !== undefined)
|
|
92
|
+
validation = validation.min(value.minLength);
|
|
93
|
+
if (value.maxLength !== undefined)
|
|
94
|
+
validation = validation.max(value.maxLength);
|
|
95
|
+
return this.#checkRequired(value, validation);
|
|
96
|
+
}
|
|
97
|
+
#validateUrl(value) {
|
|
98
|
+
let validation = z.url().max(MAX_URL_LENGTH);
|
|
99
|
+
if (value.protocols !== undefined) {
|
|
100
|
+
const escapedProtocols = value.protocols.map(p => p.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
101
|
+
const expression = escapedProtocols.join('|');
|
|
102
|
+
validation = validation.regex(new RegExp(`^(${expression}):.*`));
|
|
103
|
+
}
|
|
104
|
+
return this.#checkRequired(value, validation);
|
|
105
|
+
}
|
|
106
|
+
#validateEnum(value) {
|
|
107
|
+
const validation = value.values === undefined
|
|
108
|
+
? z.enum([])
|
|
109
|
+
: z.enum(value.values);
|
|
110
|
+
return this.#checkRequired(value, validation);
|
|
111
|
+
}
|
|
112
|
+
#checkRequired(value, validation) {
|
|
113
|
+
return value.required
|
|
114
|
+
? validation
|
|
115
|
+
: validation.optional();
|
|
116
|
+
}
|
|
117
|
+
#getMessages(issues, schema) {
|
|
118
|
+
const messages = new Map();
|
|
119
|
+
for (const issue of issues) {
|
|
120
|
+
if (issue.code === 'unrecognized_keys') {
|
|
121
|
+
this.#mapUnrecognizedKeys(issue, schema, messages);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (issue.path.length === 0)
|
|
125
|
+
continue;
|
|
126
|
+
const field = String(issue.path[0]);
|
|
127
|
+
const message = this.#getMessageByField(field, schema);
|
|
128
|
+
messages.set(field, message);
|
|
129
|
+
}
|
|
130
|
+
return messages;
|
|
131
|
+
}
|
|
132
|
+
#mapUnrecognizedKeys(issue, schema, messages) {
|
|
133
|
+
for (const key of issue.keys) {
|
|
134
|
+
const message = this.#getMessageByField(key, schema);
|
|
135
|
+
messages.set(key, message);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
#getMessageByField(path, schema) {
|
|
139
|
+
const field = schema[path];
|
|
140
|
+
return field?.message ?? 'Invalid field';
|
|
141
|
+
}
|
|
142
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as ZodDriver } from './Zod.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as ZodDriver } from './Zod.js';
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@theshelf/validation-driver-zod",
|
|
3
|
+
"private": false,
|
|
4
|
+
"version": "0.4.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"repository": {
|
|
7
|
+
"url": "git+https://github.com/MaskingTechnology/theshelf.git"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"clean": "rimraf dist",
|
|
13
|
+
"test": "vitest run",
|
|
14
|
+
"test-coverage": "vitest run --coverage",
|
|
15
|
+
"lint": "eslint",
|
|
16
|
+
"review": "npm run build && npm run lint && npm run test",
|
|
17
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"README.md",
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"exports": "./dist/index.js",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"zod": "4.3.6"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"@theshelf/validation": "^0.4.0"
|
|
30
|
+
}
|
|
31
|
+
}
|