@tsoa-next/runtime 7.2.1 → 7.3.2

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 (27) hide show
  1. package/README.MD +4 -4
  2. package/dist/config.d.ts +2 -2
  3. package/dist/decorators/validate.d.ts +9 -0
  4. package/dist/decorators/validate.js +112 -0
  5. package/dist/decorators/validate.js.map +1 -0
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.js +2 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/metadataGeneration/tsoa.d.ts +28 -0
  10. package/dist/routeGeneration/additionalProps.d.ts +2 -0
  11. package/dist/routeGeneration/externalValidation.d.ts +13 -0
  12. package/dist/routeGeneration/externalValidation.js +232 -0
  13. package/dist/routeGeneration/externalValidation.js.map +1 -0
  14. package/dist/routeGeneration/templateHelpers.d.ts +68 -8
  15. package/dist/routeGeneration/templateHelpers.js +231 -93
  16. package/dist/routeGeneration/templateHelpers.js.map +1 -1
  17. package/dist/routeGeneration/templates/express/expressTemplateService.d.ts +2 -0
  18. package/dist/routeGeneration/templates/express/expressTemplateService.js +12 -14
  19. package/dist/routeGeneration/templates/express/expressTemplateService.js.map +1 -1
  20. package/dist/routeGeneration/templates/hapi/hapiTemplateService.d.ts +2 -0
  21. package/dist/routeGeneration/templates/hapi/hapiTemplateService.js +10 -9
  22. package/dist/routeGeneration/templates/hapi/hapiTemplateService.js.map +1 -1
  23. package/dist/routeGeneration/templates/koa/koaTemplateService.d.ts +2 -0
  24. package/dist/routeGeneration/templates/koa/koaTemplateService.js +15 -16
  25. package/dist/routeGeneration/templates/koa/koaTemplateService.js.map +1 -1
  26. package/dist/routeGeneration/tsoa-route.d.ts +5 -0
  27. package/package.json +33 -4
@@ -6,10 +6,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.ValidateError = exports.ValidationService = void 0;
7
7
  exports.ValidateParam = ValidateParam;
8
8
  const validator_1 = __importDefault(require("validator"));
9
+ const validate_1 = require("../decorators/validate");
9
10
  const assertNever_1 = require("../utils/assertNever");
11
+ const externalValidation_1 = require("./externalValidation");
10
12
  const tsoa_route_1 = require("./tsoa-route");
11
- function ValidateParam(property, value, generatedModels, name = '', fieldErrors, isBodyParam, parent = '', config) {
12
- return new ValidationService(generatedModels, config).ValidateParam(property, value, name, fieldErrors, isBodyParam, parent);
13
+ function ValidateParam(property, value, generatedModels, name = '', fieldErrors, isBodyParam, parent = '', config, metadata) {
14
+ return new ValidationService(generatedModels, config).ValidateParam(property, value, name, fieldErrors, isBodyParam, parent, metadata);
13
15
  }
14
16
  class ValidationService {
15
17
  models;
@@ -22,36 +24,67 @@ class ValidationService {
22
24
  isRecord(value) {
23
25
  return typeof value === 'object' && value !== null && !Array.isArray(value);
24
26
  }
25
- ValidateParam(property, rawValue, name = '', fieldErrors, isBodyParam, parent = '') {
26
- let value = rawValue;
27
- // If undefined is allowed type, we can move to value validation
28
- if (value === undefined && property.dataType !== 'undefined') {
29
- // If there's either default value or datatype is union with undefined valid, we can just set it and move to validation
30
- if (property.default !== undefined || (property.dataType === 'union' && property.subSchemas?.some(p => p.dataType === 'undefined'))) {
31
- value = property.default;
32
- }
33
- else if (property.required) {
34
- // If value can be typed as undefined, there's no need to check mandatoriness here.
35
- let message = `'${name}' is required`;
36
- if (property.validators) {
37
- const validators = property.validators;
38
- Object.keys(validators).forEach((key) => {
39
- const errorMsg = validators[key]?.errorMsg;
40
- if (key.startsWith('is') && errorMsg) {
41
- message = errorMsg;
42
- }
43
- });
44
- }
45
- fieldErrors[parent + name] = {
46
- message,
47
- value,
48
- };
49
- return;
50
- }
51
- else {
52
- return value;
53
- }
27
+ buildChildPath(parent, name) {
28
+ const fieldPath = parent + name;
29
+ return fieldPath ? `${fieldPath}.` : '';
30
+ }
31
+ ValidateParam(property, rawValue, name, fieldErrors, isBodyParam, parent, metadata) {
32
+ const resolvedName = name ?? '';
33
+ const resolvedParent = parent ?? '';
34
+ const handledUndefined = this.handleUndefinedValue({
35
+ property,
36
+ rawValue,
37
+ name: resolvedName,
38
+ fieldErrors,
39
+ parent: resolvedParent,
40
+ });
41
+ if (handledUndefined.handled) {
42
+ return handledUndefined.value;
43
+ }
44
+ const value = handledUndefined.value;
45
+ if (property.validationStrategy === 'external' && property.externalValidator) {
46
+ return this.validateExternal(resolvedName, value, fieldErrors, property, resolvedParent, metadata);
47
+ }
48
+ return this.validateResolvedProperty({
49
+ property,
50
+ value,
51
+ name: resolvedName,
52
+ fieldErrors,
53
+ isBodyParam,
54
+ parent: resolvedParent,
55
+ metadata,
56
+ });
57
+ }
58
+ handleUndefinedValue({ property, rawValue, name, fieldErrors, parent }) {
59
+ if (rawValue !== undefined || property.dataType === 'undefined') {
60
+ return { handled: false, value: rawValue };
54
61
  }
62
+ if (property.default !== undefined || (property.dataType === 'union' && property.subSchemas?.some(p => p.dataType === 'undefined'))) {
63
+ return { handled: false, value: property.default };
64
+ }
65
+ if (property.required) {
66
+ fieldErrors[parent + name] = {
67
+ message: this.getRequiredFieldMessage(property.validators, name),
68
+ value: rawValue,
69
+ };
70
+ return { handled: true, value: rawValue };
71
+ }
72
+ return { handled: true, value: rawValue };
73
+ }
74
+ getRequiredFieldMessage(validators, name) {
75
+ let message = `'${name}' is required`;
76
+ if (!validators) {
77
+ return message;
78
+ }
79
+ Object.keys(validators).forEach((key) => {
80
+ const errorMsg = validators[key]?.errorMsg;
81
+ if (key.startsWith('is') && errorMsg) {
82
+ message = errorMsg;
83
+ }
84
+ });
85
+ return message;
86
+ }
87
+ validateResolvedProperty({ property, value, name, fieldErrors, isBodyParam, parent, metadata, }) {
55
88
  switch (property.dataType) {
56
89
  case 'string':
57
90
  return this.validateString(name, value, fieldErrors, property.validators, parent);
@@ -66,7 +99,16 @@ class ValidationService {
66
99
  case 'enum':
67
100
  return this.validateEnum(name, value, fieldErrors, property.enums, parent);
68
101
  case 'array':
69
- return this.validateArray(name, value, fieldErrors, isBodyParam, property.array, property.validators, parent);
102
+ return this.validateArray({
103
+ name,
104
+ value,
105
+ fieldErrors,
106
+ isBodyParam,
107
+ schema: property.array,
108
+ validators: property.validators,
109
+ parent,
110
+ metadata,
111
+ });
70
112
  case 'date':
71
113
  return this.validateDate(name, value, fieldErrors, isBodyParam, property.validators, parent);
72
114
  case 'datetime':
@@ -74,15 +116,24 @@ class ValidationService {
74
116
  case 'buffer':
75
117
  return this.validateBuffer(name, value, fieldErrors, parent);
76
118
  case 'union':
77
- return this.validateUnion(name, value, fieldErrors, isBodyParam, property, parent);
119
+ return this.validateUnion(name, value, fieldErrors, isBodyParam, property, parent, metadata);
78
120
  case 'intersection':
79
- return this.validateIntersection(name, value, fieldErrors, isBodyParam, property.subSchemas, parent);
121
+ return this.validateIntersection(name, value, fieldErrors, isBodyParam, property.subSchemas, parent, metadata);
80
122
  case 'undefined':
81
123
  return this.validateUndefined(name, value, fieldErrors, parent);
82
124
  case 'any':
83
125
  return value;
84
126
  case 'nestedObjectLiteral':
85
- return this.validateNestedObjectLiteral(name, value, fieldErrors, isBodyParam, property.nestedProperties, property.additionalProperties, parent);
127
+ return this.validateNestedObjectLiteral({
128
+ name,
129
+ value,
130
+ fieldErrors,
131
+ isBodyParam,
132
+ nestedProperties: property.nestedProperties,
133
+ additionalProperties: property.additionalProperties,
134
+ parent,
135
+ metadata,
136
+ });
86
137
  default:
87
138
  if (property.ref) {
88
139
  // Detect circular references to prevent stack overflow
@@ -92,7 +143,7 @@ class ValidationService {
92
143
  }
93
144
  this.validationStack.add(refPath);
94
145
  try {
95
- return this.validateModel({ name, value, modelDefinition: this.models[property.ref], fieldErrors, isBodyParam, parent });
146
+ return this.validateModel({ name, value, modelDefinition: this.models[property.ref], fieldErrors, isBodyParam, parent, metadata });
96
147
  }
97
148
  finally {
98
149
  this.validationStack.delete(refPath);
@@ -101,10 +152,72 @@ class ValidationService {
101
152
  return value;
102
153
  }
103
154
  }
155
+ validateExternal(name, rawValue, fieldErrors, property, parent, metadata) {
156
+ const value = rawValue === undefined && property.default !== undefined ? property.default : rawValue;
157
+ const runtimeMetadata = this.getRuntimeExternalValidatorMetadata(metadata, property);
158
+ const fieldPath = parent + name;
159
+ if (!runtimeMetadata) {
160
+ fieldErrors[fieldPath] = {
161
+ message: `Missing runtime schema metadata for external validator '${property.externalValidator?.kind || 'unknown'}' on '${fieldPath || '(anonymous parameter)'}'. Ensure the controller module is imported so decorators run, and ensure custom templates pass controllerClass, methodName, and parameterIndex into validation.`,
162
+ value,
163
+ };
164
+ return undefined;
165
+ }
166
+ const declaredKind = property.externalValidator?.kind;
167
+ const runtimeKind = runtimeMetadata.kind;
168
+ if (declaredKind && declaredKind !== runtimeKind) {
169
+ fieldErrors[fieldPath] = {
170
+ message: `External validator kind mismatch for '${fieldPath}'. Route schema expects '${declaredKind}' but runtime metadata provided '${runtimeKind}'.`,
171
+ value,
172
+ };
173
+ return undefined;
174
+ }
175
+ const kindToUse = declaredKind || runtimeKind;
176
+ const result = (0, externalValidation_1.validateExternalSchema)(kindToUse, runtimeMetadata.schema, value, this.config.validation ?? {});
177
+ if (result.ok) {
178
+ return result.value;
179
+ }
180
+ this.projectExternalFailureToFieldErrors(result.failure, fieldErrors, name, parent, value);
181
+ return undefined;
182
+ }
183
+ getRuntimeExternalValidatorMetadata(metadata, property) {
184
+ if (!metadata?.controllerClass || metadata.parameterIndex === undefined || !metadata.methodName || !property.externalValidator) {
185
+ return undefined;
186
+ }
187
+ const controllerTarget = metadata.controllerClass;
188
+ const candidateTargets = controllerTarget.prototype ? [controllerTarget.prototype, controllerTarget] : [controllerTarget];
189
+ for (const target of candidateTargets) {
190
+ const runtimeMetadata = (0, validate_1.getParameterExternalValidatorMetadata)(target, metadata.methodName, metadata.parameterIndex);
191
+ if (runtimeMetadata) {
192
+ return runtimeMetadata;
193
+ }
194
+ }
195
+ return undefined;
196
+ }
197
+ projectExternalFailureToFieldErrors(failure, fieldErrors, name, parent, value) {
198
+ if (failure.issues.length === 0) {
199
+ fieldErrors[parent + name] = {
200
+ message: failure.summaryMessage,
201
+ value,
202
+ };
203
+ return;
204
+ }
205
+ for (const issue of failure.issues) {
206
+ const baseFieldPath = parent + name;
207
+ const fieldPath = issue.path ? this.buildIssueFieldPath(baseFieldPath, issue.path) : baseFieldPath;
208
+ if (!fieldErrors[fieldPath]) {
209
+ fieldErrors[fieldPath] = {
210
+ message: issue.message || failure.summaryMessage,
211
+ value,
212
+ };
213
+ }
214
+ }
215
+ }
104
216
  hasCorrectJsType(value, type, isBodyParam) {
105
217
  return !isBodyParam || this.config.bodyCoercion || typeof value === type;
106
218
  }
107
- validateNestedObjectLiteral(name, value, fieldErrors, isBodyParam, nestedProperties, additionalProperties, parent) {
219
+ validateNestedObjectLiteral(...args) {
220
+ const { name, value, fieldErrors, isBodyParam, nestedProperties, additionalProperties, parent, metadata } = this.normalizeValidateNestedObjectLiteralArgs(args);
108
221
  if (!this.isRecord(value)) {
109
222
  fieldErrors[parent + name] = {
110
223
  message: `invalid object`,
@@ -117,7 +230,7 @@ class ValidationService {
117
230
  throw new Error('internal tsoa error: ' +
118
231
  'the metadata that was generated should have had nested property schemas since it’s for a nested object,' +
119
232
  'however it did not. ' +
120
- 'Please file an issue with tsoa at https://github.com/lukeautry/tsoa/issues');
233
+ 'Please file an issue with tsoa at https://github.com/tsoa-next/tsoa-next/issues');
121
234
  }
122
235
  const propHandling = this.config.noImplicitAdditionalProperties;
123
236
  if (propHandling !== 'ignore') {
@@ -136,8 +249,9 @@ class ValidationService {
136
249
  }
137
250
  }
138
251
  }
252
+ const childPath = this.buildChildPath(parent, name);
139
253
  Object.keys(nestedProperties).forEach(key => {
140
- const validatedProp = this.ValidateParam(nestedProperties[key], value[key], key, fieldErrors, isBodyParam, parent + name + '.');
254
+ const validatedProp = this.ValidateParam(nestedProperties[key], value[key], key, fieldErrors, isBodyParam, childPath, metadata);
141
255
  // Add value from validator if it's not undefined or if value is required and unfedined is valid type
142
256
  if (validatedProp !== undefined || (nestedProperties[key].dataType === 'undefined' && nestedProperties[key].required)) {
143
257
  value[key] = validatedProp;
@@ -146,7 +260,7 @@ class ValidationService {
146
260
  if (typeof additionalProperties === 'object') {
147
261
  const keys = Object.keys(value).filter(key => typeof nestedProperties[key] === 'undefined');
148
262
  keys.forEach(key => {
149
- const validatedProp = this.ValidateParam(additionalProperties, value[key], key, fieldErrors, isBodyParam, parent + name + '.');
263
+ const validatedProp = this.ValidateParam(additionalProperties, value[key], key, fieldErrors, isBodyParam, childPath, metadata);
150
264
  // Add value from validator if it's not undefined or if value is required and unfedined is valid type
151
265
  if (validatedProp !== undefined || (additionalProperties.dataType === 'undefined' && additionalProperties.required)) {
152
266
  value[key] = validatedProp;
@@ -158,6 +272,14 @@ class ValidationService {
158
272
  }
159
273
  return value;
160
274
  }
275
+ normalizeValidateNestedObjectLiteralArgs(args) {
276
+ if (typeof args[0] === 'string') {
277
+ const tupleArgs = args;
278
+ const [name, value, fieldErrors, isBodyParam, nestedProperties, additionalProperties, parent = '', metadata] = tupleArgs;
279
+ return { name, value, fieldErrors, isBodyParam, nestedProperties, additionalProperties, parent, metadata };
280
+ }
281
+ return args[0];
282
+ }
161
283
  validateInt(name, value, fieldErrors, isBodyParam, validators, parent = '') {
162
284
  if (!this.hasCorrectJsType(value, 'number', isBodyParam) || !validator_1.default.isInt(String(value))) {
163
285
  let message = `invalid integer number`;
@@ -440,65 +562,79 @@ class ValidationService {
440
562
  };
441
563
  return;
442
564
  }
443
- validateArray(name, value, fieldErrors, isBodyParam, schema, validators, parent = '') {
444
- if ((isBodyParam && this.config.bodyCoercion === false && !Array.isArray(value)) || !schema || value === undefined) {
445
- const message = validators && validators.isArray && validators.isArray.errorMsg ? validators.isArray.errorMsg : `invalid array`;
446
- fieldErrors[parent + name] = {
565
+ validateArray(...args) {
566
+ const options = this.normalizeValidateArrayArgs(args);
567
+ const { name, value: resolvedValue, fieldErrors: resolvedFieldErrors, isBodyParam: resolvedIsBodyParam, schema: resolvedSchema, validators: resolvedValidators, parent: resolvedParent = '', metadata: resolvedMetadata, } = options;
568
+ if ((resolvedIsBodyParam && this.config.bodyCoercion === false && !Array.isArray(resolvedValue)) || !resolvedSchema || resolvedValue === undefined) {
569
+ const message = resolvedValidators?.isArray?.errorMsg || `invalid array`;
570
+ resolvedFieldErrors[resolvedParent + name] = {
447
571
  message,
448
- value,
572
+ value: resolvedValue,
449
573
  };
450
574
  return;
451
575
  }
452
576
  let arrayValue = [];
453
- const previousErrors = Object.keys(fieldErrors).length;
454
- if (Array.isArray(value)) {
455
- arrayValue = value.map((elementValue, index) => {
456
- const validatedElement = this.ValidateParam(schema, elementValue, `$${index}`, fieldErrors, isBodyParam, name + '.');
577
+ const previousErrors = Object.keys(resolvedFieldErrors).length;
578
+ const childParent = this.buildChildPath(resolvedParent, name);
579
+ if (Array.isArray(resolvedValue)) {
580
+ arrayValue = resolvedValue.map((elementValue, index) => {
581
+ const validatedElement = this.ValidateParam(resolvedSchema, elementValue, `$${index}`, resolvedFieldErrors, resolvedIsBodyParam, childParent, resolvedMetadata);
457
582
  return validatedElement;
458
583
  });
459
584
  }
460
585
  else {
461
- const validatedElement = this.ValidateParam(schema, value, '$0', fieldErrors, isBodyParam, name + '.');
586
+ const validatedElement = this.ValidateParam(resolvedSchema, resolvedValue, '$0', resolvedFieldErrors, resolvedIsBodyParam, childParent, resolvedMetadata);
462
587
  arrayValue = [validatedElement];
463
588
  }
464
- if (Object.keys(fieldErrors).length > previousErrors) {
589
+ if (Object.keys(resolvedFieldErrors).length > previousErrors) {
590
+ return;
591
+ }
592
+ const validatorError = this.getArrayValidatorError(resolvedValidators, arrayValue, resolvedValue);
593
+ if (validatorError) {
594
+ resolvedFieldErrors[resolvedParent + name] = validatorError;
465
595
  return;
466
596
  }
597
+ return arrayValue;
598
+ }
599
+ normalizeValidateArrayArgs(args) {
600
+ if (typeof args[0] === 'string') {
601
+ const [name, value, fieldErrors, isBodyParam, schema, validators, parent = '', metadata] = args;
602
+ return { name, value, fieldErrors, isBodyParam, schema, validators, parent, metadata };
603
+ }
604
+ return args[0];
605
+ }
606
+ getArrayValidatorError(validators, arrayValue, originalValue) {
467
607
  if (!validators) {
468
- return arrayValue;
608
+ return undefined;
469
609
  }
470
- if (validators.minItems && validators.minItems.value) {
471
- if (validators.minItems.value > arrayValue.length) {
472
- fieldErrors[parent + name] = {
473
- message: validators.minItems.errorMsg || `minItems ${validators.minItems.value}`,
474
- value,
475
- };
476
- return;
477
- }
610
+ if (validators.minItems?.value && validators.minItems.value > arrayValue.length) {
611
+ return {
612
+ message: validators.minItems.errorMsg || `minItems ${validators.minItems.value}`,
613
+ value: originalValue,
614
+ };
478
615
  }
479
- if (validators.maxItems && validators.maxItems.value) {
480
- if (validators.maxItems.value < arrayValue.length) {
481
- fieldErrors[parent + name] = {
482
- message: validators.maxItems.errorMsg || `maxItems ${validators.maxItems.value}`,
483
- value,
484
- };
485
- return;
486
- }
616
+ if (validators.maxItems?.value && validators.maxItems.value < arrayValue.length) {
617
+ return {
618
+ message: validators.maxItems.errorMsg || `maxItems ${validators.maxItems.value}`,
619
+ value: originalValue,
620
+ };
487
621
  }
488
- if (validators.uniqueItems) {
489
- const unique = arrayValue.some((elem, index, arr) => {
490
- const indexOf = arr.indexOf(elem);
491
- return indexOf > -1 && indexOf !== index;
492
- });
493
- if (unique) {
494
- fieldErrors[parent + name] = {
495
- message: validators.uniqueItems.errorMsg || `required unique array`,
496
- value,
497
- };
498
- return;
499
- }
622
+ if (validators.uniqueItems && this.hasDuplicateArrayItems(arrayValue)) {
623
+ return {
624
+ message: validators.uniqueItems.errorMsg || `required unique array`,
625
+ value: originalValue,
626
+ };
500
627
  }
501
- return arrayValue;
628
+ return undefined;
629
+ }
630
+ hasDuplicateArrayItems(arrayValue) {
631
+ return arrayValue.some((elem, index, arr) => {
632
+ const indexOf = arr.indexOf(elem);
633
+ return indexOf > -1 && indexOf !== index;
634
+ });
635
+ }
636
+ buildIssueFieldPath(baseFieldPath, issuePath) {
637
+ return baseFieldPath ? `${baseFieldPath}.${issuePath}` : issuePath;
502
638
  }
503
639
  validateBuffer(name, value, fieldErrors, parent = '') {
504
640
  if (Buffer.isBuffer(value)) {
@@ -516,11 +652,11 @@ class ValidationService {
516
652
  };
517
653
  return;
518
654
  }
519
- validateUnion(name, value, fieldErrors, isBodyParam, property, parent = '') {
655
+ validateUnion(name, value, fieldErrors, isBodyParam, property, parent = '', metadata) {
520
656
  if (!property.subSchemas) {
521
657
  throw new Error('internal tsoa error: ' +
522
658
  'the metadata that was generated should have had sub schemas since it’s for a union, however it did not. ' +
523
- 'Please file an issue with tsoa at https://github.com/lukeautry/tsoa/issues');
659
+ 'Please file an issue with tsoa at https://github.com/tsoa-next/tsoa-next/issues');
524
660
  }
525
661
  const subFieldErrors = [];
526
662
  for (const subSchema of property.subSchemas) {
@@ -528,7 +664,7 @@ class ValidationService {
528
664
  // Clean value if it's not undefined or use undefined directly if it's undefined.
529
665
  // Value can be undefined if undefined is allowed datatype of the union
530
666
  const validateableValue = value !== undefined ? this.deepClone(value) : value;
531
- const cleanValue = this.ValidateParam({ ...subSchema, validators: { ...property.validators, ...subSchema.validators } }, validateableValue, name, subFieldError, isBodyParam, parent);
667
+ const cleanValue = this.ValidateParam({ ...subSchema, validators: { ...property.validators, ...subSchema.validators } }, validateableValue, name, subFieldError, isBodyParam, parent, metadata);
532
668
  subFieldErrors.push(subFieldError);
533
669
  if (Object.keys(subFieldError).length === 0) {
534
670
  return cleanValue;
@@ -537,11 +673,11 @@ class ValidationService {
537
673
  this.addSummarizedError(fieldErrors, parent + name, 'Could not match the union against any of the items. Issues: ', subFieldErrors, value);
538
674
  return;
539
675
  }
540
- validateIntersection(name, value, fieldErrors, isBodyParam, subSchemas, parent = '') {
676
+ validateIntersection(name, value, fieldErrors, isBodyParam, subSchemas, parent = '', metadata) {
541
677
  if (!subSchemas) {
542
678
  throw new Error('internal tsoa error: ' +
543
679
  'the metadata that was generated should have had sub schemas since it’s for a intersection, however it did not. ' +
544
- 'Please file an issue with tsoa at https://github.com/lukeautry/tsoa/issues');
680
+ 'Please file an issue with tsoa at https://github.com/tsoa-next/tsoa-next/issues');
545
681
  }
546
682
  const subFieldErrors = [];
547
683
  let cleanValues = {};
@@ -549,7 +685,7 @@ class ValidationService {
549
685
  const subFieldError = {};
550
686
  const cleanValue = this.createChildValidationService({
551
687
  noImplicitAdditionalProperties: 'silently-remove-extras',
552
- }).ValidateParam(subSchema, this.deepClone(value), name, subFieldError, isBodyParam, parent);
688
+ }).ValidateParam(subSchema, this.deepClone(value), name, subFieldError, isBodyParam, parent, metadata);
553
689
  if (this.isRecord(cleanValue)) {
554
690
  cleanValues = {
555
691
  ...cleanValues,
@@ -574,6 +710,7 @@ class ValidationService {
574
710
  modelDefinition: schema,
575
711
  fieldErrors: requiredPropError,
576
712
  isBodyParam,
713
+ metadata,
577
714
  });
578
715
  return requiredPropError;
579
716
  };
@@ -699,16 +836,17 @@ class ValidationService {
699
836
  }
700
837
  }
701
838
  validateModel(input) {
702
- const { name, value, modelDefinition, fieldErrors, isBodyParam, parent = '' } = input;
839
+ const { name, value, modelDefinition, fieldErrors, isBodyParam, parent = '', metadata } = input;
703
840
  const previousErrors = Object.keys(fieldErrors).length;
704
841
  if (modelDefinition) {
705
842
  if (modelDefinition.dataType === 'refEnum') {
706
843
  return this.validateEnum(name, value, fieldErrors, modelDefinition.enums, parent);
707
844
  }
708
845
  if (modelDefinition.dataType === 'refAlias') {
709
- return this.ValidateParam(modelDefinition.type, value, name, fieldErrors, isBodyParam, parent);
846
+ return this.ValidateParam(modelDefinition.type, value, name, fieldErrors, isBodyParam, parent, metadata);
710
847
  }
711
848
  const fieldPath = parent + name;
849
+ const childPath = this.buildChildPath(parent, name);
712
850
  if (!this.isRecord(value)) {
713
851
  fieldErrors[fieldPath] = {
714
852
  message: `invalid object`,
@@ -720,7 +858,7 @@ class ValidationService {
720
858
  const keysOnPropertiesModelDefinition = new Set(Object.keys(properties));
721
859
  const allPropertiesOnData = new Set(Object.keys(value));
722
860
  Object.entries(properties).forEach(([key, property]) => {
723
- const validatedParam = this.ValidateParam(property, value[key], key, fieldErrors, isBodyParam, fieldPath + '.');
861
+ const validatedParam = this.ValidateParam(property, value[key], key, fieldErrors, isBodyParam, childPath, metadata);
724
862
  // Add value from validator if it's not undefined or if value is required and unfedined is valid type
725
863
  if (validatedParam !== undefined || (property.dataType === 'undefined' && property.required)) {
726
864
  value[key] = validatedParam;
@@ -737,7 +875,7 @@ class ValidationService {
737
875
  Object.keys(value).forEach((key) => {
738
876
  if (isAnExcessProperty(key)) {
739
877
  if (this.config.noImplicitAdditionalProperties === 'throw-on-extras') {
740
- fieldErrors[`${fieldPath}.${key}`] = {
878
+ fieldErrors[`${childPath}${key}`] = {
741
879
  message: `"${key}" is an excess property and therefore is not allowed`,
742
880
  value: key,
743
881
  };
@@ -757,13 +895,13 @@ class ValidationService {
757
895
  else {
758
896
  Object.keys(value).forEach((key) => {
759
897
  if (isAnExcessProperty(key)) {
760
- const validatedValue = this.ValidateParam(additionalProperties, value[key], key, fieldErrors, isBodyParam, fieldPath + '.');
898
+ const validatedValue = this.ValidateParam(additionalProperties, value[key], key, fieldErrors, isBodyParam, childPath, metadata);
761
899
  // Add value from validator if it's not undefined or if value is required and unfedined is valid type
762
900
  if (validatedValue !== undefined || (additionalProperties.dataType === 'undefined' && additionalProperties.required)) {
763
901
  value[key] = validatedValue;
764
902
  }
765
903
  else {
766
- fieldErrors[`${fieldPath}.${key}`] = {
904
+ fieldErrors[`${childPath}${key}`] = {
767
905
  message: `No matching model found in additionalProperties to validate ${key}`,
768
906
  value: key,
769
907
  };