@digitaldefiance/node-express-suite 3.11.32 → 3.12.1
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 +874 -8
- package/package.json +5 -5
- package/src/controllers/openapi.d.ts +67 -0
- package/src/controllers/openapi.d.ts.map +1 -0
- package/src/controllers/openapi.js +89 -0
- package/src/controllers/openapi.js.map +1 -0
- package/src/decorators/auth.d.ts +128 -0
- package/src/decorators/auth.d.ts.map +1 -0
- package/src/decorators/auth.js +230 -0
- package/src/decorators/auth.js.map +1 -0
- package/src/decorators/base-controller.d.ts +144 -6
- package/src/decorators/base-controller.d.ts.map +1 -1
- package/src/decorators/base-controller.js +487 -31
- package/src/decorators/base-controller.js.map +1 -1
- package/src/decorators/controller.d.ts +63 -7
- package/src/decorators/controller.d.ts.map +1 -1
- package/src/decorators/controller.js +70 -39
- package/src/decorators/controller.js.map +1 -1
- package/src/decorators/handler-args.d.ts +68 -0
- package/src/decorators/handler-args.d.ts.map +1 -0
- package/src/decorators/handler-args.js +83 -0
- package/src/decorators/handler-args.js.map +1 -0
- package/src/decorators/http-methods.d.ts +143 -0
- package/src/decorators/http-methods.d.ts.map +1 -0
- package/src/decorators/http-methods.js +265 -0
- package/src/decorators/http-methods.js.map +1 -0
- package/src/decorators/index.d.ts +22 -0
- package/src/decorators/index.d.ts.map +1 -1
- package/src/decorators/index.js +56 -0
- package/src/decorators/index.js.map +1 -1
- package/src/decorators/lifecycle.d.ts +248 -0
- package/src/decorators/lifecycle.d.ts.map +1 -0
- package/src/decorators/lifecycle.js +301 -0
- package/src/decorators/lifecycle.js.map +1 -0
- package/src/decorators/metadata-collector.d.ts +175 -0
- package/src/decorators/metadata-collector.d.ts.map +1 -0
- package/src/decorators/metadata-collector.js +272 -0
- package/src/decorators/metadata-collector.js.map +1 -0
- package/src/decorators/metadata-keys.d.ts +121 -0
- package/src/decorators/metadata-keys.d.ts.map +1 -0
- package/src/decorators/metadata-keys.js +116 -0
- package/src/decorators/metadata-keys.js.map +1 -0
- package/src/decorators/middleware.d.ts +181 -0
- package/src/decorators/middleware.d.ts.map +1 -0
- package/src/decorators/middleware.js +400 -0
- package/src/decorators/middleware.js.map +1 -0
- package/src/decorators/openapi-params.d.ts +192 -0
- package/src/decorators/openapi-params.d.ts.map +1 -0
- package/src/decorators/openapi-params.js +332 -0
- package/src/decorators/openapi-params.js.map +1 -0
- package/src/decorators/openapi.d.ts +201 -0
- package/src/decorators/openapi.d.ts.map +1 -0
- package/src/decorators/openapi.js +334 -0
- package/src/decorators/openapi.js.map +1 -0
- package/src/decorators/params.d.ts +217 -0
- package/src/decorators/params.d.ts.map +1 -0
- package/src/decorators/params.js +323 -0
- package/src/decorators/params.js.map +1 -0
- package/src/decorators/response.d.ts +200 -0
- package/src/decorators/response.d.ts.map +1 -0
- package/src/decorators/response.js +315 -0
- package/src/decorators/response.js.map +1 -0
- package/src/decorators/schema.d.ts +99 -0
- package/src/decorators/schema.d.ts.map +1 -0
- package/src/decorators/schema.js +329 -0
- package/src/decorators/schema.js.map +1 -0
- package/src/decorators/transaction.d.ts +69 -0
- package/src/decorators/transaction.d.ts.map +1 -0
- package/src/decorators/transaction.js +80 -0
- package/src/decorators/transaction.js.map +1 -0
- package/src/decorators/validation.d.ts +188 -0
- package/src/decorators/validation.d.ts.map +1 -0
- package/src/decorators/validation.js +269 -0
- package/src/decorators/validation.js.map +1 -0
- package/src/decorators/zod-validation.d.ts +164 -4
- package/src/decorators/zod-validation.d.ts.map +1 -1
- package/src/decorators/zod-validation.js +692 -13
- package/src/decorators/zod-validation.js.map +1 -1
- package/src/index.d.ts +1 -0
- package/src/index.d.ts.map +1 -1
- package/src/index.js +1 -0
- package/src/index.js.map +1 -1
- package/src/interfaces/openApi/decoratorOptions.d.ts +760 -0
- package/src/interfaces/openApi/decoratorOptions.d.ts.map +1 -0
- package/src/interfaces/openApi/decoratorOptions.js +734 -0
- package/src/interfaces/openApi/decoratorOptions.js.map +1 -0
- package/src/interfaces/openApi/index.d.ts +1 -0
- package/src/interfaces/openApi/index.d.ts.map +1 -1
- package/src/interfaces/openApi/index.js +23 -0
- package/src/interfaces/openApi/index.js.map +1 -1
- package/src/interfaces/openApi/parameter.d.ts +2 -0
- package/src/interfaces/openApi/parameter.d.ts.map +1 -1
- package/src/interfaces/openApi/parameter.js +3 -1
- package/src/interfaces/openApi/parameter.js.map +1 -1
- package/src/interfaces/openApi/parameterSchema.d.ts +2 -0
- package/src/interfaces/openApi/parameterSchema.d.ts.map +1 -1
- package/src/interfaces/openApi/parameterSchema.js +3 -0
- package/src/interfaces/openApi/parameterSchema.js.map +1 -1
- package/src/openapi/builder.d.ts +249 -0
- package/src/openapi/builder.d.ts.map +1 -0
- package/src/openapi/builder.js +352 -0
- package/src/openapi/builder.js.map +1 -0
- package/src/openapi/controller.d.ts +153 -0
- package/src/openapi/controller.d.ts.map +1 -0
- package/src/openapi/controller.js +331 -0
- package/src/openapi/controller.js.map +1 -0
- package/src/openapi/index.d.ts +12 -0
- package/src/openapi/index.d.ts.map +1 -0
- package/src/openapi/index.js +20 -0
- package/src/openapi/index.js.map +1 -0
- package/src/openapi/markdown-generator.d.ts +52 -0
- package/src/openapi/markdown-generator.d.ts.map +1 -0
- package/src/openapi/markdown-generator.js +569 -0
- package/src/openapi/markdown-generator.js.map +1 -0
- package/src/openapi/middleware/index.d.ts +9 -0
- package/src/openapi/middleware/index.d.ts.map +1 -0
- package/src/openapi/middleware/index.js +15 -0
- package/src/openapi/middleware/index.js.map +1 -0
- package/src/openapi/middleware/redoc.d.ts +314 -0
- package/src/openapi/middleware/redoc.d.ts.map +1 -0
- package/src/openapi/middleware/redoc.js +181 -0
- package/src/openapi/middleware/redoc.js.map +1 -0
- package/src/openapi/middleware/swagger-ui.d.ts +123 -0
- package/src/openapi/middleware/swagger-ui.d.ts.map +1 -0
- package/src/openapi/middleware/swagger-ui.js +227 -0
- package/src/openapi/middleware/swagger-ui.js.map +1 -0
- package/src/openapi/schemas.d.ts +170 -0
- package/src/openapi/schemas.d.ts.map +1 -0
- package/src/openapi/schemas.js +340 -0
- package/src/openapi/schemas.js.map +1 -0
- package/src/registry/controller-registry.d.ts +78 -0
- package/src/registry/controller-registry.d.ts.map +1 -0
- package/src/registry/controller-registry.js +86 -0
- package/src/registry/controller-registry.js.map +1 -0
- package/src/registry/index.d.ts +2 -0
- package/src/registry/index.d.ts.map +1 -1
- package/src/registry/index.js +3 -1
- package/src/registry/index.js.map +1 -1
- package/src/routers/api.d.ts +2 -1
- package/src/routers/api.d.ts.map +1 -1
- package/src/routers/api.js +7 -1
- package/src/routers/api.js.map +1 -1
- package/src/types.d.ts +1 -0
- package/src/types.d.ts.map +1 -1
- package/src/types.js +1 -0
- package/src/types.js.map +1 -1
|
@@ -1,14 +1,626 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* @fileoverview Zod schema validation decorator.
|
|
4
|
-
* Converts Zod schemas to express-validator chains.
|
|
3
|
+
* @fileoverview Zod schema validation decorator and OpenAPI conversion utilities.
|
|
4
|
+
* Converts Zod schemas to express-validator chains and OpenAPI schemas.
|
|
5
5
|
* @module decorators/zod-validation
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.extractZodMetadata = extractZodMetadata;
|
|
9
|
+
exports.zodToOpenAPI = zodToOpenAPI;
|
|
10
|
+
exports.getZodDescription = getZodDescription;
|
|
11
|
+
exports.getZodExample = getZodExample;
|
|
12
|
+
exports.getZodExamples = getZodExamples;
|
|
13
|
+
exports.getZodTitle = getZodTitle;
|
|
14
|
+
exports.isZodDeprecated = isZodDeprecated;
|
|
8
15
|
exports.zodToExpressValidator = zodToExpressValidator;
|
|
9
16
|
exports.ZodValidate = ZodValidate;
|
|
10
17
|
const express_validator_1 = require("express-validator");
|
|
11
18
|
const zod_1 = require("zod");
|
|
19
|
+
/**
|
|
20
|
+
* Extracts OpenAPI metadata from a Zod schema.
|
|
21
|
+
* Checks for description via .describe() and custom metadata via ._def.
|
|
22
|
+
*
|
|
23
|
+
* @param schema - The Zod schema to extract metadata from
|
|
24
|
+
* @returns The extracted metadata object
|
|
25
|
+
*/
|
|
26
|
+
function extractZodMetadata(schema) {
|
|
27
|
+
const metadata = {};
|
|
28
|
+
// Extract description from .describe()
|
|
29
|
+
if (schema._def.description) {
|
|
30
|
+
metadata.description = schema._def.description;
|
|
31
|
+
}
|
|
32
|
+
// Check for custom openapi metadata stored in _def
|
|
33
|
+
const def = schema._def;
|
|
34
|
+
if (def.openapi && typeof def.openapi === 'object') {
|
|
35
|
+
const openapi = def.openapi;
|
|
36
|
+
if (openapi.description)
|
|
37
|
+
metadata.description = openapi.description;
|
|
38
|
+
if (openapi.example !== undefined)
|
|
39
|
+
metadata.example = openapi.example;
|
|
40
|
+
if (openapi.examples)
|
|
41
|
+
metadata.examples = openapi.examples;
|
|
42
|
+
if (openapi.title)
|
|
43
|
+
metadata.title = openapi.title;
|
|
44
|
+
if (openapi.deprecated !== undefined)
|
|
45
|
+
metadata.deprecated = openapi.deprecated;
|
|
46
|
+
if (openapi.readOnly !== undefined)
|
|
47
|
+
metadata.readOnly = openapi.readOnly;
|
|
48
|
+
if (openapi.writeOnly !== undefined)
|
|
49
|
+
metadata.writeOnly = openapi.writeOnly;
|
|
50
|
+
if (openapi.format)
|
|
51
|
+
metadata.format = openapi.format;
|
|
52
|
+
if (openapi.default !== undefined)
|
|
53
|
+
metadata.default = openapi.default;
|
|
54
|
+
}
|
|
55
|
+
return metadata;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Applies extracted metadata to an OpenAPI schema.
|
|
59
|
+
*
|
|
60
|
+
* @param schema - The OpenAPI schema to apply metadata to
|
|
61
|
+
* @param metadata - The metadata to apply
|
|
62
|
+
* @returns The schema with metadata applied
|
|
63
|
+
*/
|
|
64
|
+
function applyMetadata(schema, metadata) {
|
|
65
|
+
const result = { ...schema };
|
|
66
|
+
if (metadata.description) {
|
|
67
|
+
Object.assign(result, { description: metadata.description });
|
|
68
|
+
}
|
|
69
|
+
if (metadata.example !== undefined) {
|
|
70
|
+
Object.assign(result, { example: metadata.example });
|
|
71
|
+
}
|
|
72
|
+
if (metadata.title) {
|
|
73
|
+
Object.assign(result, { title: metadata.title });
|
|
74
|
+
}
|
|
75
|
+
if (metadata.deprecated !== undefined) {
|
|
76
|
+
Object.assign(result, { deprecated: metadata.deprecated });
|
|
77
|
+
}
|
|
78
|
+
if (metadata.readOnly !== undefined) {
|
|
79
|
+
Object.assign(result, { readOnly: metadata.readOnly });
|
|
80
|
+
}
|
|
81
|
+
if (metadata.writeOnly !== undefined) {
|
|
82
|
+
Object.assign(result, { writeOnly: metadata.writeOnly });
|
|
83
|
+
}
|
|
84
|
+
if (metadata.format && 'type' in result && result.type === 'string') {
|
|
85
|
+
Object.assign(result, { format: metadata.format });
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Converts a Zod schema to an OpenAPI schema.
|
|
91
|
+
* Supports nested objects, arrays, unions, enums, and various Zod types.
|
|
92
|
+
* Extracts descriptions and examples from Zod schema metadata.
|
|
93
|
+
*
|
|
94
|
+
* @param schema - The Zod schema to convert
|
|
95
|
+
* @returns The OpenAPI schema representation
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```typescript
|
|
99
|
+
* const UserSchema = z.object({
|
|
100
|
+
* id: z.string().uuid().describe('Unique user identifier'),
|
|
101
|
+
* name: z.string().min(1).max(100).describe('User display name'),
|
|
102
|
+
* email: z.string().email().describe('User email address'),
|
|
103
|
+
* age: z.number().int().positive().optional().describe('User age in years'),
|
|
104
|
+
* roles: z.array(z.enum(['admin', 'user', 'guest'])).describe('User roles'),
|
|
105
|
+
* });
|
|
106
|
+
*
|
|
107
|
+
* const openApiSchema = zodToOpenAPI(UserSchema);
|
|
108
|
+
* // Returns:
|
|
109
|
+
* // {
|
|
110
|
+
* // type: 'object',
|
|
111
|
+
* // properties: {
|
|
112
|
+
* // id: { type: 'string', format: 'uuid', description: 'Unique user identifier' },
|
|
113
|
+
* // name: { type: 'string', minLength: 1, maxLength: 100, description: 'User display name' },
|
|
114
|
+
* // email: { type: 'string', format: 'email', description: 'User email address' },
|
|
115
|
+
* // age: { type: 'integer', minimum: 1, description: 'User age in years' },
|
|
116
|
+
* // roles: { type: 'array', items: { type: 'string', enum: ['admin', 'user', 'guest'] }, description: 'User roles' },
|
|
117
|
+
* // },
|
|
118
|
+
* // required: ['id', 'name', 'email', 'roles'],
|
|
119
|
+
* // }
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
function zodToOpenAPI(schema) {
|
|
123
|
+
return convertZodType(schema);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Internal function to convert a Zod type to OpenAPI schema.
|
|
127
|
+
* Handles all Zod type variants recursively.
|
|
128
|
+
* Extracts and applies metadata (description, example, etc.) from Zod schemas.
|
|
129
|
+
*/
|
|
130
|
+
function convertZodType(zodType) {
|
|
131
|
+
// Extract metadata from the schema
|
|
132
|
+
const metadata = extractZodMetadata(zodType);
|
|
133
|
+
// Handle ZodOptional - unwrap and mark as not required
|
|
134
|
+
if (zodType instanceof zod_1.z.ZodOptional) {
|
|
135
|
+
const innerSchema = convertZodType(zodType._def.innerType);
|
|
136
|
+
return applyMetadata(innerSchema, metadata);
|
|
137
|
+
}
|
|
138
|
+
// Handle ZodNullable - add nullable: true
|
|
139
|
+
if (zodType instanceof zod_1.z.ZodNullable) {
|
|
140
|
+
const innerSchema = convertZodType(zodType._def.innerType);
|
|
141
|
+
return applyMetadata({ ...innerSchema, nullable: true }, metadata);
|
|
142
|
+
}
|
|
143
|
+
// Handle ZodDefault - unwrap and add default value
|
|
144
|
+
if (zodType instanceof zod_1.z.ZodDefault) {
|
|
145
|
+
const innerSchema = convertZodType(zodType._def.innerType);
|
|
146
|
+
const defaultValue = zodType._def.defaultValue();
|
|
147
|
+
return applyMetadata({ ...innerSchema, default: defaultValue }, metadata);
|
|
148
|
+
}
|
|
149
|
+
// Handle ZodEffects (refinements, transforms) - unwrap
|
|
150
|
+
if (zodType instanceof zod_1.z.ZodEffects) {
|
|
151
|
+
const innerSchema = convertZodType(zodType._def.schema);
|
|
152
|
+
return applyMetadata(innerSchema, metadata);
|
|
153
|
+
}
|
|
154
|
+
// Handle ZodCatch - unwrap
|
|
155
|
+
if (zodType instanceof zod_1.z.ZodCatch) {
|
|
156
|
+
const innerSchema = convertZodType(zodType._def.innerType);
|
|
157
|
+
return applyMetadata(innerSchema, metadata);
|
|
158
|
+
}
|
|
159
|
+
// Handle ZodBranded - unwrap
|
|
160
|
+
if (zodType instanceof zod_1.z.ZodBranded) {
|
|
161
|
+
const innerSchema = convertZodType(zodType._def.type);
|
|
162
|
+
return applyMetadata(innerSchema, metadata);
|
|
163
|
+
}
|
|
164
|
+
// Handle ZodReadonly - unwrap and mark as readOnly
|
|
165
|
+
if (zodType instanceof zod_1.z.ZodReadonly) {
|
|
166
|
+
const innerSchema = convertZodType(zodType._def.innerType);
|
|
167
|
+
return applyMetadata({ ...innerSchema, readOnly: true }, metadata);
|
|
168
|
+
}
|
|
169
|
+
// Handle ZodString
|
|
170
|
+
if (zodType instanceof zod_1.z.ZodString) {
|
|
171
|
+
return applyMetadata(convertZodString(zodType), metadata);
|
|
172
|
+
}
|
|
173
|
+
// Handle ZodNumber
|
|
174
|
+
if (zodType instanceof zod_1.z.ZodNumber) {
|
|
175
|
+
return applyMetadata(convertZodNumber(zodType), metadata);
|
|
176
|
+
}
|
|
177
|
+
// Handle ZodBigInt
|
|
178
|
+
if (zodType instanceof zod_1.z.ZodBigInt) {
|
|
179
|
+
return applyMetadata({ type: 'integer', format: 'int64' }, metadata);
|
|
180
|
+
}
|
|
181
|
+
// Handle ZodBoolean
|
|
182
|
+
if (zodType instanceof zod_1.z.ZodBoolean) {
|
|
183
|
+
return applyMetadata({ type: 'boolean' }, metadata);
|
|
184
|
+
}
|
|
185
|
+
// Handle ZodDate
|
|
186
|
+
if (zodType instanceof zod_1.z.ZodDate) {
|
|
187
|
+
return applyMetadata({ type: 'string', format: 'date-time' }, metadata);
|
|
188
|
+
}
|
|
189
|
+
// Handle ZodEnum
|
|
190
|
+
if (zodType instanceof zod_1.z.ZodEnum) {
|
|
191
|
+
return applyMetadata({
|
|
192
|
+
type: 'string',
|
|
193
|
+
enum: zodType._def.values,
|
|
194
|
+
}, metadata);
|
|
195
|
+
}
|
|
196
|
+
// Handle ZodNativeEnum
|
|
197
|
+
if (zodType instanceof zod_1.z.ZodNativeEnum) {
|
|
198
|
+
const enumValues = Object.values(zodType._def.values);
|
|
199
|
+
// Filter out numeric keys for numeric enums
|
|
200
|
+
const stringValues = enumValues.filter((v) => typeof v === 'string');
|
|
201
|
+
if (stringValues.length > 0) {
|
|
202
|
+
return applyMetadata({ type: 'string', enum: stringValues }, metadata);
|
|
203
|
+
}
|
|
204
|
+
// Numeric enum
|
|
205
|
+
return applyMetadata({
|
|
206
|
+
type: 'integer',
|
|
207
|
+
enum: enumValues
|
|
208
|
+
.filter((v) => typeof v === 'number')
|
|
209
|
+
.map(String),
|
|
210
|
+
}, metadata);
|
|
211
|
+
}
|
|
212
|
+
// Handle ZodLiteral
|
|
213
|
+
if (zodType instanceof zod_1.z.ZodLiteral) {
|
|
214
|
+
const value = zodType._def.value;
|
|
215
|
+
if (typeof value === 'string') {
|
|
216
|
+
return applyMetadata({ type: 'string', enum: [value] }, metadata);
|
|
217
|
+
}
|
|
218
|
+
if (typeof value === 'number') {
|
|
219
|
+
return applyMetadata({ type: 'number', enum: [String(value)] }, metadata);
|
|
220
|
+
}
|
|
221
|
+
if (typeof value === 'boolean') {
|
|
222
|
+
return applyMetadata({ type: 'boolean' }, metadata);
|
|
223
|
+
}
|
|
224
|
+
return applyMetadata({ type: 'string' }, metadata);
|
|
225
|
+
}
|
|
226
|
+
// Handle ZodArray
|
|
227
|
+
if (zodType instanceof zod_1.z.ZodArray) {
|
|
228
|
+
return applyMetadata(convertZodArray(zodType), metadata);
|
|
229
|
+
}
|
|
230
|
+
// Handle ZodObject
|
|
231
|
+
if (zodType instanceof zod_1.z.ZodObject) {
|
|
232
|
+
return applyMetadata(convertZodObject(zodType), metadata);
|
|
233
|
+
}
|
|
234
|
+
// Handle ZodUnion
|
|
235
|
+
if (zodType instanceof zod_1.z.ZodUnion) {
|
|
236
|
+
const options = zodType._def.options;
|
|
237
|
+
return applyMetadata({
|
|
238
|
+
oneOf: options.map((opt) => convertZodType(opt)),
|
|
239
|
+
}, metadata);
|
|
240
|
+
}
|
|
241
|
+
// Handle ZodDiscriminatedUnion
|
|
242
|
+
if (zodType instanceof zod_1.z.ZodDiscriminatedUnion) {
|
|
243
|
+
const discriminator = zodType._def.discriminator;
|
|
244
|
+
const options = Array.from(zodType._def.optionsMap.values());
|
|
245
|
+
const result = {
|
|
246
|
+
oneOf: options.map((opt) => convertZodType(opt)),
|
|
247
|
+
discriminator: {
|
|
248
|
+
propertyName: discriminator,
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
return applyMetadata(result, metadata);
|
|
252
|
+
}
|
|
253
|
+
// Handle ZodIntersection
|
|
254
|
+
if (zodType instanceof zod_1.z.ZodIntersection) {
|
|
255
|
+
const left = convertZodType(zodType._def.left);
|
|
256
|
+
const right = convertZodType(zodType._def.right);
|
|
257
|
+
// Merge the two schemas if they're both objects
|
|
258
|
+
if (isObjectSchema(left) && isObjectSchema(right)) {
|
|
259
|
+
return applyMetadata({
|
|
260
|
+
type: 'object',
|
|
261
|
+
properties: { ...left.properties, ...right.properties },
|
|
262
|
+
required: [...(left.required ?? []), ...(right.required ?? [])],
|
|
263
|
+
}, metadata);
|
|
264
|
+
}
|
|
265
|
+
return applyMetadata({
|
|
266
|
+
anyOf: [left, right],
|
|
267
|
+
}, metadata);
|
|
268
|
+
}
|
|
269
|
+
// Handle ZodRecord
|
|
270
|
+
if (zodType instanceof zod_1.z.ZodRecord) {
|
|
271
|
+
const valueSchema = convertZodType(zodType._def.valueType);
|
|
272
|
+
return applyMetadata({
|
|
273
|
+
type: 'object',
|
|
274
|
+
additionalProperties: valueSchema,
|
|
275
|
+
}, metadata);
|
|
276
|
+
}
|
|
277
|
+
// Handle ZodMap - convert to object with additionalProperties
|
|
278
|
+
if (zodType instanceof zod_1.z.ZodMap) {
|
|
279
|
+
const valueSchema = convertZodType(zodType._def.valueType);
|
|
280
|
+
return applyMetadata({
|
|
281
|
+
type: 'object',
|
|
282
|
+
additionalProperties: valueSchema,
|
|
283
|
+
}, metadata);
|
|
284
|
+
}
|
|
285
|
+
// Handle ZodSet - convert to array with uniqueItems
|
|
286
|
+
if (zodType instanceof zod_1.z.ZodSet) {
|
|
287
|
+
const valueSchema = convertZodType(zodType._def.valueType);
|
|
288
|
+
return applyMetadata({
|
|
289
|
+
type: 'array',
|
|
290
|
+
items: valueSchema,
|
|
291
|
+
uniqueItems: true,
|
|
292
|
+
}, metadata);
|
|
293
|
+
}
|
|
294
|
+
// Handle ZodTuple
|
|
295
|
+
if (zodType instanceof zod_1.z.ZodTuple) {
|
|
296
|
+
const items = zodType._def.items;
|
|
297
|
+
return applyMetadata({
|
|
298
|
+
type: 'array',
|
|
299
|
+
items: items.length === 1
|
|
300
|
+
? convertZodType(items[0])
|
|
301
|
+
: { oneOf: items.map((item) => convertZodType(item)) },
|
|
302
|
+
minItems: items.length,
|
|
303
|
+
maxItems: items.length,
|
|
304
|
+
}, metadata);
|
|
305
|
+
}
|
|
306
|
+
// Handle ZodAny, ZodUnknown
|
|
307
|
+
if (zodType instanceof zod_1.z.ZodAny || zodType instanceof zod_1.z.ZodUnknown) {
|
|
308
|
+
return applyMetadata({ type: 'object' }, metadata);
|
|
309
|
+
}
|
|
310
|
+
// Handle ZodVoid, ZodUndefined, ZodNull, ZodNever
|
|
311
|
+
if (zodType instanceof zod_1.z.ZodVoid || zodType instanceof zod_1.z.ZodUndefined) {
|
|
312
|
+
return applyMetadata({ type: 'string' }, metadata);
|
|
313
|
+
}
|
|
314
|
+
if (zodType instanceof zod_1.z.ZodNull) {
|
|
315
|
+
return applyMetadata({ type: 'string', nullable: true }, metadata);
|
|
316
|
+
}
|
|
317
|
+
if (zodType instanceof zod_1.z.ZodNever) {
|
|
318
|
+
// ZodNever represents an impossible type - return empty schema
|
|
319
|
+
return applyMetadata({ type: 'string' }, metadata);
|
|
320
|
+
}
|
|
321
|
+
// Handle ZodNaN - represents NaN value
|
|
322
|
+
if (zodType instanceof zod_1.z.ZodNaN) {
|
|
323
|
+
return applyMetadata({ type: 'number' }, metadata);
|
|
324
|
+
}
|
|
325
|
+
// Handle ZodLazy - evaluate and convert
|
|
326
|
+
if (zodType instanceof zod_1.z.ZodLazy) {
|
|
327
|
+
const innerSchema = convertZodType(zodType._def.getter());
|
|
328
|
+
return applyMetadata(innerSchema, metadata);
|
|
329
|
+
}
|
|
330
|
+
// Handle ZodPipeline
|
|
331
|
+
if (zodType instanceof zod_1.z.ZodPipeline) {
|
|
332
|
+
const innerSchema = convertZodType(zodType._def.out);
|
|
333
|
+
return applyMetadata(innerSchema, metadata);
|
|
334
|
+
}
|
|
335
|
+
// Handle ZodPromise - unwrap the inner type
|
|
336
|
+
if (zodType instanceof zod_1.z.ZodPromise) {
|
|
337
|
+
const innerSchema = convertZodType(zodType._def.type);
|
|
338
|
+
return applyMetadata(innerSchema, metadata);
|
|
339
|
+
}
|
|
340
|
+
// Handle ZodFunction - not directly representable in OpenAPI
|
|
341
|
+
if (zodType instanceof zod_1.z.ZodFunction) {
|
|
342
|
+
return applyMetadata({ type: 'object' }, metadata);
|
|
343
|
+
}
|
|
344
|
+
// Handle ZodSymbol - not directly representable in OpenAPI
|
|
345
|
+
if (zodType instanceof zod_1.z.ZodSymbol) {
|
|
346
|
+
return applyMetadata({ type: 'string' }, metadata);
|
|
347
|
+
}
|
|
348
|
+
// Default fallback
|
|
349
|
+
return applyMetadata({ type: 'string' }, metadata);
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Converts a ZodString to OpenAPI schema with all string validations.
|
|
353
|
+
*/
|
|
354
|
+
function convertZodString(zodString) {
|
|
355
|
+
const schema = { type: 'string' };
|
|
356
|
+
if (zodString._def.checks) {
|
|
357
|
+
for (const check of zodString._def.checks) {
|
|
358
|
+
switch (check.kind) {
|
|
359
|
+
case 'min':
|
|
360
|
+
schema.minLength = check.value;
|
|
361
|
+
break;
|
|
362
|
+
case 'max':
|
|
363
|
+
schema.maxLength = check.value;
|
|
364
|
+
break;
|
|
365
|
+
case 'length':
|
|
366
|
+
schema.minLength = check.value;
|
|
367
|
+
schema.maxLength = check.value;
|
|
368
|
+
break;
|
|
369
|
+
case 'email':
|
|
370
|
+
schema.format = 'email';
|
|
371
|
+
break;
|
|
372
|
+
case 'url':
|
|
373
|
+
schema.format = 'uri';
|
|
374
|
+
break;
|
|
375
|
+
case 'uuid':
|
|
376
|
+
schema.format = 'uuid';
|
|
377
|
+
break;
|
|
378
|
+
case 'cuid':
|
|
379
|
+
case 'cuid2':
|
|
380
|
+
schema.format = 'cuid';
|
|
381
|
+
break;
|
|
382
|
+
case 'ulid':
|
|
383
|
+
schema.format = 'ulid';
|
|
384
|
+
break;
|
|
385
|
+
case 'datetime':
|
|
386
|
+
schema.format = 'date-time';
|
|
387
|
+
break;
|
|
388
|
+
case 'date':
|
|
389
|
+
schema.format = 'date';
|
|
390
|
+
break;
|
|
391
|
+
case 'time':
|
|
392
|
+
schema.format = 'time';
|
|
393
|
+
break;
|
|
394
|
+
case 'duration':
|
|
395
|
+
schema.format = 'duration';
|
|
396
|
+
break;
|
|
397
|
+
case 'ip':
|
|
398
|
+
schema.format = 'ip';
|
|
399
|
+
break;
|
|
400
|
+
case 'emoji':
|
|
401
|
+
// No direct OpenAPI equivalent
|
|
402
|
+
break;
|
|
403
|
+
case 'base64':
|
|
404
|
+
schema.format = 'byte';
|
|
405
|
+
break;
|
|
406
|
+
case 'nanoid':
|
|
407
|
+
schema.format = 'nanoid';
|
|
408
|
+
break;
|
|
409
|
+
case 'regex':
|
|
410
|
+
if (check.regex) {
|
|
411
|
+
schema.pattern = check.regex.source;
|
|
412
|
+
}
|
|
413
|
+
break;
|
|
414
|
+
case 'startsWith':
|
|
415
|
+
case 'endsWith':
|
|
416
|
+
case 'includes':
|
|
417
|
+
case 'toLowerCase':
|
|
418
|
+
case 'toUpperCase':
|
|
419
|
+
case 'trim':
|
|
420
|
+
// These don't have direct OpenAPI equivalents
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return schema;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Converts a ZodNumber to OpenAPI schema with all number validations.
|
|
429
|
+
*/
|
|
430
|
+
function convertZodNumber(zodNumber) {
|
|
431
|
+
const schema = { type: 'number' };
|
|
432
|
+
if (zodNumber._def.checks) {
|
|
433
|
+
for (const check of zodNumber._def.checks) {
|
|
434
|
+
switch (check.kind) {
|
|
435
|
+
case 'int':
|
|
436
|
+
schema.type = 'integer';
|
|
437
|
+
break;
|
|
438
|
+
case 'min':
|
|
439
|
+
schema.minimum = check.value;
|
|
440
|
+
if (check.inclusive === false) {
|
|
441
|
+
schema.exclusiveMinimum = true;
|
|
442
|
+
}
|
|
443
|
+
break;
|
|
444
|
+
case 'max':
|
|
445
|
+
schema.maximum = check.value;
|
|
446
|
+
if (check.inclusive === false) {
|
|
447
|
+
schema.exclusiveMaximum = true;
|
|
448
|
+
}
|
|
449
|
+
break;
|
|
450
|
+
case 'multipleOf':
|
|
451
|
+
schema.multipleOf = check.value;
|
|
452
|
+
break;
|
|
453
|
+
case 'finite':
|
|
454
|
+
case 'safe':
|
|
455
|
+
// These don't have direct OpenAPI equivalents
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return schema;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Converts a ZodArray to OpenAPI array schema.
|
|
464
|
+
*/
|
|
465
|
+
function convertZodArray(zodArray) {
|
|
466
|
+
const itemSchema = convertZodType(zodArray._def.type);
|
|
467
|
+
const schema = {
|
|
468
|
+
type: 'array',
|
|
469
|
+
items: itemSchema,
|
|
470
|
+
};
|
|
471
|
+
if (zodArray._def.minLength !== null &&
|
|
472
|
+
zodArray._def.minLength !== undefined) {
|
|
473
|
+
schema.minItems = zodArray._def.minLength.value;
|
|
474
|
+
}
|
|
475
|
+
if (zodArray._def.maxLength !== null &&
|
|
476
|
+
zodArray._def.maxLength !== undefined) {
|
|
477
|
+
schema.maxItems = zodArray._def.maxLength.value;
|
|
478
|
+
}
|
|
479
|
+
if (zodArray._def.exactLength !== null &&
|
|
480
|
+
zodArray._def.exactLength !== undefined) {
|
|
481
|
+
schema.minItems = zodArray._def.exactLength.value;
|
|
482
|
+
schema.maxItems = zodArray._def.exactLength.value;
|
|
483
|
+
}
|
|
484
|
+
return schema;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Converts a ZodObject to OpenAPI object schema.
|
|
488
|
+
*/
|
|
489
|
+
function convertZodObject(zodObject) {
|
|
490
|
+
const properties = {};
|
|
491
|
+
const required = [];
|
|
492
|
+
const shape = zodObject._def.shape();
|
|
493
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
494
|
+
const zodValue = value;
|
|
495
|
+
properties[key] = convertZodType(zodValue);
|
|
496
|
+
// Check if the field is required (not optional)
|
|
497
|
+
if (!isOptionalType(zodValue)) {
|
|
498
|
+
required.push(key);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
const schema = {
|
|
502
|
+
type: 'object',
|
|
503
|
+
properties,
|
|
504
|
+
};
|
|
505
|
+
if (required.length > 0) {
|
|
506
|
+
schema.required = required;
|
|
507
|
+
}
|
|
508
|
+
return schema;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Checks if a Zod type is optional.
|
|
512
|
+
*/
|
|
513
|
+
function isOptionalType(zodType) {
|
|
514
|
+
if (zodType instanceof zod_1.z.ZodOptional) {
|
|
515
|
+
return true;
|
|
516
|
+
}
|
|
517
|
+
if (zodType instanceof zod_1.z.ZodDefault) {
|
|
518
|
+
return true;
|
|
519
|
+
}
|
|
520
|
+
if (zodType instanceof zod_1.z.ZodNullable) {
|
|
521
|
+
return isOptionalType(zodType._def.innerType);
|
|
522
|
+
}
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Type guard to check if a schema is an object schema.
|
|
527
|
+
*/
|
|
528
|
+
function isObjectSchema(schema) {
|
|
529
|
+
return 'type' in schema && schema.type === 'object' && 'properties' in schema;
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Extracts description from a Zod schema if available.
|
|
533
|
+
*
|
|
534
|
+
* @param schema - The Zod schema
|
|
535
|
+
* @returns The description or undefined
|
|
536
|
+
*/
|
|
537
|
+
function getZodDescription(schema) {
|
|
538
|
+
// First check the direct description
|
|
539
|
+
if (schema._def.description) {
|
|
540
|
+
return schema._def.description;
|
|
541
|
+
}
|
|
542
|
+
// Check for custom openapi metadata
|
|
543
|
+
const def = schema._def;
|
|
544
|
+
if (def.openapi && typeof def.openapi === 'object') {
|
|
545
|
+
const openapi = def.openapi;
|
|
546
|
+
if (openapi.description) {
|
|
547
|
+
return openapi.description;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return undefined;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Extracts example from a Zod schema if available.
|
|
554
|
+
* Checks for custom openapi metadata that may contain examples.
|
|
555
|
+
*
|
|
556
|
+
* @param schema - The Zod schema
|
|
557
|
+
* @returns The example or undefined
|
|
558
|
+
*/
|
|
559
|
+
function getZodExample(schema) {
|
|
560
|
+
// Check for custom openapi metadata
|
|
561
|
+
const def = schema._def;
|
|
562
|
+
if (def.openapi && typeof def.openapi === 'object') {
|
|
563
|
+
const openapi = def.openapi;
|
|
564
|
+
if (openapi.example !== undefined) {
|
|
565
|
+
return openapi.example;
|
|
566
|
+
}
|
|
567
|
+
if (openapi.examples && openapi.examples.length > 0) {
|
|
568
|
+
return openapi.examples[0];
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
// Check for default value as a fallback example
|
|
572
|
+
if (schema instanceof zod_1.z.ZodDefault) {
|
|
573
|
+
return schema._def.defaultValue();
|
|
574
|
+
}
|
|
575
|
+
return undefined;
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Extracts all examples from a Zod schema if available.
|
|
579
|
+
*
|
|
580
|
+
* @param schema - The Zod schema
|
|
581
|
+
* @returns Array of examples or undefined
|
|
582
|
+
*/
|
|
583
|
+
function getZodExamples(schema) {
|
|
584
|
+
const def = schema._def;
|
|
585
|
+
if (def.openapi && typeof def.openapi === 'object') {
|
|
586
|
+
const openapi = def.openapi;
|
|
587
|
+
if (openapi.examples && openapi.examples.length > 0) {
|
|
588
|
+
return openapi.examples;
|
|
589
|
+
}
|
|
590
|
+
if (openapi.example !== undefined) {
|
|
591
|
+
return [openapi.example];
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return undefined;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Extracts the title from a Zod schema if available.
|
|
598
|
+
*
|
|
599
|
+
* @param schema - The Zod schema
|
|
600
|
+
* @returns The title or undefined
|
|
601
|
+
*/
|
|
602
|
+
function getZodTitle(schema) {
|
|
603
|
+
const def = schema._def;
|
|
604
|
+
if (def.openapi && typeof def.openapi === 'object') {
|
|
605
|
+
const openapi = def.openapi;
|
|
606
|
+
return openapi.title;
|
|
607
|
+
}
|
|
608
|
+
return undefined;
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Checks if a Zod schema is marked as deprecated.
|
|
612
|
+
*
|
|
613
|
+
* @param schema - The Zod schema
|
|
614
|
+
* @returns True if deprecated, false otherwise
|
|
615
|
+
*/
|
|
616
|
+
function isZodDeprecated(schema) {
|
|
617
|
+
const def = schema._def;
|
|
618
|
+
if (def.openapi && typeof def.openapi === 'object') {
|
|
619
|
+
const openapi = def.openapi;
|
|
620
|
+
return openapi.deprecated === true;
|
|
621
|
+
}
|
|
622
|
+
return false;
|
|
623
|
+
}
|
|
12
624
|
// Convert Zod schema to express-validator chains
|
|
13
625
|
function zodToExpressValidator(schema) {
|
|
14
626
|
return (_lang) => {
|
|
@@ -17,25 +629,92 @@ function zodToExpressValidator(schema) {
|
|
|
17
629
|
if (!(schema instanceof zod_1.z.ZodObject)) {
|
|
18
630
|
return chains;
|
|
19
631
|
}
|
|
20
|
-
|
|
632
|
+
const shape = schema._def.shape();
|
|
633
|
+
Object.entries(shape).forEach(([key, zodType]) => {
|
|
21
634
|
let chain = (0, express_validator_1.body)(key);
|
|
635
|
+
let currentType = zodType;
|
|
22
636
|
// Handle optional fields
|
|
23
|
-
if (
|
|
637
|
+
if (currentType instanceof zod_1.z.ZodOptional) {
|
|
638
|
+
chain = chain.optional();
|
|
639
|
+
currentType = currentType._def.innerType;
|
|
640
|
+
}
|
|
641
|
+
// Handle nullable fields
|
|
642
|
+
if (currentType instanceof zod_1.z.ZodNullable) {
|
|
643
|
+
chain = chain.optional({ values: 'null' });
|
|
644
|
+
currentType = currentType._def.innerType;
|
|
645
|
+
}
|
|
646
|
+
// Handle default fields
|
|
647
|
+
if (currentType instanceof zod_1.z.ZodDefault) {
|
|
24
648
|
chain = chain.optional();
|
|
25
|
-
|
|
649
|
+
currentType = currentType._def.innerType;
|
|
26
650
|
}
|
|
27
651
|
// Handle string validations
|
|
28
|
-
if (
|
|
652
|
+
if (currentType instanceof zod_1.z.ZodString) {
|
|
29
653
|
chain = chain.isString();
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
654
|
+
if (currentType._def.checks) {
|
|
655
|
+
for (const check of currentType._def.checks) {
|
|
656
|
+
switch (check.kind) {
|
|
657
|
+
case 'min':
|
|
658
|
+
chain = chain.isLength({ min: check.value });
|
|
659
|
+
break;
|
|
660
|
+
case 'max':
|
|
661
|
+
chain = chain.isLength({ max: check.value });
|
|
662
|
+
break;
|
|
663
|
+
case 'length':
|
|
664
|
+
chain = chain.isLength({ min: check.value, max: check.value });
|
|
665
|
+
break;
|
|
666
|
+
case 'email':
|
|
667
|
+
chain = chain.isEmail();
|
|
668
|
+
break;
|
|
669
|
+
case 'url':
|
|
670
|
+
chain = chain.isURL();
|
|
671
|
+
break;
|
|
672
|
+
case 'uuid':
|
|
673
|
+
chain = chain.isUUID();
|
|
674
|
+
break;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
// Handle number validations
|
|
680
|
+
if (currentType instanceof zod_1.z.ZodNumber) {
|
|
681
|
+
let isInt = false;
|
|
682
|
+
const numericOptions = {};
|
|
683
|
+
if (currentType._def.checks) {
|
|
684
|
+
for (const check of currentType._def.checks) {
|
|
685
|
+
switch (check.kind) {
|
|
686
|
+
case 'int':
|
|
687
|
+
isInt = true;
|
|
688
|
+
break;
|
|
689
|
+
case 'min':
|
|
690
|
+
numericOptions.min = check.value;
|
|
691
|
+
break;
|
|
692
|
+
case 'max':
|
|
693
|
+
numericOptions.max = check.value;
|
|
694
|
+
break;
|
|
36
695
|
}
|
|
37
|
-
}
|
|
696
|
+
}
|
|
38
697
|
}
|
|
698
|
+
if (isInt) {
|
|
699
|
+
chain = chain.isInt(numericOptions);
|
|
700
|
+
}
|
|
701
|
+
else {
|
|
702
|
+
chain = chain.isNumeric();
|
|
703
|
+
if (numericOptions.min !== undefined) {
|
|
704
|
+
chain = chain.custom((value) => Number(value) >= numericOptions.min);
|
|
705
|
+
}
|
|
706
|
+
if (numericOptions.max !== undefined) {
|
|
707
|
+
chain = chain.custom((value) => Number(value) <= numericOptions.max);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
// Handle boolean validations
|
|
712
|
+
if (currentType instanceof zod_1.z.ZodBoolean) {
|
|
713
|
+
chain = chain.isBoolean();
|
|
714
|
+
}
|
|
715
|
+
// Handle array validations
|
|
716
|
+
if (currentType instanceof zod_1.z.ZodArray) {
|
|
717
|
+
chain = chain.isArray();
|
|
39
718
|
}
|
|
40
719
|
chains.push(chain);
|
|
41
720
|
});
|