@tsoa-next/runtime 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.MD +3 -0
- package/dist/config.d.ts +240 -0
- package/dist/config.js +3 -0
- package/dist/config.js.map +1 -0
- package/dist/decorators/customAttribute.d.ts +1 -0
- package/dist/decorators/customAttribute.js +9 -0
- package/dist/decorators/customAttribute.js.map +1 -0
- package/dist/decorators/deprecated.d.ts +4 -0
- package/dist/decorators/deprecated.js +12 -0
- package/dist/decorators/deprecated.js.map +1 -0
- package/dist/decorators/example.d.ts +1 -0
- package/dist/decorators/example.js +9 -0
- package/dist/decorators/example.js.map +1 -0
- package/dist/decorators/extension.d.ts +4 -0
- package/dist/decorators/extension.js +9 -0
- package/dist/decorators/extension.js.map +1 -0
- package/dist/decorators/methods.d.ts +7 -0
- package/dist/decorators/methods.js +45 -0
- package/dist/decorators/methods.js.map +1 -0
- package/dist/decorators/middlewares.d.ts +15 -0
- package/dist/decorators/middlewares.js +53 -0
- package/dist/decorators/middlewares.js.map +1 -0
- package/dist/decorators/operationid.d.ts +1 -0
- package/dist/decorators/operationid.js +9 -0
- package/dist/decorators/operationid.js.map +1 -0
- package/dist/decorators/parameter.d.ts +73 -0
- package/dist/decorators/parameter.js +141 -0
- package/dist/decorators/parameter.js.map +1 -0
- package/dist/decorators/response.d.ts +17 -0
- package/dist/decorators/response.js +38 -0
- package/dist/decorators/response.js.map +1 -0
- package/dist/decorators/route.d.ts +5 -0
- package/dist/decorators/route.js +18 -0
- package/dist/decorators/route.js.map +1 -0
- package/dist/decorators/security.d.ts +10 -0
- package/dist/decorators/security.js +21 -0
- package/dist/decorators/security.js.map +1 -0
- package/dist/decorators/tags.d.ts +1 -0
- package/dist/decorators/tags.js +9 -0
- package/dist/decorators/tags.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/controller.d.ts +16 -0
- package/dist/interfaces/controller.js +24 -0
- package/dist/interfaces/controller.js.map +1 -0
- package/dist/interfaces/file.d.ts +31 -0
- package/dist/interfaces/file.js +3 -0
- package/dist/interfaces/file.js.map +1 -0
- package/dist/interfaces/iocModule.d.ts +7 -0
- package/dist/interfaces/iocModule.js +3 -0
- package/dist/interfaces/iocModule.js.map +1 -0
- package/dist/interfaces/response.d.ts +5 -0
- package/dist/interfaces/response.js +3 -0
- package/dist/interfaces/response.js.map +1 -0
- package/dist/metadataGeneration/tsoa.d.ts +219 -0
- package/dist/metadataGeneration/tsoa.js +3 -0
- package/dist/metadataGeneration/tsoa.js.map +1 -0
- package/dist/routeGeneration/additionalProps.d.ts +6 -0
- package/dist/routeGeneration/additionalProps.js +3 -0
- package/dist/routeGeneration/additionalProps.js.map +1 -0
- package/dist/routeGeneration/templateHelpers.d.ts +203 -0
- package/dist/routeGeneration/templateHelpers.js +859 -0
- package/dist/routeGeneration/templateHelpers.js.map +1 -0
- package/dist/routeGeneration/templates/express/expressTemplateService.d.ts +29 -0
- package/dist/routeGeneration/templates/express/expressTemplateService.js +118 -0
- package/dist/routeGeneration/templates/express/expressTemplateService.js.map +1 -0
- package/dist/routeGeneration/templates/hapi/hapiTemplateService.d.ts +36 -0
- package/dist/routeGeneration/templates/hapi/hapiTemplateService.js +120 -0
- package/dist/routeGeneration/templates/hapi/hapiTemplateService.js.map +1 -0
- package/dist/routeGeneration/templates/index.d.ts +4 -0
- package/dist/routeGeneration/templates/index.js +21 -0
- package/dist/routeGeneration/templates/index.js.map +1 -0
- package/dist/routeGeneration/templates/koa/koaTemplateService.d.ts +29 -0
- package/dist/routeGeneration/templates/koa/koaTemplateService.js +116 -0
- package/dist/routeGeneration/templates/koa/koaTemplateService.js.map +1 -0
- package/dist/routeGeneration/templates/templateService.d.ts +15 -0
- package/dist/routeGeneration/templates/templateService.js +37 -0
- package/dist/routeGeneration/templates/templateService.js.map +1 -0
- package/dist/routeGeneration/tsoa-route.d.ts +56 -0
- package/dist/routeGeneration/tsoa-route.js +11 -0
- package/dist/routeGeneration/tsoa-route.js.map +1 -0
- package/dist/swagger/swagger.d.ts +550 -0
- package/dist/swagger/swagger.js +12 -0
- package/dist/swagger/swagger.js.map +1 -0
- package/dist/utils/assertNever.d.ts +4 -0
- package/dist/utils/assertNever.js +10 -0
- package/dist/utils/assertNever.js.map +1 -0
- package/dist/utils/isHeaderType.d.ts +5 -0
- package/dist/utils/isHeaderType.js +3 -0
- package/dist/utils/isHeaderType.js.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1,859 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ValidateError = exports.ValidationService = void 0;
|
|
7
|
+
exports.ValidateParam = ValidateParam;
|
|
8
|
+
const validator_1 = __importDefault(require("validator"));
|
|
9
|
+
const assertNever_1 = require("../utils/assertNever");
|
|
10
|
+
const tsoa_route_1 = require("./tsoa-route");
|
|
11
|
+
// for backwards compatibility with custom templates
|
|
12
|
+
function ValidateParam(property, value, generatedModels, name = '', fieldErrors, isBodyParam, parent = '', config) {
|
|
13
|
+
return new ValidationService(generatedModels, config).ValidateParam(property, value, name, fieldErrors, isBodyParam, parent);
|
|
14
|
+
}
|
|
15
|
+
class ValidationService {
|
|
16
|
+
models;
|
|
17
|
+
config;
|
|
18
|
+
validationStack = new Set();
|
|
19
|
+
constructor(models, config) {
|
|
20
|
+
this.models = models;
|
|
21
|
+
this.config = config;
|
|
22
|
+
}
|
|
23
|
+
ValidateParam(property, rawValue, name = '', fieldErrors, isBodyParam, parent = '') {
|
|
24
|
+
let value = rawValue;
|
|
25
|
+
// If undefined is allowed type, we can move to value validation
|
|
26
|
+
if (value === undefined && property.dataType !== 'undefined') {
|
|
27
|
+
// If there's either default value or datatype is union with undefined valid, we can just set it and move to validation
|
|
28
|
+
if (property.default !== undefined || (property.dataType === 'union' && property.subSchemas?.some(p => p.dataType === 'undefined'))) {
|
|
29
|
+
value = property.default;
|
|
30
|
+
}
|
|
31
|
+
else if (property.required) {
|
|
32
|
+
// If value can be typed as undefined, there's no need to check mandatoriness here.
|
|
33
|
+
let message = `'${name}' is required`;
|
|
34
|
+
if (property.validators) {
|
|
35
|
+
const validators = property.validators;
|
|
36
|
+
Object.keys(validators).forEach((key) => {
|
|
37
|
+
const errorMsg = validators[key]?.errorMsg;
|
|
38
|
+
if (key.startsWith('is') && errorMsg) {
|
|
39
|
+
message = errorMsg;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
fieldErrors[parent + name] = {
|
|
44
|
+
message,
|
|
45
|
+
value,
|
|
46
|
+
};
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
switch (property.dataType) {
|
|
54
|
+
case 'string':
|
|
55
|
+
return this.validateString(name, value, fieldErrors, property.validators, parent);
|
|
56
|
+
case 'boolean':
|
|
57
|
+
return this.validateBool(name, value, fieldErrors, isBodyParam, property.validators, parent);
|
|
58
|
+
case 'integer':
|
|
59
|
+
case 'long':
|
|
60
|
+
return this.validateInt(name, value, fieldErrors, isBodyParam, property.validators, parent);
|
|
61
|
+
case 'float':
|
|
62
|
+
case 'double':
|
|
63
|
+
return this.validateFloat(name, value, fieldErrors, isBodyParam, property.validators, parent);
|
|
64
|
+
case 'enum':
|
|
65
|
+
return this.validateEnum(name, value, fieldErrors, property.enums, parent);
|
|
66
|
+
case 'array':
|
|
67
|
+
return this.validateArray(name, value, fieldErrors, isBodyParam, property.array, property.validators, parent);
|
|
68
|
+
case 'date':
|
|
69
|
+
return this.validateDate(name, value, fieldErrors, isBodyParam, property.validators, parent);
|
|
70
|
+
case 'datetime':
|
|
71
|
+
return this.validateDateTime(name, value, fieldErrors, isBodyParam, property.validators, parent);
|
|
72
|
+
case 'buffer':
|
|
73
|
+
return this.validateBuffer(name, value);
|
|
74
|
+
case 'union':
|
|
75
|
+
return this.validateUnion(name, value, fieldErrors, isBodyParam, property, parent);
|
|
76
|
+
case 'intersection':
|
|
77
|
+
return this.validateIntersection(name, value, fieldErrors, isBodyParam, property.subSchemas, parent);
|
|
78
|
+
case 'undefined':
|
|
79
|
+
return this.validateUndefined(name, value, fieldErrors, parent);
|
|
80
|
+
case 'any':
|
|
81
|
+
return value;
|
|
82
|
+
case 'nestedObjectLiteral':
|
|
83
|
+
return this.validateNestedObjectLiteral(name, value, fieldErrors, isBodyParam, property.nestedProperties, property.additionalProperties, parent);
|
|
84
|
+
default:
|
|
85
|
+
if (property.ref) {
|
|
86
|
+
// Detect circular references to prevent stack overflow
|
|
87
|
+
const refPath = `${parent}${name}:${property.ref}`;
|
|
88
|
+
if (this.validationStack.has(refPath)) {
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
this.validationStack.add(refPath);
|
|
92
|
+
try {
|
|
93
|
+
return this.validateModel({ name, value, modelDefinition: this.models[property.ref], fieldErrors, isBodyParam, parent });
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
this.validationStack.delete(refPath);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
hasCorrectJsType(value, type, isBodyParam) {
|
|
103
|
+
return !isBodyParam || this.config.bodyCoercion || typeof value === type;
|
|
104
|
+
}
|
|
105
|
+
validateNestedObjectLiteral(name, value, fieldErrors, isBodyParam, nestedProperties, additionalProperties, parent) {
|
|
106
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
107
|
+
fieldErrors[parent + name] = {
|
|
108
|
+
message: `invalid object`,
|
|
109
|
+
value,
|
|
110
|
+
};
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const previousErrors = Object.keys(fieldErrors).length;
|
|
114
|
+
if (!nestedProperties) {
|
|
115
|
+
throw new Error('internal tsoa error: ' +
|
|
116
|
+
'the metadata that was generated should have had nested property schemas since it’s for a nested object,' +
|
|
117
|
+
'however it did not. ' +
|
|
118
|
+
'Please file an issue with tsoa at https://github.com/lukeautry/tsoa/issues');
|
|
119
|
+
}
|
|
120
|
+
const propHandling = this.config.noImplicitAdditionalProperties;
|
|
121
|
+
if (propHandling !== 'ignore') {
|
|
122
|
+
const excessProps = this.getExcessPropertiesFor({ dataType: 'refObject', properties: nestedProperties, additionalProperties }, Object.keys(value));
|
|
123
|
+
if (excessProps.length > 0) {
|
|
124
|
+
if (propHandling === 'silently-remove-extras') {
|
|
125
|
+
excessProps.forEach(excessProp => {
|
|
126
|
+
delete value[excessProp];
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if (propHandling === 'throw-on-extras') {
|
|
130
|
+
fieldErrors[parent + name] = {
|
|
131
|
+
message: `"${excessProps.join(',')}" is an excess property and therefore is not allowed`,
|
|
132
|
+
value: excessProps.reduce((acc, propName) => ({ [propName]: value[propName], ...acc }), {}),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
Object.keys(nestedProperties).forEach(key => {
|
|
138
|
+
const validatedProp = this.ValidateParam(nestedProperties[key], value[key], key, fieldErrors, isBodyParam, parent + name + '.');
|
|
139
|
+
// Add value from validator if it's not undefined or if value is required and unfedined is valid type
|
|
140
|
+
if (validatedProp !== undefined || (nestedProperties[key].dataType === 'undefined' && nestedProperties[key].required)) {
|
|
141
|
+
value[key] = validatedProp;
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
if (typeof additionalProperties === 'object' && typeof value === 'object') {
|
|
145
|
+
const keys = Object.keys(value).filter(key => typeof nestedProperties[key] === 'undefined');
|
|
146
|
+
keys.forEach(key => {
|
|
147
|
+
const validatedProp = this.ValidateParam(additionalProperties, value[key], key, fieldErrors, isBodyParam, parent + name + '.');
|
|
148
|
+
// Add value from validator if it's not undefined or if value is required and unfedined is valid type
|
|
149
|
+
if (validatedProp !== undefined || (additionalProperties.dataType === 'undefined' && additionalProperties.required)) {
|
|
150
|
+
value[key] = validatedProp;
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
if (Object.keys(fieldErrors).length > previousErrors) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
return value;
|
|
158
|
+
}
|
|
159
|
+
validateInt(name, value, fieldErrors, isBodyParam, validators, parent = '') {
|
|
160
|
+
if (!this.hasCorrectJsType(value, 'number', isBodyParam) || !validator_1.default.isInt(String(value))) {
|
|
161
|
+
let message = `invalid integer number`;
|
|
162
|
+
if (validators) {
|
|
163
|
+
if (validators.isInt && validators.isInt.errorMsg) {
|
|
164
|
+
message = validators.isInt.errorMsg;
|
|
165
|
+
}
|
|
166
|
+
if (validators.isLong && validators.isLong.errorMsg) {
|
|
167
|
+
message = validators.isLong.errorMsg;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
fieldErrors[parent + name] = {
|
|
171
|
+
message,
|
|
172
|
+
value,
|
|
173
|
+
};
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const numberValue = validator_1.default.toInt(String(value), 10);
|
|
177
|
+
if (!validators) {
|
|
178
|
+
return numberValue;
|
|
179
|
+
}
|
|
180
|
+
if (validators.minimum && validators.minimum.value !== undefined) {
|
|
181
|
+
if (validators.minimum.value > numberValue) {
|
|
182
|
+
fieldErrors[parent + name] = {
|
|
183
|
+
message: validators.minimum.errorMsg || `min ${validators.minimum.value}`,
|
|
184
|
+
value,
|
|
185
|
+
};
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (validators.maximum && validators.maximum.value !== undefined) {
|
|
190
|
+
if (validators.maximum.value < numberValue) {
|
|
191
|
+
fieldErrors[parent + name] = {
|
|
192
|
+
message: validators.maximum.errorMsg || `max ${validators.maximum.value}`,
|
|
193
|
+
value,
|
|
194
|
+
};
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return numberValue;
|
|
199
|
+
}
|
|
200
|
+
validateFloat(name, value, fieldErrors, isBodyParam, validators, parent = '') {
|
|
201
|
+
if (!this.hasCorrectJsType(value, 'number', isBodyParam) || !validator_1.default.isFloat(String(value))) {
|
|
202
|
+
let message = 'invalid float number';
|
|
203
|
+
if (validators) {
|
|
204
|
+
if (validators.isFloat && validators.isFloat.errorMsg) {
|
|
205
|
+
message = validators.isFloat.errorMsg;
|
|
206
|
+
}
|
|
207
|
+
if (validators.isDouble && validators.isDouble.errorMsg) {
|
|
208
|
+
message = validators.isDouble.errorMsg;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
fieldErrors[parent + name] = {
|
|
212
|
+
message,
|
|
213
|
+
value,
|
|
214
|
+
};
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const numberValue = validator_1.default.toFloat(String(value));
|
|
218
|
+
if (!validators) {
|
|
219
|
+
return numberValue;
|
|
220
|
+
}
|
|
221
|
+
if (validators.minimum && validators.minimum.value !== undefined) {
|
|
222
|
+
if (validators.minimum.value > numberValue) {
|
|
223
|
+
fieldErrors[parent + name] = {
|
|
224
|
+
message: validators.minimum.errorMsg || `min ${validators.minimum.value}`,
|
|
225
|
+
value,
|
|
226
|
+
};
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (validators.maximum && validators.maximum.value !== undefined) {
|
|
231
|
+
if (validators.maximum.value < numberValue) {
|
|
232
|
+
fieldErrors[parent + name] = {
|
|
233
|
+
message: validators.maximum.errorMsg || `max ${validators.maximum.value}`,
|
|
234
|
+
value,
|
|
235
|
+
};
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return numberValue;
|
|
240
|
+
}
|
|
241
|
+
validateEnum(name, value, fieldErrors, members, parent = '') {
|
|
242
|
+
if (!members || members.length === 0) {
|
|
243
|
+
fieldErrors[parent + name] = {
|
|
244
|
+
message: 'no member',
|
|
245
|
+
value,
|
|
246
|
+
};
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const enumMatchIndex = members.map(member => String(member)).findIndex(member => validator_1.default.equals(member, String(value)));
|
|
250
|
+
if (enumMatchIndex === -1) {
|
|
251
|
+
const membersInQuotes = members.map(member => (typeof member === 'string' ? `'${member}'` : String(member)));
|
|
252
|
+
fieldErrors[parent + name] = {
|
|
253
|
+
message: `should be one of the following; [${membersInQuotes.join(',')}]`,
|
|
254
|
+
value,
|
|
255
|
+
};
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
return members[enumMatchIndex];
|
|
259
|
+
}
|
|
260
|
+
validateDate(name, value, fieldErrors, isBodyParam, validators, parent = '') {
|
|
261
|
+
if (!this.hasCorrectJsType(value, 'string', isBodyParam) || !validator_1.default.isISO8601(String(value), { strict: true })) {
|
|
262
|
+
const message = validators && validators.isDate && validators.isDate.errorMsg ? validators.isDate.errorMsg : `invalid ISO 8601 date format, i.e. YYYY-MM-DD`;
|
|
263
|
+
fieldErrors[parent + name] = {
|
|
264
|
+
message,
|
|
265
|
+
value,
|
|
266
|
+
};
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const dateValue = new Date(String(value));
|
|
270
|
+
if (!validators) {
|
|
271
|
+
return dateValue;
|
|
272
|
+
}
|
|
273
|
+
if (validators.minDate && validators.minDate.value) {
|
|
274
|
+
const minDate = new Date(validators.minDate.value);
|
|
275
|
+
if (minDate.getTime() > dateValue.getTime()) {
|
|
276
|
+
fieldErrors[parent + name] = {
|
|
277
|
+
message: validators.minDate.errorMsg || `minDate '${validators.minDate.value}'`,
|
|
278
|
+
value,
|
|
279
|
+
};
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (validators.maxDate && validators.maxDate.value) {
|
|
284
|
+
const maxDate = new Date(validators.maxDate.value);
|
|
285
|
+
if (maxDate.getTime() < dateValue.getTime()) {
|
|
286
|
+
fieldErrors[parent + name] = {
|
|
287
|
+
message: validators.maxDate.errorMsg || `maxDate '${validators.maxDate.value}'`,
|
|
288
|
+
value,
|
|
289
|
+
};
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return dateValue;
|
|
294
|
+
}
|
|
295
|
+
validateDateTime(name, value, fieldErrors, isBodyParam, validators, parent = '') {
|
|
296
|
+
if (!this.hasCorrectJsType(value, 'string', isBodyParam) || !validator_1.default.isISO8601(String(value), { strict: true })) {
|
|
297
|
+
const message = validators && validators.isDateTime && validators.isDateTime.errorMsg ? validators.isDateTime.errorMsg : `invalid ISO 8601 datetime format, i.e. YYYY-MM-DDTHH:mm:ss`;
|
|
298
|
+
fieldErrors[parent + name] = {
|
|
299
|
+
message,
|
|
300
|
+
value,
|
|
301
|
+
};
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
const datetimeValue = new Date(String(value));
|
|
305
|
+
if (!validators) {
|
|
306
|
+
return datetimeValue;
|
|
307
|
+
}
|
|
308
|
+
if (validators.minDate && validators.minDate.value) {
|
|
309
|
+
const minDate = new Date(validators.minDate.value);
|
|
310
|
+
if (minDate.getTime() > datetimeValue.getTime()) {
|
|
311
|
+
fieldErrors[parent + name] = {
|
|
312
|
+
message: validators.minDate.errorMsg || `minDate '${validators.minDate.value}'`,
|
|
313
|
+
value,
|
|
314
|
+
};
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (validators.maxDate && validators.maxDate.value) {
|
|
319
|
+
const maxDate = new Date(validators.maxDate.value);
|
|
320
|
+
if (maxDate.getTime() < datetimeValue.getTime()) {
|
|
321
|
+
fieldErrors[parent + name] = {
|
|
322
|
+
message: validators.maxDate.errorMsg || `maxDate '${validators.maxDate.value}'`,
|
|
323
|
+
value,
|
|
324
|
+
};
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return datetimeValue;
|
|
329
|
+
}
|
|
330
|
+
validateString(name, value, fieldErrors, validators, parent = '') {
|
|
331
|
+
if (typeof value !== 'string') {
|
|
332
|
+
const message = validators && validators.isString && validators.isString.errorMsg ? validators.isString.errorMsg : `invalid string value`;
|
|
333
|
+
fieldErrors[parent + name] = {
|
|
334
|
+
message,
|
|
335
|
+
value,
|
|
336
|
+
};
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
const stringValue = String(value);
|
|
340
|
+
if (!validators) {
|
|
341
|
+
return stringValue;
|
|
342
|
+
}
|
|
343
|
+
if (validators.minLength && validators.minLength.value !== undefined) {
|
|
344
|
+
if (validators.minLength.value > stringValue.length) {
|
|
345
|
+
fieldErrors[parent + name] = {
|
|
346
|
+
message: validators.minLength.errorMsg || `minLength ${validators.minLength.value}`,
|
|
347
|
+
value,
|
|
348
|
+
};
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (validators.maxLength && validators.maxLength.value !== undefined) {
|
|
353
|
+
if (validators.maxLength.value < stringValue.length) {
|
|
354
|
+
fieldErrors[parent + name] = {
|
|
355
|
+
message: validators.maxLength.errorMsg || `maxLength ${validators.maxLength.value}`,
|
|
356
|
+
value,
|
|
357
|
+
};
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (validators.pattern && validators.pattern.value) {
|
|
362
|
+
if (!validator_1.default.matches(String(stringValue), validators.pattern.value)) {
|
|
363
|
+
fieldErrors[parent + name] = {
|
|
364
|
+
message: validators.pattern.errorMsg || `Not match in '${validators.pattern.value}'`,
|
|
365
|
+
value,
|
|
366
|
+
};
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return stringValue;
|
|
371
|
+
}
|
|
372
|
+
validateBool(name, value, fieldErrors, isBodyParam, validators, parent = '') {
|
|
373
|
+
if (value === true || value === false) {
|
|
374
|
+
return value;
|
|
375
|
+
}
|
|
376
|
+
if (!isBodyParam || this.config.bodyCoercion === true) {
|
|
377
|
+
if (value === undefined || value === null) {
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
if (String(value).toLowerCase() === 'true') {
|
|
381
|
+
return true;
|
|
382
|
+
}
|
|
383
|
+
if (String(value).toLowerCase() === 'false') {
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
const message = validators && validators.isBoolean && validators.isBoolean.errorMsg ? validators.isBoolean.errorMsg : `invalid boolean value`;
|
|
388
|
+
fieldErrors[parent + name] = {
|
|
389
|
+
message,
|
|
390
|
+
value,
|
|
391
|
+
};
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
validateUndefined(name, value, fieldErrors, parent = '') {
|
|
395
|
+
if (value === undefined) {
|
|
396
|
+
return undefined;
|
|
397
|
+
}
|
|
398
|
+
const message = 'invalid undefined value';
|
|
399
|
+
fieldErrors[parent + name] = {
|
|
400
|
+
message,
|
|
401
|
+
value,
|
|
402
|
+
};
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
validateArray(name, value, fieldErrors, isBodyParam, schema, validators, parent = '') {
|
|
406
|
+
if ((isBodyParam && this.config.bodyCoercion === false && !Array.isArray(value)) || !schema || value === undefined) {
|
|
407
|
+
const message = validators && validators.isArray && validators.isArray.errorMsg ? validators.isArray.errorMsg : `invalid array`;
|
|
408
|
+
fieldErrors[parent + name] = {
|
|
409
|
+
message,
|
|
410
|
+
value,
|
|
411
|
+
};
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
let arrayValue = [];
|
|
415
|
+
const previousErrors = Object.keys(fieldErrors).length;
|
|
416
|
+
if (Array.isArray(value)) {
|
|
417
|
+
arrayValue = value.map((elementValue, index) => {
|
|
418
|
+
return this.ValidateParam(schema, elementValue, `$${index}`, fieldErrors, isBodyParam, name + '.');
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
arrayValue = [this.ValidateParam(schema, value, '$0', fieldErrors, isBodyParam, name + '.')];
|
|
423
|
+
}
|
|
424
|
+
if (Object.keys(fieldErrors).length > previousErrors) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
if (!validators) {
|
|
428
|
+
return arrayValue;
|
|
429
|
+
}
|
|
430
|
+
if (validators.minItems && validators.minItems.value) {
|
|
431
|
+
if (validators.minItems.value > arrayValue.length) {
|
|
432
|
+
fieldErrors[parent + name] = {
|
|
433
|
+
message: validators.minItems.errorMsg || `minItems ${validators.minItems.value}`,
|
|
434
|
+
value,
|
|
435
|
+
};
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
if (validators.maxItems && validators.maxItems.value) {
|
|
440
|
+
if (validators.maxItems.value < arrayValue.length) {
|
|
441
|
+
fieldErrors[parent + name] = {
|
|
442
|
+
message: validators.maxItems.errorMsg || `maxItems ${validators.maxItems.value}`,
|
|
443
|
+
value,
|
|
444
|
+
};
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (validators.uniqueItems) {
|
|
449
|
+
const unique = arrayValue.some((elem, index, arr) => {
|
|
450
|
+
const indexOf = arr.indexOf(elem);
|
|
451
|
+
return indexOf > -1 && indexOf !== index;
|
|
452
|
+
});
|
|
453
|
+
if (unique) {
|
|
454
|
+
fieldErrors[parent + name] = {
|
|
455
|
+
message: validators.uniqueItems.errorMsg || `required unique array`,
|
|
456
|
+
value,
|
|
457
|
+
};
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return arrayValue;
|
|
462
|
+
}
|
|
463
|
+
validateBuffer(_name, value) {
|
|
464
|
+
return Buffer.from(value);
|
|
465
|
+
}
|
|
466
|
+
validateUnion(name, value, fieldErrors, isBodyParam, property, parent = '') {
|
|
467
|
+
if (!property.subSchemas) {
|
|
468
|
+
throw new Error('internal tsoa error: ' +
|
|
469
|
+
'the metadata that was generated should have had sub schemas since it’s for a union, however it did not. ' +
|
|
470
|
+
'Please file an issue with tsoa at https://github.com/lukeautry/tsoa/issues');
|
|
471
|
+
}
|
|
472
|
+
const subFieldErrors = [];
|
|
473
|
+
for (const subSchema of property.subSchemas) {
|
|
474
|
+
const subFieldError = {};
|
|
475
|
+
// Clean value if it's not undefined or use undefined directly if it's undefined.
|
|
476
|
+
// Value can be undefined if undefined is allowed datatype of the union
|
|
477
|
+
const validateableValue = value !== undefined ? this.deepClone(value) : value;
|
|
478
|
+
const cleanValue = this.ValidateParam({ ...subSchema, validators: { ...property.validators, ...subSchema.validators } }, validateableValue, name, subFieldError, isBodyParam, parent);
|
|
479
|
+
subFieldErrors.push(subFieldError);
|
|
480
|
+
if (Object.keys(subFieldError).length === 0) {
|
|
481
|
+
return cleanValue;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
this.addSummarizedError(fieldErrors, parent + name, 'Could not match the union against any of the items. Issues: ', subFieldErrors, value);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
validateIntersection(name, value, fieldErrors, isBodyParam, subSchemas, parent = '') {
|
|
488
|
+
if (!subSchemas) {
|
|
489
|
+
throw new Error('internal tsoa error: ' +
|
|
490
|
+
'the metadata that was generated should have had sub schemas since it’s for a intersection, however it did not. ' +
|
|
491
|
+
'Please file an issue with tsoa at https://github.com/lukeautry/tsoa/issues');
|
|
492
|
+
}
|
|
493
|
+
const subFieldErrors = [];
|
|
494
|
+
let cleanValues = {};
|
|
495
|
+
subSchemas.forEach(subSchema => {
|
|
496
|
+
const subFieldError = {};
|
|
497
|
+
const cleanValue = this.createChildValidationService({
|
|
498
|
+
noImplicitAdditionalProperties: 'silently-remove-extras',
|
|
499
|
+
}).ValidateParam(subSchema, this.deepClone(value), name, subFieldError, isBodyParam, parent);
|
|
500
|
+
cleanValues = {
|
|
501
|
+
...cleanValues,
|
|
502
|
+
...cleanValue,
|
|
503
|
+
};
|
|
504
|
+
subFieldErrors.push(subFieldError);
|
|
505
|
+
});
|
|
506
|
+
const filtered = subFieldErrors.filter(subFieldError => Object.keys(subFieldError).length !== 0);
|
|
507
|
+
if (filtered.length > 0) {
|
|
508
|
+
this.addSummarizedError(fieldErrors, parent + name, 'Could not match the intersection against every type. Issues: ', filtered, value);
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
const schemas = this.selfIntersectionCombinations(subSchemas.map(subSchema => this.toModelLike(subSchema)));
|
|
512
|
+
const getRequiredPropError = (schema) => {
|
|
513
|
+
const requiredPropError = {};
|
|
514
|
+
this.createChildValidationService({
|
|
515
|
+
noImplicitAdditionalProperties: 'ignore',
|
|
516
|
+
}).validateModel({
|
|
517
|
+
name,
|
|
518
|
+
value: this.deepClone(value),
|
|
519
|
+
modelDefinition: schema,
|
|
520
|
+
fieldErrors: requiredPropError,
|
|
521
|
+
isBodyParam,
|
|
522
|
+
});
|
|
523
|
+
return requiredPropError;
|
|
524
|
+
};
|
|
525
|
+
const schemasWithRequiredProps = schemas.filter(schema => Object.keys(getRequiredPropError(schema)).length === 0);
|
|
526
|
+
if (this.config.noImplicitAdditionalProperties === 'ignore') {
|
|
527
|
+
return { ...value, ...cleanValues };
|
|
528
|
+
}
|
|
529
|
+
if (this.config.noImplicitAdditionalProperties === 'silently-remove-extras') {
|
|
530
|
+
if (schemasWithRequiredProps.length > 0) {
|
|
531
|
+
return cleanValues;
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
fieldErrors[parent + name] = {
|
|
535
|
+
message: `Could not match intersection against any of the possible combinations: ${JSON.stringify(schemas.map(s => Object.keys(s.properties)))}`,
|
|
536
|
+
value,
|
|
537
|
+
};
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
if (schemasWithRequiredProps.length > 0 && schemasWithRequiredProps.some(schema => this.getExcessPropertiesFor(schema, Object.keys(value)).length === 0)) {
|
|
542
|
+
return cleanValues;
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
fieldErrors[parent + name] = {
|
|
546
|
+
message: `Could not match intersection against any of the possible combinations: ${JSON.stringify(schemas.map(s => Object.keys(s.properties)))}`,
|
|
547
|
+
value,
|
|
548
|
+
};
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
toModelLike(schema) {
|
|
553
|
+
if (schema.ref) {
|
|
554
|
+
const model = this.models[schema.ref];
|
|
555
|
+
if (model.dataType === 'refObject') {
|
|
556
|
+
return [model];
|
|
557
|
+
}
|
|
558
|
+
else if (model.dataType === 'refAlias') {
|
|
559
|
+
return [...this.toModelLike(model.type)];
|
|
560
|
+
}
|
|
561
|
+
else if (model.dataType === 'refEnum') {
|
|
562
|
+
throw new Error(`Can't transform an enum into a model like structure because it does not have properties.`);
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
return (0, assertNever_1.assertNever)(model);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
else if (schema.nestedProperties) {
|
|
569
|
+
return [{ dataType: 'refObject', properties: schema.nestedProperties, additionalProperties: schema.additionalProperties }];
|
|
570
|
+
}
|
|
571
|
+
else if (schema.subSchemas && schema.dataType === 'intersection') {
|
|
572
|
+
const modelss = schema.subSchemas.map(subSchema => this.toModelLike(subSchema));
|
|
573
|
+
return this.selfIntersectionCombinations(modelss);
|
|
574
|
+
}
|
|
575
|
+
else if (schema.subSchemas && schema.dataType === 'union') {
|
|
576
|
+
const modelss = schema.subSchemas.map(subSchema => this.toModelLike(subSchema));
|
|
577
|
+
return modelss.reduce((acc, models) => [...acc, ...models], []);
|
|
578
|
+
}
|
|
579
|
+
else {
|
|
580
|
+
// There are no properties to check for excess here.
|
|
581
|
+
return [{ dataType: 'refObject', properties: {}, additionalProperties: false }];
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* combine all schemas once, ignoring order ie
|
|
586
|
+
* input: [[value1], [value2]] should be [[value1, value2]]
|
|
587
|
+
* not [[value1, value2],[value2, value1]]
|
|
588
|
+
* and
|
|
589
|
+
* input: [[value1, value2], [value3, value4], [value5, value6]] should be [
|
|
590
|
+
* [value1, value3, value5],
|
|
591
|
+
* [value1, value3, value6],
|
|
592
|
+
* [value1, value4, value5],
|
|
593
|
+
* [value1, value4, value6],
|
|
594
|
+
* [value2, value3, value5],
|
|
595
|
+
* [value2, value3, value6],
|
|
596
|
+
* [value2, value4, value5],
|
|
597
|
+
* [value2, value4, value6],
|
|
598
|
+
* ]
|
|
599
|
+
* @param modelSchemass
|
|
600
|
+
*/
|
|
601
|
+
selfIntersectionCombinations(modelSchemass) {
|
|
602
|
+
const res = [];
|
|
603
|
+
// Picks one schema from each sub-array
|
|
604
|
+
const combinations = this.getAllCombinations(modelSchemass);
|
|
605
|
+
for (const combination of combinations) {
|
|
606
|
+
// Combine all schemas of this combination
|
|
607
|
+
let currentCollector = { ...combination[0] };
|
|
608
|
+
for (let subSchemaIdx = 1; subSchemaIdx < combination.length; subSchemaIdx++) {
|
|
609
|
+
currentCollector = { ...this.combineProperties(currentCollector, combination[subSchemaIdx]) };
|
|
610
|
+
}
|
|
611
|
+
res.push(currentCollector);
|
|
612
|
+
}
|
|
613
|
+
return res;
|
|
614
|
+
}
|
|
615
|
+
getAllCombinations(arrays) {
|
|
616
|
+
function combine(current, index) {
|
|
617
|
+
if (index === arrays.length) {
|
|
618
|
+
result.push(current.slice());
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
for (let i = 0; i < arrays[index].length; i++) {
|
|
622
|
+
current.push(arrays[index][i]);
|
|
623
|
+
combine(current, index + 1);
|
|
624
|
+
current.pop();
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
const result = [];
|
|
628
|
+
combine([], 0);
|
|
629
|
+
return result;
|
|
630
|
+
}
|
|
631
|
+
combineProperties(a, b) {
|
|
632
|
+
return { dataType: 'refObject', properties: { ...a.properties, ...b.properties }, additionalProperties: a.additionalProperties || b.additionalProperties || false };
|
|
633
|
+
}
|
|
634
|
+
getExcessPropertiesFor(modelDefinition, properties) {
|
|
635
|
+
const modelProperties = new Set(Object.keys(modelDefinition.properties));
|
|
636
|
+
if (modelDefinition.additionalProperties) {
|
|
637
|
+
return [];
|
|
638
|
+
}
|
|
639
|
+
else if (this.config.noImplicitAdditionalProperties === 'ignore') {
|
|
640
|
+
return [];
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
return [...properties].filter(property => !modelProperties.has(property));
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
validateModel(input) {
|
|
647
|
+
const { name, value, modelDefinition, fieldErrors, isBodyParam, parent = '' } = input;
|
|
648
|
+
const previousErrors = Object.keys(fieldErrors).length;
|
|
649
|
+
if (modelDefinition) {
|
|
650
|
+
if (modelDefinition.dataType === 'refEnum') {
|
|
651
|
+
return this.validateEnum(name, value, fieldErrors, modelDefinition.enums, parent);
|
|
652
|
+
}
|
|
653
|
+
if (modelDefinition.dataType === 'refAlias') {
|
|
654
|
+
return this.ValidateParam(modelDefinition.type, value, name, fieldErrors, isBodyParam, parent);
|
|
655
|
+
}
|
|
656
|
+
const fieldPath = parent + name;
|
|
657
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
658
|
+
fieldErrors[fieldPath] = {
|
|
659
|
+
message: `invalid object`,
|
|
660
|
+
value,
|
|
661
|
+
};
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
const properties = modelDefinition.properties || {};
|
|
665
|
+
const keysOnPropertiesModelDefinition = new Set(Object.keys(properties));
|
|
666
|
+
const allPropertiesOnData = new Set(Object.keys(value));
|
|
667
|
+
Object.entries(properties).forEach(([key, property]) => {
|
|
668
|
+
const validatedParam = this.ValidateParam(property, value[key], key, fieldErrors, isBodyParam, fieldPath + '.');
|
|
669
|
+
// Add value from validator if it's not undefined or if value is required and unfedined is valid type
|
|
670
|
+
if (validatedParam !== undefined || (property.dataType === 'undefined' && property.required)) {
|
|
671
|
+
value[key] = validatedParam;
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
const isAnExcessProperty = (objectKeyThatMightBeExcess) => {
|
|
675
|
+
return allPropertiesOnData.has(objectKeyThatMightBeExcess) && !keysOnPropertiesModelDefinition.has(objectKeyThatMightBeExcess);
|
|
676
|
+
};
|
|
677
|
+
const additionalProperties = modelDefinition.additionalProperties;
|
|
678
|
+
if (additionalProperties === true || (0, tsoa_route_1.isDefaultForAdditionalPropertiesAllowed)(additionalProperties)) {
|
|
679
|
+
// then don't validate any of the additional properties
|
|
680
|
+
}
|
|
681
|
+
else if (additionalProperties === false) {
|
|
682
|
+
Object.keys(value).forEach((key) => {
|
|
683
|
+
if (isAnExcessProperty(key)) {
|
|
684
|
+
if (this.config.noImplicitAdditionalProperties === 'throw-on-extras') {
|
|
685
|
+
fieldErrors[`${fieldPath}.${key}`] = {
|
|
686
|
+
message: `"${key}" is an excess property and therefore is not allowed`,
|
|
687
|
+
value: key,
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
else if (this.config.noImplicitAdditionalProperties === 'silently-remove-extras') {
|
|
691
|
+
delete value[key];
|
|
692
|
+
}
|
|
693
|
+
else if (this.config.noImplicitAdditionalProperties === 'ignore') {
|
|
694
|
+
// then it's okay to have additionalProperties
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
(0, assertNever_1.assertNever)(this.config.noImplicitAdditionalProperties);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
Object.keys(value).forEach((key) => {
|
|
704
|
+
if (isAnExcessProperty(key)) {
|
|
705
|
+
const validatedValue = this.ValidateParam(additionalProperties, value[key], key, fieldErrors, isBodyParam, fieldPath + '.');
|
|
706
|
+
// Add value from validator if it's not undefined or if value is required and unfedined is valid type
|
|
707
|
+
if (validatedValue !== undefined || (additionalProperties.dataType === 'undefined' && additionalProperties.required)) {
|
|
708
|
+
value[key] = validatedValue;
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
fieldErrors[`${fieldPath}.${key}`] = {
|
|
712
|
+
message: `No matching model found in additionalProperties to validate ${key}`,
|
|
713
|
+
value: key,
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
if (Object.keys(fieldErrors).length > previousErrors) {
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
return value;
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Creates a new ValidationService instance with specific configuration
|
|
727
|
+
* @param overrides Configuration overrides
|
|
728
|
+
* @returns New ValidationService instance
|
|
729
|
+
*/
|
|
730
|
+
createChildValidationService(overrides = {}) {
|
|
731
|
+
return new ValidationService(this.models, {
|
|
732
|
+
...this.config,
|
|
733
|
+
...overrides,
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Deep clones an object without using JSON.stringify/parse to avoid:
|
|
738
|
+
* 1. Loss of undefined values
|
|
739
|
+
* 2. Loss of functions
|
|
740
|
+
* 3. Conversion of dates to strings
|
|
741
|
+
* 4. Exponential escaping issues with nested objects
|
|
742
|
+
*/
|
|
743
|
+
deepClone(obj) {
|
|
744
|
+
// Fast path for primitives
|
|
745
|
+
if (obj === null || obj === undefined) {
|
|
746
|
+
return obj;
|
|
747
|
+
}
|
|
748
|
+
const type = typeof obj;
|
|
749
|
+
if (type !== 'object') {
|
|
750
|
+
return obj;
|
|
751
|
+
}
|
|
752
|
+
// Handle built-in object types
|
|
753
|
+
if (obj instanceof Date) {
|
|
754
|
+
return new Date(obj.getTime());
|
|
755
|
+
}
|
|
756
|
+
if (obj instanceof RegExp) {
|
|
757
|
+
return new RegExp(obj.source, obj.flags);
|
|
758
|
+
}
|
|
759
|
+
if (obj instanceof Array) {
|
|
760
|
+
const cloneArr = new Array(obj.length);
|
|
761
|
+
for (let i = 0; i < obj.length; i++) {
|
|
762
|
+
cloneArr[i] = this.deepClone(obj[i]);
|
|
763
|
+
}
|
|
764
|
+
return cloneArr;
|
|
765
|
+
}
|
|
766
|
+
if (Buffer && obj instanceof Buffer) {
|
|
767
|
+
return Buffer.from(obj);
|
|
768
|
+
}
|
|
769
|
+
// Handle plain objects
|
|
770
|
+
const cloneObj = {};
|
|
771
|
+
for (const key in obj) {
|
|
772
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
773
|
+
cloneObj[key] = this.deepClone(obj[key]);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
return cloneObj;
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Adds a summarized error to the fieldErrors object
|
|
780
|
+
* @param fieldErrors The errors object to add to
|
|
781
|
+
* @param errorKey The key for the error
|
|
782
|
+
* @param prefix The error message prefix
|
|
783
|
+
* @param subErrors Array of sub-errors to summarize
|
|
784
|
+
* @param value The value that failed validation
|
|
785
|
+
*/
|
|
786
|
+
addSummarizedError(fieldErrors, errorKey, prefix, subErrors, value) {
|
|
787
|
+
const maxErrorLength = this.config.maxValidationErrorSize ? this.config.maxValidationErrorSize - prefix.length : undefined;
|
|
788
|
+
fieldErrors[errorKey] = {
|
|
789
|
+
message: `${prefix}${this.summarizeValidationErrors(subErrors, maxErrorLength)}`,
|
|
790
|
+
value,
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Summarizes validation errors to prevent extremely large error messages
|
|
795
|
+
* @param errors Array of field errors from union/intersection validation
|
|
796
|
+
* @param maxLength Maximum length of the summarized message
|
|
797
|
+
* @returns Summarized error message
|
|
798
|
+
*/
|
|
799
|
+
summarizeValidationErrors(errors, maxLength) {
|
|
800
|
+
const effectiveMaxLength = maxLength || this.config.maxValidationErrorSize || 1000;
|
|
801
|
+
// If there are no errors, return empty
|
|
802
|
+
if (errors.length === 0) {
|
|
803
|
+
return '[]';
|
|
804
|
+
}
|
|
805
|
+
// Start with a count of total errors
|
|
806
|
+
const errorCount = errors.length;
|
|
807
|
+
const summary = [];
|
|
808
|
+
// Try to include first few errors
|
|
809
|
+
let currentLength = 0;
|
|
810
|
+
let includedErrors = 0;
|
|
811
|
+
// Calculate the size of the suffix if we need to truncate
|
|
812
|
+
const truncatedSuffix = `,...and ${errorCount} more errors]`;
|
|
813
|
+
const reservedSpace = truncatedSuffix.length + 10; // +10 for safety margin
|
|
814
|
+
for (const error of errors) {
|
|
815
|
+
const errorStr = JSON.stringify(error);
|
|
816
|
+
const projectedLength = currentLength + errorStr.length + (summary.length > 0 ? 1 : 0) + 2; // +1 for comma if not first, +2 for brackets
|
|
817
|
+
if (projectedLength + reservedSpace < effectiveMaxLength && includedErrors < 3) {
|
|
818
|
+
summary.push(errorStr);
|
|
819
|
+
currentLength = projectedLength;
|
|
820
|
+
includedErrors++;
|
|
821
|
+
}
|
|
822
|
+
else {
|
|
823
|
+
break;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
// Build final message
|
|
827
|
+
if (includedErrors < errorCount) {
|
|
828
|
+
const result = `[${summary.join(',')},...and ${errorCount - includedErrors} more errors]`;
|
|
829
|
+
// Make sure we don't exceed the limit
|
|
830
|
+
if (result.length > effectiveMaxLength) {
|
|
831
|
+
// If still too long, remove the last error and try again
|
|
832
|
+
if (summary.length > 0) {
|
|
833
|
+
summary.pop();
|
|
834
|
+
includedErrors--;
|
|
835
|
+
return `[${summary.join(',')},...and ${errorCount - includedErrors} more errors]`;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
return result;
|
|
839
|
+
}
|
|
840
|
+
else {
|
|
841
|
+
return `[${summary.join(',')}]`;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
exports.ValidationService = ValidationService;
|
|
846
|
+
class ValidateError extends Error {
|
|
847
|
+
fields;
|
|
848
|
+
message;
|
|
849
|
+
status = 400;
|
|
850
|
+
name = 'ValidateError';
|
|
851
|
+
constructor(fields, message) {
|
|
852
|
+
super(message);
|
|
853
|
+
this.fields = fields;
|
|
854
|
+
this.message = message;
|
|
855
|
+
Object.setPrototypeOf(this, ValidateError.prototype);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
exports.ValidateError = ValidateError;
|
|
859
|
+
//# sourceMappingURL=templateHelpers.js.map
|