@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.
- package/README.MD +4 -4
- package/dist/config.d.ts +2 -2
- package/dist/decorators/validate.d.ts +9 -0
- package/dist/decorators/validate.js +112 -0
- package/dist/decorators/validate.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/metadataGeneration/tsoa.d.ts +28 -0
- package/dist/routeGeneration/additionalProps.d.ts +2 -0
- package/dist/routeGeneration/externalValidation.d.ts +13 -0
- package/dist/routeGeneration/externalValidation.js +232 -0
- package/dist/routeGeneration/externalValidation.js.map +1 -0
- package/dist/routeGeneration/templateHelpers.d.ts +68 -8
- package/dist/routeGeneration/templateHelpers.js +231 -93
- package/dist/routeGeneration/templateHelpers.js.map +1 -1
- package/dist/routeGeneration/templates/express/expressTemplateService.d.ts +2 -0
- package/dist/routeGeneration/templates/express/expressTemplateService.js +12 -14
- package/dist/routeGeneration/templates/express/expressTemplateService.js.map +1 -1
- package/dist/routeGeneration/templates/hapi/hapiTemplateService.d.ts +2 -0
- package/dist/routeGeneration/templates/hapi/hapiTemplateService.js +10 -9
- package/dist/routeGeneration/templates/hapi/hapiTemplateService.js.map +1 -1
- package/dist/routeGeneration/templates/koa/koaTemplateService.d.ts +2 -0
- package/dist/routeGeneration/templates/koa/koaTemplateService.js +15 -16
- package/dist/routeGeneration/templates/koa/koaTemplateService.js.map +1 -1
- package/dist/routeGeneration/tsoa-route.d.ts +5 -0
- 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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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/
|
|
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,
|
|
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,
|
|
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(
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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(
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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(
|
|
586
|
+
const validatedElement = this.ValidateParam(resolvedSchema, resolvedValue, '$0', resolvedFieldErrors, resolvedIsBodyParam, childParent, resolvedMetadata);
|
|
462
587
|
arrayValue = [validatedElement];
|
|
463
588
|
}
|
|
464
|
-
if (Object.keys(
|
|
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
|
|
608
|
+
return undefined;
|
|
469
609
|
}
|
|
470
|
-
if (validators.minItems && validators.minItems.value) {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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
|
|
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/
|
|
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/
|
|
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,
|
|
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[`${
|
|
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,
|
|
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[`${
|
|
904
|
+
fieldErrors[`${childPath}${key}`] = {
|
|
767
905
|
message: `No matching model found in additionalProperties to validate ${key}`,
|
|
768
906
|
value: key,
|
|
769
907
|
};
|