@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.
Files changed (92) hide show
  1. package/README.MD +3 -0
  2. package/dist/config.d.ts +240 -0
  3. package/dist/config.js +3 -0
  4. package/dist/config.js.map +1 -0
  5. package/dist/decorators/customAttribute.d.ts +1 -0
  6. package/dist/decorators/customAttribute.js +9 -0
  7. package/dist/decorators/customAttribute.js.map +1 -0
  8. package/dist/decorators/deprecated.d.ts +4 -0
  9. package/dist/decorators/deprecated.js +12 -0
  10. package/dist/decorators/deprecated.js.map +1 -0
  11. package/dist/decorators/example.d.ts +1 -0
  12. package/dist/decorators/example.js +9 -0
  13. package/dist/decorators/example.js.map +1 -0
  14. package/dist/decorators/extension.d.ts +4 -0
  15. package/dist/decorators/extension.js +9 -0
  16. package/dist/decorators/extension.js.map +1 -0
  17. package/dist/decorators/methods.d.ts +7 -0
  18. package/dist/decorators/methods.js +45 -0
  19. package/dist/decorators/methods.js.map +1 -0
  20. package/dist/decorators/middlewares.d.ts +15 -0
  21. package/dist/decorators/middlewares.js +53 -0
  22. package/dist/decorators/middlewares.js.map +1 -0
  23. package/dist/decorators/operationid.d.ts +1 -0
  24. package/dist/decorators/operationid.js +9 -0
  25. package/dist/decorators/operationid.js.map +1 -0
  26. package/dist/decorators/parameter.d.ts +73 -0
  27. package/dist/decorators/parameter.js +141 -0
  28. package/dist/decorators/parameter.js.map +1 -0
  29. package/dist/decorators/response.d.ts +17 -0
  30. package/dist/decorators/response.js +38 -0
  31. package/dist/decorators/response.js.map +1 -0
  32. package/dist/decorators/route.d.ts +5 -0
  33. package/dist/decorators/route.js +18 -0
  34. package/dist/decorators/route.js.map +1 -0
  35. package/dist/decorators/security.d.ts +10 -0
  36. package/dist/decorators/security.js +21 -0
  37. package/dist/decorators/security.js.map +1 -0
  38. package/dist/decorators/tags.d.ts +1 -0
  39. package/dist/decorators/tags.js +9 -0
  40. package/dist/decorators/tags.js.map +1 -0
  41. package/dist/index.d.ts +24 -0
  42. package/dist/index.js +41 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/interfaces/controller.d.ts +16 -0
  45. package/dist/interfaces/controller.js +24 -0
  46. package/dist/interfaces/controller.js.map +1 -0
  47. package/dist/interfaces/file.d.ts +31 -0
  48. package/dist/interfaces/file.js +3 -0
  49. package/dist/interfaces/file.js.map +1 -0
  50. package/dist/interfaces/iocModule.d.ts +7 -0
  51. package/dist/interfaces/iocModule.js +3 -0
  52. package/dist/interfaces/iocModule.js.map +1 -0
  53. package/dist/interfaces/response.d.ts +5 -0
  54. package/dist/interfaces/response.js +3 -0
  55. package/dist/interfaces/response.js.map +1 -0
  56. package/dist/metadataGeneration/tsoa.d.ts +219 -0
  57. package/dist/metadataGeneration/tsoa.js +3 -0
  58. package/dist/metadataGeneration/tsoa.js.map +1 -0
  59. package/dist/routeGeneration/additionalProps.d.ts +6 -0
  60. package/dist/routeGeneration/additionalProps.js +3 -0
  61. package/dist/routeGeneration/additionalProps.js.map +1 -0
  62. package/dist/routeGeneration/templateHelpers.d.ts +203 -0
  63. package/dist/routeGeneration/templateHelpers.js +859 -0
  64. package/dist/routeGeneration/templateHelpers.js.map +1 -0
  65. package/dist/routeGeneration/templates/express/expressTemplateService.d.ts +29 -0
  66. package/dist/routeGeneration/templates/express/expressTemplateService.js +118 -0
  67. package/dist/routeGeneration/templates/express/expressTemplateService.js.map +1 -0
  68. package/dist/routeGeneration/templates/hapi/hapiTemplateService.d.ts +36 -0
  69. package/dist/routeGeneration/templates/hapi/hapiTemplateService.js +120 -0
  70. package/dist/routeGeneration/templates/hapi/hapiTemplateService.js.map +1 -0
  71. package/dist/routeGeneration/templates/index.d.ts +4 -0
  72. package/dist/routeGeneration/templates/index.js +21 -0
  73. package/dist/routeGeneration/templates/index.js.map +1 -0
  74. package/dist/routeGeneration/templates/koa/koaTemplateService.d.ts +29 -0
  75. package/dist/routeGeneration/templates/koa/koaTemplateService.js +116 -0
  76. package/dist/routeGeneration/templates/koa/koaTemplateService.js.map +1 -0
  77. package/dist/routeGeneration/templates/templateService.d.ts +15 -0
  78. package/dist/routeGeneration/templates/templateService.js +37 -0
  79. package/dist/routeGeneration/templates/templateService.js.map +1 -0
  80. package/dist/routeGeneration/tsoa-route.d.ts +56 -0
  81. package/dist/routeGeneration/tsoa-route.js +11 -0
  82. package/dist/routeGeneration/tsoa-route.js.map +1 -0
  83. package/dist/swagger/swagger.d.ts +550 -0
  84. package/dist/swagger/swagger.js +12 -0
  85. package/dist/swagger/swagger.js.map +1 -0
  86. package/dist/utils/assertNever.d.ts +4 -0
  87. package/dist/utils/assertNever.js +10 -0
  88. package/dist/utils/assertNever.js.map +1 -0
  89. package/dist/utils/isHeaderType.d.ts +5 -0
  90. package/dist/utils/isHeaderType.js +3 -0
  91. package/dist/utils/isHeaderType.js.map +1 -0
  92. 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