@geekmidas/schema 1.0.4 → 10.0.0-alpha.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 +6 -6
- package/package.json +4 -1
- package/CHANGELOG.md +0 -45
- package/src/__benchmarks__/conversion.bench.ts +0 -88
- package/src/__tests__/conversion.spec.ts +0 -502
- package/src/__tests__/openapi.spec.ts +0 -396
- package/src/__tests__/parser.spec.ts +0 -236
- package/src/conversion.ts +0 -220
- package/src/index.ts +0 -15
- package/src/openapi.ts +0 -75
- package/src/parser.ts +0 -30
- package/src/types.ts +0 -37
- package/tsconfig.json +0 -9
- package/tsdown.config.ts +0 -5
package/README.md
CHANGED
|
@@ -73,7 +73,7 @@ import { z } from 'zod';
|
|
|
73
73
|
// Define a schema
|
|
74
74
|
const userSchema = z.object({
|
|
75
75
|
id: z.string(),
|
|
76
|
-
email: z.
|
|
76
|
+
email: z.email(),
|
|
77
77
|
age: z.number().min(18),
|
|
78
78
|
role: z.enum(['admin', 'user'])
|
|
79
79
|
});
|
|
@@ -130,7 +130,7 @@ import { z } from 'zod';
|
|
|
130
130
|
const schemas = {
|
|
131
131
|
user: z.object({
|
|
132
132
|
id: z.string(),
|
|
133
|
-
email: z.
|
|
133
|
+
email: z.email()
|
|
134
134
|
}),
|
|
135
135
|
post: z.object({
|
|
136
136
|
id: z.string(),
|
|
@@ -237,7 +237,7 @@ import { z } from 'zod';
|
|
|
237
237
|
const createUserEndpoint = {
|
|
238
238
|
body: z.object({
|
|
239
239
|
name: z.string().min(1),
|
|
240
|
-
email: z.
|
|
240
|
+
email: z.email(),
|
|
241
241
|
age: z.number().int().min(18)
|
|
242
242
|
}),
|
|
243
243
|
response: z.object({
|
|
@@ -272,7 +272,7 @@ import { z } from 'zod';
|
|
|
272
272
|
|
|
273
273
|
const userSchema = z.object({
|
|
274
274
|
id: z.string(),
|
|
275
|
-
email: z.
|
|
275
|
+
email: z.email()
|
|
276
276
|
});
|
|
277
277
|
|
|
278
278
|
type User = InferStandardSchema<typeof userSchema>;
|
|
@@ -296,11 +296,11 @@ if (isUser(data)) {
|
|
|
296
296
|
This package is used internally by `@geekmidas/constructs` for type-safe endpoint validation:
|
|
297
297
|
|
|
298
298
|
```typescript
|
|
299
|
-
import {
|
|
299
|
+
import { api } from '../constructs/api';
|
|
300
300
|
import { z } from 'zod';
|
|
301
301
|
|
|
302
302
|
// The constructs package uses InferStandardSchema internally
|
|
303
|
-
const endpoint =
|
|
303
|
+
const endpoint = api
|
|
304
304
|
.post('/users')
|
|
305
305
|
.body(z.object({ name: z.string() }))
|
|
306
306
|
.output(z.object({ id: z.string() }))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geekmidas/schema",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.0.0-alpha.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -44,6 +44,9 @@
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
|
+
"files": [
|
|
48
|
+
"dist"
|
|
49
|
+
],
|
|
47
50
|
"repository": {
|
|
48
51
|
"type": "git",
|
|
49
52
|
"url": "https://github.com/geekmidas/toolbox"
|
package/CHANGELOG.md
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# @geekmidas/schema
|
|
2
|
-
|
|
3
|
-
## 1.0.4
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
- 🐛 [#11](https://github.com/geekmidas/toolbox/pull/11) [`40f4dc0`](https://github.com/geekmidas/toolbox/commit/40f4dc095911b2223a255029d8f776caf7781309) Thanks [@geekmidas](https://github.com/geekmidas)! - Patch release across all packages to realign published versions with the
|
|
8
|
-
registry. The previous release only published the four packages that had
|
|
9
|
-
version bumps; the remaining packages failed with "cannot publish over the
|
|
10
|
-
previously published versions" because their versions were unchanged.
|
|
11
|
-
|
|
12
|
-
## 1.0.3
|
|
13
|
-
|
|
14
|
-
### Patch Changes
|
|
15
|
-
|
|
16
|
-
- [#7](https://github.com/geekmidas/toolbox/pull/7) [`e0d06b3`](https://github.com/geekmidas/toolbox/commit/e0d06b38dfd275758f7955f5754900ab78779302) Thanks [@geekmidas](https://github.com/geekmidas)! - feat(constructs): allow endpoint handlers to return the output schema's input type
|
|
17
|
-
|
|
18
|
-
Endpoint handlers previously had to return the output schema's _parsed_ type
|
|
19
|
-
(`InferStandardSchema`). When an output schema coerces its value (e.g. a `Date`
|
|
20
|
-
serialized to an ISO `string`, or an applied default), that forced handlers to
|
|
21
|
-
pre-coerce values themselves even though the schema would do it on the way out.
|
|
22
|
-
|
|
23
|
-
A new `InferStandardSchemaInput` type is added to `@geekmidas/schema`, exposing a
|
|
24
|
-
Standard Schema's _input_ type (`StandardSchemaV1.InferInput`). `Endpoint`'s
|
|
25
|
-
handler return type now uses it, so handlers may return the looser pre-coercion
|
|
26
|
-
input while consumers (`EndpointOutput` and the generated client) still see the
|
|
27
|
-
narrower parsed output type.
|
|
28
|
-
|
|
29
|
-
## 1.0.2
|
|
30
|
-
|
|
31
|
-
### Patch Changes
|
|
32
|
-
|
|
33
|
-
- 🐛 [`d70c6c0`](https://github.com/geekmidas/toolbox/commit/d70c6c0aeb8a79da2473ac77dbd8255a4a2f5651) Thanks [@geekmidas](https://github.com/geekmidas)! - Fix `package.json` exports so TypeScript declarations resolve correctly under NodeNext/Bundler module resolution. Each subpath export now nests `types` inside its `import`/`require` condition, pointing at the `.d.mts` and `.d.cts` files that `tsdown` actually emits (previously the exports referenced non-existent `.d.ts` files, causing type-resolution failures for consumers). Both ESM (`.mjs`) and CJS (`.cjs`) runtime entry points are preserved. Additionally, `@geekmidas/ui` had `import` paths pointing at `.js` files that were never emitted — those are corrected to `.mjs`.
|
|
34
|
-
|
|
35
|
-
## 1.0.1
|
|
36
|
-
|
|
37
|
-
### Patch Changes
|
|
38
|
-
|
|
39
|
-
- 🐛 [`aeba918`](https://github.com/geekmidas/toolbox/commit/aeba918fc258f6ccdb96b8273b2bc01bd2190553) Thanks [@geekmidas](https://github.com/geekmidas)! - Fix schema, openapi generation and events testkit
|
|
40
|
-
|
|
41
|
-
## 1.0.0
|
|
42
|
-
|
|
43
|
-
### Major Changes
|
|
44
|
-
|
|
45
|
-
- [`ff7b115`](https://github.com/geekmidas/toolbox/commit/ff7b11599f60f84ac6cdc73714c853ecf786b2e8) Thanks [@geekmidas](https://github.com/geekmidas)! - Version 1 Stable release
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { bench, describe } from 'vitest';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { convertStandardSchemaToJsonSchema } from '../conversion';
|
|
4
|
-
|
|
5
|
-
describe('Schema Conversion - Simple', () => {
|
|
6
|
-
const simpleSchema = z.object({
|
|
7
|
-
id: z.string(),
|
|
8
|
-
name: z.string(),
|
|
9
|
-
email: z.string().email(),
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
bench('simple object schema', async () => {
|
|
13
|
-
await convertStandardSchemaToJsonSchema(simpleSchema);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
const primitiveSchema = z.string();
|
|
17
|
-
bench('primitive string schema', async () => {
|
|
18
|
-
await convertStandardSchemaToJsonSchema(primitiveSchema);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
const arraySchema = z.array(z.string());
|
|
22
|
-
bench('array of strings schema', async () => {
|
|
23
|
-
await convertStandardSchemaToJsonSchema(arraySchema);
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe('Schema Conversion - Complex', () => {
|
|
28
|
-
const nestedSchema = z.object({
|
|
29
|
-
user: z.object({
|
|
30
|
-
profile: z.object({
|
|
31
|
-
name: z.string(),
|
|
32
|
-
bio: z.string().optional(),
|
|
33
|
-
settings: z.record(z.string(), z.unknown()),
|
|
34
|
-
}),
|
|
35
|
-
contacts: z.array(
|
|
36
|
-
z.object({
|
|
37
|
-
type: z.enum(['email', 'phone']),
|
|
38
|
-
value: z.string(),
|
|
39
|
-
}),
|
|
40
|
-
),
|
|
41
|
-
}),
|
|
42
|
-
metadata: z.object({
|
|
43
|
-
createdAt: z.string(),
|
|
44
|
-
updatedAt: z.string(),
|
|
45
|
-
}),
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
bench('deeply nested schema', async () => {
|
|
49
|
-
await convertStandardSchemaToJsonSchema(nestedSchema);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
const unionSchema = z.discriminatedUnion('type', [
|
|
53
|
-
z.object({ type: z.literal('text'), content: z.string() }),
|
|
54
|
-
z.object({ type: z.literal('image'), url: z.string() }),
|
|
55
|
-
z.object({
|
|
56
|
-
type: z.literal('video'),
|
|
57
|
-
url: z.string(),
|
|
58
|
-
duration: z.number(),
|
|
59
|
-
}),
|
|
60
|
-
]);
|
|
61
|
-
|
|
62
|
-
bench('discriminated union schema', async () => {
|
|
63
|
-
await convertStandardSchemaToJsonSchema(unionSchema);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
const largeSchema = z.object(
|
|
67
|
-
Object.fromEntries(
|
|
68
|
-
Array.from({ length: 50 }, (_, i) => [`field${i}`, z.string()]),
|
|
69
|
-
),
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
bench('large object (50 fields)', async () => {
|
|
73
|
-
await convertStandardSchemaToJsonSchema(largeSchema);
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
describe('Schema Conversion - With Refinements', () => {
|
|
78
|
-
const refinedSchema = z.object({
|
|
79
|
-
age: z.number().min(0).max(150),
|
|
80
|
-
email: z.string().email(),
|
|
81
|
-
url: z.string().url(),
|
|
82
|
-
uuid: z.string().uuid(),
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
bench('schema with refinements', async () => {
|
|
86
|
-
await convertStandardSchemaToJsonSchema(refinedSchema);
|
|
87
|
-
});
|
|
88
|
-
});
|
|
@@ -1,502 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { z } from 'zod/v4';
|
|
3
|
-
import {
|
|
4
|
-
convertSchemaWithComponents,
|
|
5
|
-
convertStandardSchemaToJsonSchema,
|
|
6
|
-
getSchemaMetadata,
|
|
7
|
-
getZodMetadata,
|
|
8
|
-
SchemaVendor,
|
|
9
|
-
} from '../conversion';
|
|
10
|
-
import { createComponentCollector } from '../openapi';
|
|
11
|
-
|
|
12
|
-
describe('Schema Conversion', () => {
|
|
13
|
-
describe('convertStandardSchemaToJsonSchema', () => {
|
|
14
|
-
it('should convert Zod object schema to JSON Schema', async () => {
|
|
15
|
-
const schema = z.object({
|
|
16
|
-
name: z.string(),
|
|
17
|
-
age: z.number(),
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
|
|
21
|
-
|
|
22
|
-
expect(jsonSchema).toHaveProperty('type', 'object');
|
|
23
|
-
expect(jsonSchema).toHaveProperty('properties');
|
|
24
|
-
expect(jsonSchema.properties).toHaveProperty('name');
|
|
25
|
-
expect(jsonSchema.properties).toHaveProperty('age');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('should return undefined for undefined schema', async () => {
|
|
29
|
-
const jsonSchema = await convertStandardSchemaToJsonSchema(undefined);
|
|
30
|
-
|
|
31
|
-
expect(jsonSchema).toBeUndefined();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('should convert Zod string schema', async () => {
|
|
35
|
-
const schema = z.string();
|
|
36
|
-
|
|
37
|
-
const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
|
|
38
|
-
|
|
39
|
-
expect(jsonSchema).toHaveProperty('type', 'string');
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('should convert Zod number schema', async () => {
|
|
43
|
-
const schema = z.number();
|
|
44
|
-
|
|
45
|
-
const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
|
|
46
|
-
|
|
47
|
-
expect(jsonSchema).toHaveProperty('type', 'number');
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('should convert Zod array schema', async () => {
|
|
51
|
-
const schema = z.array(z.string());
|
|
52
|
-
|
|
53
|
-
const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
|
|
54
|
-
|
|
55
|
-
expect(jsonSchema).toHaveProperty('type', 'array');
|
|
56
|
-
expect(jsonSchema).toHaveProperty('items');
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('should convert Zod enum schema', async () => {
|
|
60
|
-
const schema = z.enum(['active', 'inactive', 'pending']);
|
|
61
|
-
|
|
62
|
-
const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
|
|
63
|
-
|
|
64
|
-
expect(jsonSchema).toHaveProperty('enum');
|
|
65
|
-
expect(jsonSchema.enum).toEqual(['active', 'inactive', 'pending']);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('should convert nested object schema', async () => {
|
|
69
|
-
const schema = z.object({
|
|
70
|
-
user: z.object({
|
|
71
|
-
name: z.string(),
|
|
72
|
-
age: z.number(),
|
|
73
|
-
}),
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
|
|
77
|
-
|
|
78
|
-
expect(jsonSchema.type).toBe('object');
|
|
79
|
-
expect(jsonSchema.properties).toHaveProperty('user');
|
|
80
|
-
expect(jsonSchema.properties.user).toHaveProperty('type', 'object');
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('should handle optional fields', async () => {
|
|
84
|
-
const schema = z.object({
|
|
85
|
-
required: z.string(),
|
|
86
|
-
optional: z.string().optional(),
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
|
|
90
|
-
|
|
91
|
-
expect(jsonSchema.required).toContain('required');
|
|
92
|
-
expect(jsonSchema.required).not.toContain('optional');
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('should extract and convert $defs with component collector', async () => {
|
|
96
|
-
const userSchema = z.object({
|
|
97
|
-
name: z.string(),
|
|
98
|
-
age: z.number(),
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
const schema = z.object({
|
|
102
|
-
user: userSchema,
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const collector = createComponentCollector();
|
|
106
|
-
const jsonSchema = await convertStandardSchemaToJsonSchema(
|
|
107
|
-
schema,
|
|
108
|
-
collector,
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
expect(jsonSchema).toBeDefined();
|
|
112
|
-
// $defs should be removed from main schema
|
|
113
|
-
expect(jsonSchema).not.toHaveProperty('$defs');
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('should throw error for unsupported vendor', async () => {
|
|
117
|
-
const invalidSchema = {
|
|
118
|
-
'~standard': {
|
|
119
|
-
vendor: 'unsupported' as any,
|
|
120
|
-
validate: vi.fn(),
|
|
121
|
-
},
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
await expect(
|
|
125
|
-
convertStandardSchemaToJsonSchema(invalidSchema as any),
|
|
126
|
-
).rejects.toThrow(/Unsupported or missing vendor/);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('should throw error for missing vendor', async () => {
|
|
130
|
-
const invalidSchema = {
|
|
131
|
-
'~standard': {
|
|
132
|
-
vendor: undefined,
|
|
133
|
-
validate: vi.fn(),
|
|
134
|
-
},
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
await expect(
|
|
138
|
-
convertStandardSchemaToJsonSchema(invalidSchema as any),
|
|
139
|
-
).rejects.toThrow(/Unsupported or missing vendor/);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('should handle schema with descriptions', async () => {
|
|
143
|
-
const schema = z
|
|
144
|
-
.object({
|
|
145
|
-
name: z.string(),
|
|
146
|
-
})
|
|
147
|
-
.describe('User information');
|
|
148
|
-
|
|
149
|
-
const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
|
|
150
|
-
|
|
151
|
-
expect(jsonSchema.description).toBe('User information');
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
it('should handle union types', async () => {
|
|
155
|
-
const schema = z.union([z.string(), z.number()]);
|
|
156
|
-
|
|
157
|
-
const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
|
|
158
|
-
|
|
159
|
-
expect(jsonSchema).toHaveProperty('anyOf');
|
|
160
|
-
});
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
describe('getZodMetadata', () => {
|
|
164
|
-
it('should return undefined for non-Zod objects', async () => {
|
|
165
|
-
const schema = z.string();
|
|
166
|
-
|
|
167
|
-
const metadata = await getZodMetadata(schema);
|
|
168
|
-
|
|
169
|
-
expect(metadata).toBeUndefined();
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it('should get metadata from Zod object with meta', async () => {
|
|
173
|
-
const schema = z.object({ name: z.string() }).meta({ id: 'User' });
|
|
174
|
-
|
|
175
|
-
const metadata = await getZodMetadata(schema);
|
|
176
|
-
|
|
177
|
-
expect(metadata).toEqual({ id: 'User' });
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it('should return undefined for Zod object without meta', async () => {
|
|
181
|
-
const schema = z.object({ name: z.string() });
|
|
182
|
-
|
|
183
|
-
const metadata = await getZodMetadata(schema);
|
|
184
|
-
|
|
185
|
-
// Returns undefined when no meta is set
|
|
186
|
-
expect(metadata).toBeUndefined();
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
describe('getSchemaMetadata', () => {
|
|
191
|
-
it('should get metadata for Zod schema', async () => {
|
|
192
|
-
const schema = z.object({ name: z.string() }).meta({ id: 'UserMeta' });
|
|
193
|
-
|
|
194
|
-
const metadata = await getSchemaMetadata(schema);
|
|
195
|
-
|
|
196
|
-
expect(metadata).toEqual({ id: 'UserMeta' });
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it('should return undefined for non-Zod vendor', async () => {
|
|
200
|
-
const schema = {
|
|
201
|
-
'~standard': {
|
|
202
|
-
vendor: 'valibot',
|
|
203
|
-
validate: vi.fn(),
|
|
204
|
-
},
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
const metadata = await getSchemaMetadata(schema as any);
|
|
208
|
-
|
|
209
|
-
expect(metadata).toBeUndefined();
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it('should return undefined for schema without vendor', async () => {
|
|
213
|
-
const schema = {
|
|
214
|
-
'~standard': {
|
|
215
|
-
vendor: undefined,
|
|
216
|
-
validate: vi.fn(),
|
|
217
|
-
},
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
const metadata = await getSchemaMetadata(schema as any);
|
|
221
|
-
|
|
222
|
-
expect(metadata).toBeUndefined();
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
describe('convertSchemaWithComponents', () => {
|
|
227
|
-
it('should convert schema without component collector', async () => {
|
|
228
|
-
const schema = z.object({
|
|
229
|
-
name: z.string(),
|
|
230
|
-
age: z.number(),
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
const jsonSchema = await convertSchemaWithComponents(schema);
|
|
234
|
-
|
|
235
|
-
expect(jsonSchema).toHaveProperty('type', 'object');
|
|
236
|
-
expect(jsonSchema).toHaveProperty('properties');
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
it('should return undefined for undefined schema', async () => {
|
|
240
|
-
const jsonSchema = await convertSchemaWithComponents(undefined);
|
|
241
|
-
|
|
242
|
-
expect(jsonSchema).toBeUndefined();
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
it('should add schema with ID to component collector', async () => {
|
|
246
|
-
const schema = z.object({ name: z.string() }).meta({ id: 'UserComp' });
|
|
247
|
-
|
|
248
|
-
const collector = createComponentCollector();
|
|
249
|
-
const result = await convertSchemaWithComponents(schema, collector);
|
|
250
|
-
|
|
251
|
-
expect(result).toEqual({ $ref: '#/components/schemas/UserComp' });
|
|
252
|
-
expect(collector.schemas).toHaveProperty('UserComp');
|
|
253
|
-
expect(collector.schemas.UserComp).not.toHaveProperty('id');
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
it('should not add schema without ID to component collector', async () => {
|
|
257
|
-
const schema = z.object({ name: z.string() });
|
|
258
|
-
|
|
259
|
-
const collector = createComponentCollector();
|
|
260
|
-
const result = await convertSchemaWithComponents(schema, collector);
|
|
261
|
-
|
|
262
|
-
expect(result).not.toHaveProperty('$ref');
|
|
263
|
-
expect(Object.keys(collector.schemas)).toHaveLength(0);
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
it('should handle schema with id in JSON Schema', async () => {
|
|
267
|
-
const schema = z.object({ name: z.string() }).meta({ id: 'Person' });
|
|
268
|
-
|
|
269
|
-
const collector = createComponentCollector();
|
|
270
|
-
const result = await convertSchemaWithComponents(schema, collector);
|
|
271
|
-
|
|
272
|
-
expect(result).toEqual({ $ref: '#/components/schemas/Person' });
|
|
273
|
-
expect(collector.schemas).toHaveProperty('Person');
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
it('should convert multiple schemas with collector', async () => {
|
|
277
|
-
const schema1 = z.object({ name: z.string() }).meta({ id: 'UserMulti' });
|
|
278
|
-
const schema2 = z.object({ title: z.string() }).meta({ id: 'PostMulti' });
|
|
279
|
-
|
|
280
|
-
const collector = createComponentCollector();
|
|
281
|
-
|
|
282
|
-
await convertSchemaWithComponents(schema1, collector);
|
|
283
|
-
await convertSchemaWithComponents(schema2, collector);
|
|
284
|
-
|
|
285
|
-
expect(Object.keys(collector.schemas)).toHaveLength(2);
|
|
286
|
-
expect(collector.schemas).toHaveProperty('UserMulti');
|
|
287
|
-
expect(collector.schemas).toHaveProperty('PostMulti');
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
it('should handle nested objects with collector', async () => {
|
|
291
|
-
const schema = z
|
|
292
|
-
.object({
|
|
293
|
-
user: z.object({
|
|
294
|
-
name: z.string(),
|
|
295
|
-
profile: z.object({
|
|
296
|
-
bio: z.string(),
|
|
297
|
-
}),
|
|
298
|
-
}),
|
|
299
|
-
})
|
|
300
|
-
.meta({ id: 'UserData' });
|
|
301
|
-
|
|
302
|
-
const collector = createComponentCollector();
|
|
303
|
-
const result = await convertSchemaWithComponents(schema, collector);
|
|
304
|
-
|
|
305
|
-
expect(result).toEqual({ $ref: '#/components/schemas/UserData' });
|
|
306
|
-
expect(collector.schemas).toHaveProperty('UserData');
|
|
307
|
-
});
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
describe('SchemaVendor enum', () => {
|
|
311
|
-
it('should have zod vendor', () => {
|
|
312
|
-
expect(SchemaVendor.zod).toBe('zod');
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
it('should have valibot vendor', () => {
|
|
316
|
-
expect(SchemaVendor.valibot).toBe('valibot');
|
|
317
|
-
});
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
describe('extractAndConvertDefs edge cases', () => {
|
|
321
|
-
it('should handle $ref that does not start with #/$defs/', async () => {
|
|
322
|
-
// Create a schema with a $ref that doesn't use $defs pattern
|
|
323
|
-
const schema = z.object({
|
|
324
|
-
name: z.string(),
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
const collector = createComponentCollector();
|
|
328
|
-
// Add a schema first
|
|
329
|
-
collector.addSchema('ExternalRef', { type: 'string' });
|
|
330
|
-
|
|
331
|
-
const jsonSchema = await convertStandardSchemaToJsonSchema(
|
|
332
|
-
schema,
|
|
333
|
-
collector,
|
|
334
|
-
);
|
|
335
|
-
|
|
336
|
-
// Basic test - the schema should still convert properly
|
|
337
|
-
expect(jsonSchema).toHaveProperty('type', 'object');
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
it('should process arrays in schema correctly', async () => {
|
|
341
|
-
const schema = z.object({
|
|
342
|
-
tags: z.array(z.string()),
|
|
343
|
-
numbers: z.array(z.number()),
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
const collector = createComponentCollector();
|
|
347
|
-
const jsonSchema = await convertStandardSchemaToJsonSchema(
|
|
348
|
-
schema,
|
|
349
|
-
collector,
|
|
350
|
-
);
|
|
351
|
-
|
|
352
|
-
expect(jsonSchema.properties.tags).toHaveProperty('type', 'array');
|
|
353
|
-
expect(jsonSchema.properties.numbers).toHaveProperty('type', 'array');
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
it('should skip $defs key during processing and extract to components', async () => {
|
|
357
|
-
// Create a schema that produces $defs in JSON Schema output
|
|
358
|
-
const addressSchema = z.object({
|
|
359
|
-
street: z.string(),
|
|
360
|
-
city: z.string(),
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
const personSchema = z.object({
|
|
364
|
-
name: z.string(),
|
|
365
|
-
address: addressSchema,
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
const collector = createComponentCollector();
|
|
369
|
-
const jsonSchema = await convertStandardSchemaToJsonSchema(
|
|
370
|
-
personSchema,
|
|
371
|
-
collector,
|
|
372
|
-
);
|
|
373
|
-
|
|
374
|
-
// $defs should be removed from the output
|
|
375
|
-
expect(jsonSchema).not.toHaveProperty('$defs');
|
|
376
|
-
expect(jsonSchema).toHaveProperty('type', 'object');
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
it('should handle null/undefined values in schema processing', async () => {
|
|
380
|
-
const schema = z.object({
|
|
381
|
-
nullableField: z.string().nullable(),
|
|
382
|
-
optionalField: z.string().optional(),
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
const collector = createComponentCollector();
|
|
386
|
-
const jsonSchema = await convertStandardSchemaToJsonSchema(
|
|
387
|
-
schema,
|
|
388
|
-
collector,
|
|
389
|
-
);
|
|
390
|
-
|
|
391
|
-
expect(jsonSchema).toHaveProperty('properties');
|
|
392
|
-
expect(jsonSchema.properties).toHaveProperty('nullableField');
|
|
393
|
-
expect(jsonSchema.properties).toHaveProperty('optionalField');
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
it('should handle deeply nested objects with arrays', async () => {
|
|
397
|
-
const schema = z.object({
|
|
398
|
-
users: z.array(
|
|
399
|
-
z.object({
|
|
400
|
-
name: z.string(),
|
|
401
|
-
addresses: z.array(
|
|
402
|
-
z.object({
|
|
403
|
-
street: z.string(),
|
|
404
|
-
}),
|
|
405
|
-
),
|
|
406
|
-
}),
|
|
407
|
-
),
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
const collector = createComponentCollector();
|
|
411
|
-
const jsonSchema = await convertStandardSchemaToJsonSchema(
|
|
412
|
-
schema,
|
|
413
|
-
collector,
|
|
414
|
-
);
|
|
415
|
-
|
|
416
|
-
expect(jsonSchema).toHaveProperty('type', 'object');
|
|
417
|
-
expect(jsonSchema.properties.users).toHaveProperty('type', 'array');
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
it('should handle primitive non-object schema types', async () => {
|
|
421
|
-
const stringSchema = z.string();
|
|
422
|
-
const numberSchema = z.number();
|
|
423
|
-
const booleanSchema = z.boolean();
|
|
424
|
-
|
|
425
|
-
const collector = createComponentCollector();
|
|
426
|
-
|
|
427
|
-
const stringJson = await convertStandardSchemaToJsonSchema(
|
|
428
|
-
stringSchema,
|
|
429
|
-
collector,
|
|
430
|
-
);
|
|
431
|
-
const numberJson = await convertStandardSchemaToJsonSchema(
|
|
432
|
-
numberSchema,
|
|
433
|
-
collector,
|
|
434
|
-
);
|
|
435
|
-
const booleanJson = await convertStandardSchemaToJsonSchema(
|
|
436
|
-
booleanSchema,
|
|
437
|
-
collector,
|
|
438
|
-
);
|
|
439
|
-
|
|
440
|
-
expect(stringJson).toMatchObject({ type: 'string' });
|
|
441
|
-
expect(numberJson).toMatchObject({ type: 'number' });
|
|
442
|
-
expect(booleanJson).toMatchObject({ type: 'boolean' });
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
it('should handle schema with discriminated unions', async () => {
|
|
446
|
-
const catSchema = z.object({
|
|
447
|
-
type: z.literal('cat'),
|
|
448
|
-
meows: z.boolean(),
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
const dogSchema = z.object({
|
|
452
|
-
type: z.literal('dog'),
|
|
453
|
-
barks: z.boolean(),
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
const petSchema = z.discriminatedUnion('type', [catSchema, dogSchema]);
|
|
457
|
-
|
|
458
|
-
const collector = createComponentCollector();
|
|
459
|
-
const jsonSchema = await convertStandardSchemaToJsonSchema(
|
|
460
|
-
petSchema,
|
|
461
|
-
collector,
|
|
462
|
-
);
|
|
463
|
-
|
|
464
|
-
// Should have oneOf or anyOf for discriminated union
|
|
465
|
-
expect(
|
|
466
|
-
jsonSchema.anyOf || jsonSchema.oneOf || jsonSchema.discriminator,
|
|
467
|
-
).toBeDefined();
|
|
468
|
-
});
|
|
469
|
-
|
|
470
|
-
it('should preserve $ref as-is when not matching $defs pattern', async () => {
|
|
471
|
-
// Test the extractAndConvertDefs function with a schema that has
|
|
472
|
-
// $ref not starting with #/$defs/
|
|
473
|
-
const schema = z.object({
|
|
474
|
-
name: z.string(),
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
// Without collector, refs should be preserved as-is
|
|
478
|
-
const jsonSchema = await convertStandardSchemaToJsonSchema(schema);
|
|
479
|
-
|
|
480
|
-
expect(jsonSchema).toHaveProperty('type', 'object');
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
it('should convert $defs references to component references with collector', async () => {
|
|
484
|
-
// Use lazy schema to create a recursive structure that produces $defs
|
|
485
|
-
const nodeSchema: z.ZodTypeAny = z.lazy(() =>
|
|
486
|
-
z.object({
|
|
487
|
-
value: z.string(),
|
|
488
|
-
children: z.array(nodeSchema).optional(),
|
|
489
|
-
}),
|
|
490
|
-
);
|
|
491
|
-
|
|
492
|
-
const collector = createComponentCollector();
|
|
493
|
-
const jsonSchema = await convertStandardSchemaToJsonSchema(
|
|
494
|
-
nodeSchema,
|
|
495
|
-
collector,
|
|
496
|
-
);
|
|
497
|
-
|
|
498
|
-
// $defs should be processed and potentially converted
|
|
499
|
-
expect(jsonSchema).not.toHaveProperty('$defs');
|
|
500
|
-
});
|
|
501
|
-
});
|
|
502
|
-
});
|