@signe/schema-to-zod 1.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/dist/index.d.ts +9 -0
- package/dist/index.js +4216 -0
- package/dist/index.js.map +1 -0
- package/package.json +27 -0
- package/readme.md +150 -0
- package/src/index.ts +207 -0
- package/tests/validate.spec.ts +302 -0
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@signe/schema-to-zod",
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsup src/index.ts",
|
|
8
|
+
"dev": "tsup src/index.ts --watch"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
},
|
|
15
|
+
"./*": "./*"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [],
|
|
18
|
+
"author": "Samuel Ronce",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"type": "module",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@types/json-schema": "^7.0.15"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# Schema to Zod
|
|
2
|
+
|
|
3
|
+
Convert JSON Schema to Zod validation schemas.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @signe/schema-to-zod
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { jsonSchemaToZod } from '@signe/schema-to-zod';
|
|
15
|
+
import { z } from 'zod';
|
|
16
|
+
|
|
17
|
+
// Define your JSON Schema
|
|
18
|
+
const schema = {
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
name: { type: 'string', minLength: 3 },
|
|
22
|
+
email: { type: 'string', format: 'email' },
|
|
23
|
+
age: { type: 'integer', minimum: 18 },
|
|
24
|
+
tags: {
|
|
25
|
+
type: 'array',
|
|
26
|
+
items: { type: 'string' },
|
|
27
|
+
minItems: 1
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
required: ['name', 'email']
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Convert to Zod schema
|
|
34
|
+
const zodSchema = z.object(jsonSchemaToZod(schema));
|
|
35
|
+
|
|
36
|
+
// Use the schema for validation
|
|
37
|
+
const result = zodSchema.safeParse({
|
|
38
|
+
name: 'John',
|
|
39
|
+
email: 'john@example.com',
|
|
40
|
+
age: 25,
|
|
41
|
+
tags: ['developer']
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (result.success) {
|
|
45
|
+
console.log('Valid data:', result.data);
|
|
46
|
+
} else {
|
|
47
|
+
console.log('Validation errors:', result.error);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Features
|
|
52
|
+
|
|
53
|
+
- Supports common JSON Schema types:
|
|
54
|
+
- string
|
|
55
|
+
- number
|
|
56
|
+
- integer
|
|
57
|
+
- boolean
|
|
58
|
+
- array
|
|
59
|
+
- object
|
|
60
|
+
- null
|
|
61
|
+
|
|
62
|
+
- Handles JSON Schema formats:
|
|
63
|
+
- date
|
|
64
|
+
- date-time
|
|
65
|
+
- email
|
|
66
|
+
- hostname
|
|
67
|
+
- ipv4
|
|
68
|
+
- ipv6
|
|
69
|
+
- uri
|
|
70
|
+
- uuid
|
|
71
|
+
- color
|
|
72
|
+
- password
|
|
73
|
+
- code
|
|
74
|
+
- percent
|
|
75
|
+
|
|
76
|
+
- Supports validation rules:
|
|
77
|
+
- required fields
|
|
78
|
+
- string: minLength, maxLength, pattern
|
|
79
|
+
- number: minimum, maximum
|
|
80
|
+
- array: minItems, maxItems
|
|
81
|
+
- enum values
|
|
82
|
+
|
|
83
|
+
## API
|
|
84
|
+
|
|
85
|
+
### `jsonSchemaToZod(schema: JSONSchema7 | JSONSchema7Definition[]): Record<string, ZodType<unknown>>`
|
|
86
|
+
|
|
87
|
+
Converts a JSON Schema to a Zod schema object.
|
|
88
|
+
|
|
89
|
+
Parameters:
|
|
90
|
+
- `schema`: A JSON Schema object or array of schema objects
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
- A record of Zod type definitions that can be used with `z.object()`
|
|
94
|
+
|
|
95
|
+
## Examples
|
|
96
|
+
|
|
97
|
+
### Nested Objects
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
const schema = {
|
|
101
|
+
type: 'object',
|
|
102
|
+
properties: {
|
|
103
|
+
user: {
|
|
104
|
+
type: 'object',
|
|
105
|
+
properties: {
|
|
106
|
+
profile: {
|
|
107
|
+
type: 'object',
|
|
108
|
+
properties: {
|
|
109
|
+
name: { type: 'string' },
|
|
110
|
+
age: { type: 'integer' }
|
|
111
|
+
},
|
|
112
|
+
required: ['name']
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const zodSchema = z.object(jsonSchemaToZod(schema));
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Array Validation
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const schema = {
|
|
126
|
+
type: 'object',
|
|
127
|
+
properties: {
|
|
128
|
+
users: {
|
|
129
|
+
type: 'array',
|
|
130
|
+
items: {
|
|
131
|
+
type: 'object',
|
|
132
|
+
properties: {
|
|
133
|
+
name: { type: 'string' },
|
|
134
|
+
age: { type: 'integer' }
|
|
135
|
+
},
|
|
136
|
+
required: ['name']
|
|
137
|
+
},
|
|
138
|
+
minItems: 1,
|
|
139
|
+
maxItems: 3
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
required: ['users']
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const zodSchema = z.object(jsonSchemaToZod(schema));
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## License
|
|
149
|
+
|
|
150
|
+
MIT
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { z, ZodType, ZodTypeDef } from "zod";
|
|
2
|
+
import type { JSONSchema7, JSONSchema7Definition, JSONSchema7TypeName } from "json-schema";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Standard JSON Schema types only
|
|
6
|
+
*/
|
|
7
|
+
type SchemaType = Exclude<JSONSchema7TypeName, 'array' | 'object'> | 'array' | 'object';
|
|
8
|
+
|
|
9
|
+
function isValidSchemaType(type: unknown): type is SchemaType {
|
|
10
|
+
if (typeof type !== 'string') return false;
|
|
11
|
+
return ['string', 'number', 'boolean', 'integer', 'array', 'object', 'null'].includes(type);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isJSONSchema7(schema: JSONSchema7Definition): schema is JSONSchema7 {
|
|
15
|
+
return typeof schema !== 'boolean';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Zod schema for percentage values (0-100)
|
|
20
|
+
*/
|
|
21
|
+
const percent = z.number().min(0).max(100);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Map of JSON Schema formats to their corresponding Zod schema creators
|
|
25
|
+
*/
|
|
26
|
+
const formatMap: Record<string, (schema: JSONSchema7) => ZodType<unknown>> = {
|
|
27
|
+
'date': () => z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
|
28
|
+
'date-time': () => z.string().datetime(),
|
|
29
|
+
'email': () => z.string().email(),
|
|
30
|
+
'hostname': () => z.string(),
|
|
31
|
+
'ipv4': () => z.string().ip({ version: 'v4' }),
|
|
32
|
+
'ipv6': () => z.string().ip({ version: 'v6' }),
|
|
33
|
+
'uri': () => z.string().url(),
|
|
34
|
+
'uuid': () => z.string().uuid(),
|
|
35
|
+
'color': () => z.string().regex(/^#(?:[0-9a-fA-F]{3}){1,2}$/),
|
|
36
|
+
'password': () => z.string(),
|
|
37
|
+
'code': () => z.string(),
|
|
38
|
+
'percent': () => percent,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Map of JSON Schema types to their corresponding Zod schema creators
|
|
43
|
+
*/
|
|
44
|
+
const typeMap: Record<SchemaType, (schema: JSONSchema7) => ZodType<unknown, ZodTypeDef, unknown>> = {
|
|
45
|
+
'string': (schema) => {
|
|
46
|
+
if (schema.format && formatMap[schema.format]) {
|
|
47
|
+
return formatMap[schema.format](schema);
|
|
48
|
+
}
|
|
49
|
+
return z.string();
|
|
50
|
+
},
|
|
51
|
+
'number': (schema) => {
|
|
52
|
+
if (schema.format === 'percent') {
|
|
53
|
+
return percent;
|
|
54
|
+
}
|
|
55
|
+
return z.number();
|
|
56
|
+
},
|
|
57
|
+
'integer': () => z.number().int(),
|
|
58
|
+
'boolean': () => z.boolean(),
|
|
59
|
+
'null': () => z.null(),
|
|
60
|
+
'array': (schema: JSONSchema7) => {
|
|
61
|
+
if (!schema.items || Array.isArray(schema.items)) {
|
|
62
|
+
throw new Error('Invalid array items');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!isJSONSchema7(schema.items)) {
|
|
66
|
+
throw new Error('Boolean schema is not supported for array items');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (schema.items.type === 'object') {
|
|
70
|
+
return z.array(z.object(jsonSchemaToZod(schema.items)));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const itemType = schema.items.type;
|
|
74
|
+
if (!itemType || !isValidSchemaType(itemType)) {
|
|
75
|
+
throw new Error(`Unsupported array item type: ${itemType}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return z.array(typeMap[itemType](schema.items));
|
|
79
|
+
},
|
|
80
|
+
'object': (schema: JSONSchema7) => {
|
|
81
|
+
if (!schema.properties) {
|
|
82
|
+
throw new Error('Invalid object schema: missing properties');
|
|
83
|
+
}
|
|
84
|
+
return z.object(jsonSchemaToZod(schema));
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Applies validators to a Zod schema based on JSON Schema property constraints
|
|
90
|
+
*/
|
|
91
|
+
function applyValidators(
|
|
92
|
+
zodType: ZodType<unknown>,
|
|
93
|
+
schema: JSONSchema7,
|
|
94
|
+
required: boolean
|
|
95
|
+
): ZodType<unknown> {
|
|
96
|
+
let zodTypeWithValidators = zodType;
|
|
97
|
+
|
|
98
|
+
// String validators
|
|
99
|
+
if (schema.type === 'string' && required) {
|
|
100
|
+
zodTypeWithValidators = (zodTypeWithValidators as z.ZodString).min(1);
|
|
101
|
+
|
|
102
|
+
if (schema.minLength !== undefined) {
|
|
103
|
+
zodTypeWithValidators = (zodTypeWithValidators as z.ZodString).min(schema.minLength);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (schema.maxLength !== undefined) {
|
|
107
|
+
zodTypeWithValidators = (zodTypeWithValidators as z.ZodString).max(schema.maxLength);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Array validators
|
|
112
|
+
if (schema.type === 'array') {
|
|
113
|
+
if (schema.minItems !== undefined) {
|
|
114
|
+
zodTypeWithValidators = (zodTypeWithValidators as z.ZodArray<ZodType>).min(schema.minItems);
|
|
115
|
+
}
|
|
116
|
+
if (schema.maxItems !== undefined) {
|
|
117
|
+
zodTypeWithValidators = (zodTypeWithValidators as z.ZodArray<ZodType>).max(schema.maxItems);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Number validators
|
|
122
|
+
if (schema.type === 'number' || schema.type === 'integer') {
|
|
123
|
+
if (schema.minimum !== undefined) {
|
|
124
|
+
zodTypeWithValidators = (zodTypeWithValidators as z.ZodNumber).min(schema.minimum);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (schema.maximum !== undefined) {
|
|
128
|
+
zodTypeWithValidators = (zodTypeWithValidators as z.ZodNumber).max(schema.maximum);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Enum validators
|
|
133
|
+
if (schema.enum) {
|
|
134
|
+
zodTypeWithValidators = zodTypeWithValidators.refine(
|
|
135
|
+
(value): value is typeof schema.enum[number] => schema.enum!.includes(value as string),
|
|
136
|
+
{
|
|
137
|
+
message: `Must be one of: ${schema.enum.join(", ")}`,
|
|
138
|
+
}
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Pattern validators
|
|
143
|
+
if (schema.pattern) {
|
|
144
|
+
zodTypeWithValidators = (zodTypeWithValidators as z.ZodString).regex(new RegExp(schema.pattern));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return zodTypeWithValidators;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function getTypeFunction(schema: JSONSchema7): ZodType<unknown> {
|
|
151
|
+
if (!schema.type || !isValidSchemaType(schema.type as string)) {
|
|
152
|
+
throw new Error(`Unsupported type: ${schema.type}`);
|
|
153
|
+
}
|
|
154
|
+
return typeMap[schema.type as SchemaType](schema);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Converts a JSON Schema property to a Zod schema
|
|
159
|
+
*/
|
|
160
|
+
function convertPropertyToZod(
|
|
161
|
+
schema: JSONSchema7,
|
|
162
|
+
key: string,
|
|
163
|
+
parentSchema: JSONSchema7
|
|
164
|
+
): ZodType<unknown> {
|
|
165
|
+
if (schema.$ref) {
|
|
166
|
+
return z.array(
|
|
167
|
+
z.object({
|
|
168
|
+
value: z.object({
|
|
169
|
+
id: z.string()
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (schema.type === 'object') {
|
|
176
|
+
return z.object(jsonSchemaToZod(schema));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const required = Array.isArray(parentSchema.required) && parentSchema.required.includes(key);
|
|
180
|
+
const typeValidator = applyValidators(getTypeFunction(schema), schema, required);
|
|
181
|
+
return required ? typeValidator : typeValidator.optional();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Converts a JSON Schema to a Zod schema
|
|
186
|
+
*/
|
|
187
|
+
export function jsonSchemaToZod(
|
|
188
|
+
schema: JSONSchema7 | JSONSchema7Definition[],
|
|
189
|
+
): Record<string, ZodType<unknown>> {
|
|
190
|
+
const zodSchema: Record<string, ZodType<unknown>> = {};
|
|
191
|
+
|
|
192
|
+
if (Array.isArray(schema)) {
|
|
193
|
+
// Handle array of schemas (merge all schemas)
|
|
194
|
+
for (const item of schema) {
|
|
195
|
+
if (!isJSONSchema7(item)) continue;
|
|
196
|
+
Object.assign(zodSchema, jsonSchemaToZod(item));
|
|
197
|
+
}
|
|
198
|
+
} else if (schema.type === 'object' && schema.properties) {
|
|
199
|
+
// Handle object schema
|
|
200
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
201
|
+
if (!isJSONSchema7(prop)) continue;
|
|
202
|
+
zodSchema[key] = convertPropertyToZod(prop, key, schema);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return zodSchema;
|
|
207
|
+
}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from 'vitest'
|
|
2
|
+
import { jsonSchemaToZod } from '../src'
|
|
3
|
+
import { z } from 'zod'
|
|
4
|
+
|
|
5
|
+
describe('jsonSchemaToZod', () => {
|
|
6
|
+
|
|
7
|
+
test('Should convert basic types correctly', async () => {
|
|
8
|
+
const schema = {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
name: { type: 'string' },
|
|
12
|
+
email: { type: 'string', format: 'email' },
|
|
13
|
+
age: { type: 'integer' },
|
|
14
|
+
isActive: { type: 'boolean' },
|
|
15
|
+
scores: { type: 'array', items: { type: 'number' } },
|
|
16
|
+
},
|
|
17
|
+
required: ['name', 'email']
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const zodSchema = jsonSchemaToZod(schema)
|
|
21
|
+
|
|
22
|
+
expect(Object.keys(zodSchema)).toEqual(['name', 'email', 'age', 'isActive', 'scores'])
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('Should throw an error for unsupported types', async () => {
|
|
26
|
+
const schema = {
|
|
27
|
+
type: 'object',
|
|
28
|
+
properties: {
|
|
29
|
+
unsupportedProp: { type: 'unsupported' }
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
expect(() => jsonSchemaToZod(schema)).toThrow('Unsupported type: unsupported')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('Should apply validators correctly', async () => {
|
|
37
|
+
const schema = {
|
|
38
|
+
type: 'object',
|
|
39
|
+
properties: {
|
|
40
|
+
name: { type: 'string', minLength: 3, maxLength: 50 },
|
|
41
|
+
age: { type: 'integer', minimum: 18, maximum: 100 },
|
|
42
|
+
},
|
|
43
|
+
required: ['name']
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const zodSchema = z.object(jsonSchemaToZod(schema))
|
|
47
|
+
|
|
48
|
+
// Name is required
|
|
49
|
+
let result = zodSchema.safeParse({ age: 30 })
|
|
50
|
+
expect(result.success).toBeFalsy()
|
|
51
|
+
|
|
52
|
+
// Name is too short
|
|
53
|
+
result = zodSchema.safeParse({ name: 'Jo', age: 30 })
|
|
54
|
+
expect(result.success).toBeFalsy()
|
|
55
|
+
|
|
56
|
+
// Name is too long
|
|
57
|
+
result = zodSchema.safeParse({ name: 'J'.repeat(51), age: 30 })
|
|
58
|
+
expect(result.success).toBeFalsy()
|
|
59
|
+
|
|
60
|
+
// Age is too low
|
|
61
|
+
result = zodSchema.safeParse({ name: 'John', age: 17 })
|
|
62
|
+
expect(result.success).toBeFalsy()
|
|
63
|
+
|
|
64
|
+
// Age is too high
|
|
65
|
+
result = zodSchema.safeParse({ name: 'John', age: 101 })
|
|
66
|
+
expect(result.success).toBeFalsy()
|
|
67
|
+
|
|
68
|
+
// All validations pass
|
|
69
|
+
result = zodSchema.safeParse({ name: 'John', age: 30 })
|
|
70
|
+
expect(result.success).toBeTruthy()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('Should handle string type correctly', async () => {
|
|
74
|
+
const schema = {
|
|
75
|
+
type: 'object',
|
|
76
|
+
properties: {
|
|
77
|
+
name: { type: 'string' },
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const zodSchema = z.object(jsonSchemaToZod(schema))
|
|
82
|
+
|
|
83
|
+
// Name is not a string
|
|
84
|
+
let result = zodSchema.safeParse({ name: 123 })
|
|
85
|
+
expect(result.success).toBeFalsy()
|
|
86
|
+
|
|
87
|
+
// Name is a string
|
|
88
|
+
result = zodSchema.safeParse({ name: 'John' })
|
|
89
|
+
expect(result.success).toBeTruthy()
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('Should handle special types correctly', async () => {
|
|
93
|
+
const schema = {
|
|
94
|
+
type: 'object',
|
|
95
|
+
properties: {
|
|
96
|
+
colorCode: { type: 'string', format: 'color' },
|
|
97
|
+
secretCode: { type: 'string', format: 'code' },
|
|
98
|
+
userPassword: { type: 'string', format: 'password' },
|
|
99
|
+
percentage: { type: 'number', format: 'percent' },
|
|
100
|
+
createdAt: { type: 'string', format: 'date' },
|
|
101
|
+
description: { type: 'string' }
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const zodSchema = z.object(jsonSchemaToZod(schema))
|
|
106
|
+
|
|
107
|
+
// Test color validation
|
|
108
|
+
expect(zodSchema.safeParse({ colorCode: '#123456' }).success).toBeTruthy()
|
|
109
|
+
expect(zodSchema.safeParse({ colorCode: '#abc' }).success).toBeTruthy()
|
|
110
|
+
expect(zodSchema.safeParse({ colorCode: 'invalid' }).success).toBeFalsy()
|
|
111
|
+
|
|
112
|
+
// Test percent validation
|
|
113
|
+
expect(zodSchema.safeParse({ percentage: 50 }).success).toBeTruthy()
|
|
114
|
+
expect(zodSchema.safeParse({ percentage: 101 }).success).toBeFalsy()
|
|
115
|
+
expect(zodSchema.safeParse({ percentage: -1 }).success).toBeFalsy()
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test('Should handle nested objects correctly', async () => {
|
|
119
|
+
const schema = {
|
|
120
|
+
type: 'object' as const,
|
|
121
|
+
properties: {
|
|
122
|
+
user: {
|
|
123
|
+
type: 'object' as const,
|
|
124
|
+
properties: {
|
|
125
|
+
profile: {
|
|
126
|
+
type: 'object' as const,
|
|
127
|
+
properties: {
|
|
128
|
+
name: { type: 'string' as const },
|
|
129
|
+
age: { type: 'integer' as const }
|
|
130
|
+
},
|
|
131
|
+
required: ['name']
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const zodSchema = z.object(jsonSchemaToZod(schema))
|
|
139
|
+
|
|
140
|
+
expect(zodSchema.safeParse({
|
|
141
|
+
user: {
|
|
142
|
+
profile: {
|
|
143
|
+
name: 'John',
|
|
144
|
+
age: 30
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}).success).toBeTruthy()
|
|
148
|
+
|
|
149
|
+
expect(zodSchema.safeParse({
|
|
150
|
+
user: {
|
|
151
|
+
profile: {
|
|
152
|
+
age: 30
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}).success).toBeFalsy()
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test('Should handle array of schemas correctly', async () => {
|
|
159
|
+
const schemas = [
|
|
160
|
+
{
|
|
161
|
+
schema: {
|
|
162
|
+
type: 'object',
|
|
163
|
+
properties: {
|
|
164
|
+
name: { type: 'string' }
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
schema: {
|
|
170
|
+
type: 'object',
|
|
171
|
+
properties: {
|
|
172
|
+
age: { type: 'integer' }
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
const zodSchema = z.object(jsonSchemaToZod(schemas))
|
|
179
|
+
|
|
180
|
+
expect(zodSchema.safeParse({
|
|
181
|
+
name: 'John',
|
|
182
|
+
age: 30
|
|
183
|
+
}).success).toBeTruthy()
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
test('Should handle $ref correctly', async () => {
|
|
187
|
+
const schema = {
|
|
188
|
+
type: 'object',
|
|
189
|
+
properties: {
|
|
190
|
+
categories: {
|
|
191
|
+
$ref: '#/definitions/CategoryList'
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const zodSchema = z.object(jsonSchemaToZod(schema))
|
|
197
|
+
|
|
198
|
+
expect(zodSchema.safeParse({
|
|
199
|
+
categories: [
|
|
200
|
+
{ value: { id: '123' } }
|
|
201
|
+
]
|
|
202
|
+
}).success).toBeTruthy()
|
|
203
|
+
|
|
204
|
+
expect(zodSchema.safeParse({
|
|
205
|
+
categories: [
|
|
206
|
+
{ value: { wrong: '123' } }
|
|
207
|
+
]
|
|
208
|
+
}).success).toBeFalsy()
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
test('Should handle pattern and enum validations', async () => {
|
|
212
|
+
const schema = {
|
|
213
|
+
type: 'object',
|
|
214
|
+
properties: {
|
|
215
|
+
code: {
|
|
216
|
+
type: 'string',
|
|
217
|
+
pattern: '^[A-Z]{3}[0-9]{3}$'
|
|
218
|
+
},
|
|
219
|
+
status: {
|
|
220
|
+
type: 'string',
|
|
221
|
+
enum: ['active', 'inactive', 'pending']
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const zodSchema = z.object(jsonSchemaToZod(schema))
|
|
227
|
+
|
|
228
|
+
// Test pattern validation
|
|
229
|
+
expect(zodSchema.safeParse({ code: 'ABC123' }).success).toBeTruthy()
|
|
230
|
+
expect(zodSchema.safeParse({ code: 'invalid' }).success).toBeFalsy()
|
|
231
|
+
|
|
232
|
+
// Test enum validation
|
|
233
|
+
expect(zodSchema.safeParse({ status: 'active' }).success).toBeTruthy()
|
|
234
|
+
expect(zodSchema.safeParse({ status: 'invalid' }).success).toBeFalsy()
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
test('Should handle array items with complex validation', async () => {
|
|
238
|
+
const schema = {
|
|
239
|
+
type: 'object',
|
|
240
|
+
properties: {
|
|
241
|
+
users: {
|
|
242
|
+
type: 'array',
|
|
243
|
+
items: {
|
|
244
|
+
type: 'object',
|
|
245
|
+
properties: {
|
|
246
|
+
name: { type: 'string' },
|
|
247
|
+
age: { type: 'integer' }
|
|
248
|
+
},
|
|
249
|
+
required: ['name']
|
|
250
|
+
},
|
|
251
|
+
minItems: 1,
|
|
252
|
+
maxItems: 3
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
required: ['users']
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const zodSchema = z.object(jsonSchemaToZod(schema))
|
|
259
|
+
|
|
260
|
+
// Valid case
|
|
261
|
+
let result = zodSchema.safeParse({
|
|
262
|
+
users: [
|
|
263
|
+
{ name: 'John', age: 30 },
|
|
264
|
+
{ name: 'Jane', age: 25 }
|
|
265
|
+
]
|
|
266
|
+
})
|
|
267
|
+
expect(result.success).toBeTruthy()
|
|
268
|
+
|
|
269
|
+
// Too many items
|
|
270
|
+
result = zodSchema.safeParse({
|
|
271
|
+
users: [
|
|
272
|
+
{ name: 'John', age: 30 },
|
|
273
|
+
{ name: 'Jane', age: 25 },
|
|
274
|
+
{ name: 'Bob', age: 35 },
|
|
275
|
+
{ name: 'Alice', age: 28 }
|
|
276
|
+
]
|
|
277
|
+
})
|
|
278
|
+
expect(result.success).toBeFalsy()
|
|
279
|
+
|
|
280
|
+
// Missing required field in array item
|
|
281
|
+
result = zodSchema.safeParse({
|
|
282
|
+
users: [
|
|
283
|
+
{ age: 30 }
|
|
284
|
+
]
|
|
285
|
+
})
|
|
286
|
+
expect(result.success).toBeFalsy()
|
|
287
|
+
|
|
288
|
+
// Wrong type in array item
|
|
289
|
+
result = zodSchema.safeParse({
|
|
290
|
+
users: [
|
|
291
|
+
{ name: 'John', age: 'thirty' }
|
|
292
|
+
]
|
|
293
|
+
})
|
|
294
|
+
expect(result.success).toBeFalsy()
|
|
295
|
+
|
|
296
|
+
// Empty array (violates minItems)
|
|
297
|
+
result = zodSchema.safeParse({
|
|
298
|
+
users: []
|
|
299
|
+
})
|
|
300
|
+
expect(result.success).toBeFalsy()
|
|
301
|
+
})
|
|
302
|
+
})
|