@tsoa-next/runtime 7.1.0 → 7.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.MD +8 -2
- package/dist/config.d.ts +11 -4
- package/dist/decorators/example.d.ts +1 -1
- package/dist/decorators/example.js +1 -1
- package/dist/decorators/example.js.map +1 -1
- package/dist/decorators/methods.d.ts +7 -7
- package/dist/decorators/methods.js +7 -7
- package/dist/decorators/methods.js.map +1 -1
- package/dist/decorators/middlewares.d.ts +2 -1
- package/dist/decorators/middlewares.js +9 -6
- package/dist/decorators/middlewares.js.map +1 -1
- package/dist/decorators/operationid.d.ts +1 -1
- package/dist/decorators/operationid.js +1 -1
- package/dist/decorators/operationid.js.map +1 -1
- package/dist/decorators/parameter.d.ts +9 -9
- package/dist/decorators/parameter.js +9 -9
- package/dist/decorators/parameter.js.map +1 -1
- package/dist/decorators/response.d.ts +3 -3
- package/dist/decorators/response.js +3 -3
- package/dist/decorators/response.js.map +1 -1
- package/dist/decorators/route.d.ts +1 -1
- package/dist/decorators/route.js +1 -1
- package/dist/decorators/route.js.map +1 -1
- package/dist/decorators/security.d.ts +2 -2
- package/dist/decorators/security.js +1 -1
- package/dist/decorators/security.js.map +1 -1
- package/dist/decorators/tags.d.ts +1 -1
- package/dist/decorators/tags.js +1 -1
- package/dist/decorators/tags.js.map +1 -1
- 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 +13 -11
- package/dist/index.js +13 -11
- package/dist/index.js.map +1 -1
- package/dist/interfaces/iocModule.d.ts +4 -2
- package/dist/interfaces/response.d.ts +1 -1
- package/dist/metadataGeneration/tsoa.d.ts +41 -9
- 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 +98 -21
- package/dist/routeGeneration/templateHelpers.js +305 -113
- package/dist/routeGeneration/templateHelpers.js.map +1 -1
- package/dist/routeGeneration/templates/express/expressTemplateService.d.ts +8 -4
- package/dist/routeGeneration/templates/express/expressTemplateService.js +32 -15
- package/dist/routeGeneration/templates/express/expressTemplateService.js.map +1 -1
- package/dist/routeGeneration/templates/hapi/hapiTemplateService.d.ts +22 -8
- package/dist/routeGeneration/templates/hapi/hapiTemplateService.js +67 -19
- package/dist/routeGeneration/templates/hapi/hapiTemplateService.js.map +1 -1
- package/dist/routeGeneration/templates/index.d.ts +1 -1
- package/dist/routeGeneration/templates/index.js +1 -1
- package/dist/routeGeneration/templates/index.js.map +1 -1
- package/dist/routeGeneration/templates/koa/koaTemplateService.d.ts +15 -6
- package/dist/routeGeneration/templates/koa/koaTemplateService.js +63 -22
- package/dist/routeGeneration/templates/koa/koaTemplateService.js.map +1 -1
- package/dist/routeGeneration/templates/templateService.d.ts +9 -4
- package/dist/routeGeneration/templates/templateService.js +39 -1
- package/dist/routeGeneration/templates/templateService.js.map +1 -1
- package/dist/routeGeneration/tsoa-route.d.ts +5 -0
- package/dist/swagger/swagger.d.ts +42 -41
- package/dist/swagger/swagger.js +4 -0
- package/dist/swagger/swagger.js.map +1 -1
- package/package.json +35 -6
|
@@ -6,11 +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
|
-
|
|
12
|
-
|
|
13
|
-
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);
|
|
14
15
|
}
|
|
15
16
|
class ValidationService {
|
|
16
17
|
models;
|
|
@@ -20,36 +21,70 @@ class ValidationService {
|
|
|
20
21
|
this.models = models;
|
|
21
22
|
this.config = config;
|
|
22
23
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
};
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
return value;
|
|
51
|
-
}
|
|
24
|
+
isRecord(value) {
|
|
25
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
26
|
+
}
|
|
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);
|
|
52
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 };
|
|
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, }) {
|
|
53
88
|
switch (property.dataType) {
|
|
54
89
|
case 'string':
|
|
55
90
|
return this.validateString(name, value, fieldErrors, property.validators, parent);
|
|
@@ -64,23 +99,41 @@ class ValidationService {
|
|
|
64
99
|
case 'enum':
|
|
65
100
|
return this.validateEnum(name, value, fieldErrors, property.enums, parent);
|
|
66
101
|
case 'array':
|
|
67
|
-
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
|
+
});
|
|
68
112
|
case 'date':
|
|
69
113
|
return this.validateDate(name, value, fieldErrors, isBodyParam, property.validators, parent);
|
|
70
114
|
case 'datetime':
|
|
71
115
|
return this.validateDateTime(name, value, fieldErrors, isBodyParam, property.validators, parent);
|
|
72
116
|
case 'buffer':
|
|
73
|
-
return this.validateBuffer(name, value);
|
|
117
|
+
return this.validateBuffer(name, value, fieldErrors, parent);
|
|
74
118
|
case 'union':
|
|
75
|
-
return this.validateUnion(name, value, fieldErrors, isBodyParam, property, parent);
|
|
119
|
+
return this.validateUnion(name, value, fieldErrors, isBodyParam, property, parent, metadata);
|
|
76
120
|
case 'intersection':
|
|
77
|
-
return this.validateIntersection(name, value, fieldErrors, isBodyParam, property.subSchemas, parent);
|
|
121
|
+
return this.validateIntersection(name, value, fieldErrors, isBodyParam, property.subSchemas, parent, metadata);
|
|
78
122
|
case 'undefined':
|
|
79
123
|
return this.validateUndefined(name, value, fieldErrors, parent);
|
|
80
124
|
case 'any':
|
|
81
125
|
return value;
|
|
82
126
|
case 'nestedObjectLiteral':
|
|
83
|
-
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
|
+
});
|
|
84
137
|
default:
|
|
85
138
|
if (property.ref) {
|
|
86
139
|
// Detect circular references to prevent stack overflow
|
|
@@ -90,7 +143,7 @@ class ValidationService {
|
|
|
90
143
|
}
|
|
91
144
|
this.validationStack.add(refPath);
|
|
92
145
|
try {
|
|
93
|
-
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 });
|
|
94
147
|
}
|
|
95
148
|
finally {
|
|
96
149
|
this.validationStack.delete(refPath);
|
|
@@ -99,11 +152,73 @@ class ValidationService {
|
|
|
99
152
|
return value;
|
|
100
153
|
}
|
|
101
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
|
+
}
|
|
102
216
|
hasCorrectJsType(value, type, isBodyParam) {
|
|
103
217
|
return !isBodyParam || this.config.bodyCoercion || typeof value === type;
|
|
104
218
|
}
|
|
105
|
-
validateNestedObjectLiteral(
|
|
106
|
-
|
|
219
|
+
validateNestedObjectLiteral(...args) {
|
|
220
|
+
const { name, value, fieldErrors, isBodyParam, nestedProperties, additionalProperties, parent, metadata } = this.normalizeValidateNestedObjectLiteralArgs(args);
|
|
221
|
+
if (!this.isRecord(value)) {
|
|
107
222
|
fieldErrors[parent + name] = {
|
|
108
223
|
message: `invalid object`,
|
|
109
224
|
value,
|
|
@@ -115,7 +230,7 @@ class ValidationService {
|
|
|
115
230
|
throw new Error('internal tsoa error: ' +
|
|
116
231
|
'the metadata that was generated should have had nested property schemas since it’s for a nested object,' +
|
|
117
232
|
'however it did not. ' +
|
|
118
|
-
'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');
|
|
119
234
|
}
|
|
120
235
|
const propHandling = this.config.noImplicitAdditionalProperties;
|
|
121
236
|
if (propHandling !== 'ignore') {
|
|
@@ -134,17 +249,18 @@ class ValidationService {
|
|
|
134
249
|
}
|
|
135
250
|
}
|
|
136
251
|
}
|
|
252
|
+
const childPath = this.buildChildPath(parent, name);
|
|
137
253
|
Object.keys(nestedProperties).forEach(key => {
|
|
138
|
-
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);
|
|
139
255
|
// Add value from validator if it's not undefined or if value is required and unfedined is valid type
|
|
140
256
|
if (validatedProp !== undefined || (nestedProperties[key].dataType === 'undefined' && nestedProperties[key].required)) {
|
|
141
257
|
value[key] = validatedProp;
|
|
142
258
|
}
|
|
143
259
|
});
|
|
144
|
-
if (typeof additionalProperties === 'object'
|
|
260
|
+
if (typeof additionalProperties === 'object') {
|
|
145
261
|
const keys = Object.keys(value).filter(key => typeof nestedProperties[key] === 'undefined');
|
|
146
262
|
keys.forEach(key => {
|
|
147
|
-
const validatedProp = this.ValidateParam(additionalProperties, value[key], key, fieldErrors, isBodyParam,
|
|
263
|
+
const validatedProp = this.ValidateParam(additionalProperties, value[key], key, fieldErrors, isBodyParam, childPath, metadata);
|
|
148
264
|
// Add value from validator if it's not undefined or if value is required and unfedined is valid type
|
|
149
265
|
if (validatedProp !== undefined || (additionalProperties.dataType === 'undefined' && additionalProperties.required)) {
|
|
150
266
|
value[key] = validatedProp;
|
|
@@ -156,6 +272,14 @@ class ValidationService {
|
|
|
156
272
|
}
|
|
157
273
|
return value;
|
|
158
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
|
+
}
|
|
159
283
|
validateInt(name, value, fieldErrors, isBodyParam, validators, parent = '') {
|
|
160
284
|
if (!this.hasCorrectJsType(value, 'number', isBodyParam) || !validator_1.default.isInt(String(value))) {
|
|
161
285
|
let message = `invalid integer number`;
|
|
@@ -186,6 +310,15 @@ class ValidationService {
|
|
|
186
310
|
return;
|
|
187
311
|
}
|
|
188
312
|
}
|
|
313
|
+
if (validators.exclusiveMinimum && validators.exclusiveMinimum.value !== undefined) {
|
|
314
|
+
if (validators.exclusiveMinimum.value >= numberValue) {
|
|
315
|
+
fieldErrors[parent + name] = {
|
|
316
|
+
message: validators.exclusiveMinimum.errorMsg || `exclusiveMin ${validators.exclusiveMinimum.value}`,
|
|
317
|
+
value,
|
|
318
|
+
};
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
189
322
|
if (validators.maximum && validators.maximum.value !== undefined) {
|
|
190
323
|
if (validators.maximum.value < numberValue) {
|
|
191
324
|
fieldErrors[parent + name] = {
|
|
@@ -195,6 +328,15 @@ class ValidationService {
|
|
|
195
328
|
return;
|
|
196
329
|
}
|
|
197
330
|
}
|
|
331
|
+
if (validators.exclusiveMaximum && validators.exclusiveMaximum.value !== undefined) {
|
|
332
|
+
if (validators.exclusiveMaximum.value <= numberValue) {
|
|
333
|
+
fieldErrors[parent + name] = {
|
|
334
|
+
message: validators.exclusiveMaximum.errorMsg || `exclusiveMax ${validators.exclusiveMaximum.value}`,
|
|
335
|
+
value,
|
|
336
|
+
};
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
198
340
|
return numberValue;
|
|
199
341
|
}
|
|
200
342
|
validateFloat(name, value, fieldErrors, isBodyParam, validators, parent = '') {
|
|
@@ -227,6 +369,15 @@ class ValidationService {
|
|
|
227
369
|
return;
|
|
228
370
|
}
|
|
229
371
|
}
|
|
372
|
+
if (validators.exclusiveMinimum && validators.exclusiveMinimum.value !== undefined) {
|
|
373
|
+
if (validators.exclusiveMinimum.value >= numberValue) {
|
|
374
|
+
fieldErrors[parent + name] = {
|
|
375
|
+
message: validators.exclusiveMinimum.errorMsg || `exclusiveMin ${validators.exclusiveMinimum.value}`,
|
|
376
|
+
value,
|
|
377
|
+
};
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
230
381
|
if (validators.maximum && validators.maximum.value !== undefined) {
|
|
231
382
|
if (validators.maximum.value < numberValue) {
|
|
232
383
|
fieldErrors[parent + name] = {
|
|
@@ -236,6 +387,15 @@ class ValidationService {
|
|
|
236
387
|
return;
|
|
237
388
|
}
|
|
238
389
|
}
|
|
390
|
+
if (validators.exclusiveMaximum && validators.exclusiveMaximum.value !== undefined) {
|
|
391
|
+
if (validators.exclusiveMaximum.value <= numberValue) {
|
|
392
|
+
fieldErrors[parent + name] = {
|
|
393
|
+
message: validators.exclusiveMaximum.errorMsg || `exclusiveMax ${validators.exclusiveMaximum.value}`,
|
|
394
|
+
value,
|
|
395
|
+
};
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
239
399
|
return numberValue;
|
|
240
400
|
}
|
|
241
401
|
validateEnum(name, value, fieldErrors, members, parent = '') {
|
|
@@ -402,72 +562,101 @@ class ValidationService {
|
|
|
402
562
|
};
|
|
403
563
|
return;
|
|
404
564
|
}
|
|
405
|
-
validateArray(
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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] = {
|
|
409
571
|
message,
|
|
410
|
-
value,
|
|
572
|
+
value: resolvedValue,
|
|
411
573
|
};
|
|
412
574
|
return;
|
|
413
575
|
}
|
|
414
576
|
let arrayValue = [];
|
|
415
|
-
const previousErrors = Object.keys(
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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);
|
|
582
|
+
return validatedElement;
|
|
419
583
|
});
|
|
420
584
|
}
|
|
421
585
|
else {
|
|
422
|
-
|
|
586
|
+
const validatedElement = this.ValidateParam(resolvedSchema, resolvedValue, '$0', resolvedFieldErrors, resolvedIsBodyParam, childParent, resolvedMetadata);
|
|
587
|
+
arrayValue = [validatedElement];
|
|
423
588
|
}
|
|
424
|
-
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;
|
|
425
595
|
return;
|
|
426
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) {
|
|
427
607
|
if (!validators) {
|
|
428
|
-
return
|
|
608
|
+
return undefined;
|
|
429
609
|
}
|
|
430
|
-
if (validators.minItems && validators.minItems.value) {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
};
|
|
436
|
-
return;
|
|
437
|
-
}
|
|
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
|
+
};
|
|
438
615
|
}
|
|
439
|
-
if (validators.maxItems && validators.maxItems.value) {
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
};
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
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
|
+
};
|
|
447
621
|
}
|
|
448
|
-
if (validators.uniqueItems) {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
}
|
|
453
|
-
if (unique) {
|
|
454
|
-
fieldErrors[parent + name] = {
|
|
455
|
-
message: validators.uniqueItems.errorMsg || `required unique array`,
|
|
456
|
-
value,
|
|
457
|
-
};
|
|
458
|
-
return;
|
|
459
|
-
}
|
|
622
|
+
if (validators.uniqueItems && this.hasDuplicateArrayItems(arrayValue)) {
|
|
623
|
+
return {
|
|
624
|
+
message: validators.uniqueItems.errorMsg || `required unique array`,
|
|
625
|
+
value: originalValue,
|
|
626
|
+
};
|
|
460
627
|
}
|
|
461
|
-
return
|
|
628
|
+
return undefined;
|
|
462
629
|
}
|
|
463
|
-
|
|
464
|
-
return
|
|
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;
|
|
638
|
+
}
|
|
639
|
+
validateBuffer(name, value, fieldErrors, parent = '') {
|
|
640
|
+
if (Buffer.isBuffer(value)) {
|
|
641
|
+
return value;
|
|
642
|
+
}
|
|
643
|
+
if (typeof value === 'string') {
|
|
644
|
+
return Buffer.from(value);
|
|
645
|
+
}
|
|
646
|
+
if (value instanceof Uint8Array) {
|
|
647
|
+
return Buffer.from(value);
|
|
648
|
+
}
|
|
649
|
+
fieldErrors[parent + name] = {
|
|
650
|
+
message: 'invalid buffer value',
|
|
651
|
+
value,
|
|
652
|
+
};
|
|
653
|
+
return;
|
|
465
654
|
}
|
|
466
|
-
validateUnion(name, value, fieldErrors, isBodyParam, property, parent = '') {
|
|
655
|
+
validateUnion(name, value, fieldErrors, isBodyParam, property, parent = '', metadata) {
|
|
467
656
|
if (!property.subSchemas) {
|
|
468
657
|
throw new Error('internal tsoa error: ' +
|
|
469
658
|
'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/
|
|
659
|
+
'Please file an issue with tsoa at https://github.com/tsoa-next/tsoa-next/issues');
|
|
471
660
|
}
|
|
472
661
|
const subFieldErrors = [];
|
|
473
662
|
for (const subSchema of property.subSchemas) {
|
|
@@ -475,7 +664,7 @@ class ValidationService {
|
|
|
475
664
|
// Clean value if it's not undefined or use undefined directly if it's undefined.
|
|
476
665
|
// Value can be undefined if undefined is allowed datatype of the union
|
|
477
666
|
const validateableValue = value !== undefined ? this.deepClone(value) : value;
|
|
478
|
-
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);
|
|
479
668
|
subFieldErrors.push(subFieldError);
|
|
480
669
|
if (Object.keys(subFieldError).length === 0) {
|
|
481
670
|
return cleanValue;
|
|
@@ -484,11 +673,11 @@ class ValidationService {
|
|
|
484
673
|
this.addSummarizedError(fieldErrors, parent + name, 'Could not match the union against any of the items. Issues: ', subFieldErrors, value);
|
|
485
674
|
return;
|
|
486
675
|
}
|
|
487
|
-
validateIntersection(name, value, fieldErrors, isBodyParam, subSchemas, parent = '') {
|
|
676
|
+
validateIntersection(name, value, fieldErrors, isBodyParam, subSchemas, parent = '', metadata) {
|
|
488
677
|
if (!subSchemas) {
|
|
489
678
|
throw new Error('internal tsoa error: ' +
|
|
490
679
|
'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/
|
|
680
|
+
'Please file an issue with tsoa at https://github.com/tsoa-next/tsoa-next/issues');
|
|
492
681
|
}
|
|
493
682
|
const subFieldErrors = [];
|
|
494
683
|
let cleanValues = {};
|
|
@@ -496,11 +685,13 @@ class ValidationService {
|
|
|
496
685
|
const subFieldError = {};
|
|
497
686
|
const cleanValue = this.createChildValidationService({
|
|
498
687
|
noImplicitAdditionalProperties: 'silently-remove-extras',
|
|
499
|
-
}).ValidateParam(subSchema, this.deepClone(value), name, subFieldError, isBodyParam, parent);
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
688
|
+
}).ValidateParam(subSchema, this.deepClone(value), name, subFieldError, isBodyParam, parent, metadata);
|
|
689
|
+
if (this.isRecord(cleanValue)) {
|
|
690
|
+
cleanValues = {
|
|
691
|
+
...cleanValues,
|
|
692
|
+
...cleanValue,
|
|
693
|
+
};
|
|
694
|
+
}
|
|
504
695
|
subFieldErrors.push(subFieldError);
|
|
505
696
|
});
|
|
506
697
|
const filtered = subFieldErrors.filter(subFieldError => Object.keys(subFieldError).length !== 0);
|
|
@@ -519,12 +710,13 @@ class ValidationService {
|
|
|
519
710
|
modelDefinition: schema,
|
|
520
711
|
fieldErrors: requiredPropError,
|
|
521
712
|
isBodyParam,
|
|
713
|
+
metadata,
|
|
522
714
|
});
|
|
523
715
|
return requiredPropError;
|
|
524
716
|
};
|
|
525
717
|
const schemasWithRequiredProps = schemas.filter(schema => Object.keys(getRequiredPropError(schema)).length === 0);
|
|
526
718
|
if (this.config.noImplicitAdditionalProperties === 'ignore') {
|
|
527
|
-
return { ...value, ...cleanValues };
|
|
719
|
+
return this.isRecord(value) ? { ...value, ...cleanValues } : cleanValues;
|
|
528
720
|
}
|
|
529
721
|
if (this.config.noImplicitAdditionalProperties === 'silently-remove-extras') {
|
|
530
722
|
if (schemasWithRequiredProps.length > 0) {
|
|
@@ -538,7 +730,7 @@ class ValidationService {
|
|
|
538
730
|
return;
|
|
539
731
|
}
|
|
540
732
|
}
|
|
541
|
-
if (schemasWithRequiredProps.length > 0 && schemasWithRequiredProps.some(schema => this.getExcessPropertiesFor(schema, Object.keys(value)).length === 0)) {
|
|
733
|
+
if (this.isRecord(value) && schemasWithRequiredProps.length > 0 && schemasWithRequiredProps.some(schema => this.getExcessPropertiesFor(schema, Object.keys(value)).length === 0)) {
|
|
542
734
|
return cleanValues;
|
|
543
735
|
}
|
|
544
736
|
else {
|
|
@@ -644,17 +836,18 @@ class ValidationService {
|
|
|
644
836
|
}
|
|
645
837
|
}
|
|
646
838
|
validateModel(input) {
|
|
647
|
-
const { name, value, modelDefinition, fieldErrors, isBodyParam, parent = '' } = input;
|
|
839
|
+
const { name, value, modelDefinition, fieldErrors, isBodyParam, parent = '', metadata } = input;
|
|
648
840
|
const previousErrors = Object.keys(fieldErrors).length;
|
|
649
841
|
if (modelDefinition) {
|
|
650
842
|
if (modelDefinition.dataType === 'refEnum') {
|
|
651
843
|
return this.validateEnum(name, value, fieldErrors, modelDefinition.enums, parent);
|
|
652
844
|
}
|
|
653
845
|
if (modelDefinition.dataType === 'refAlias') {
|
|
654
|
-
return this.ValidateParam(modelDefinition.type, value, name, fieldErrors, isBodyParam, parent);
|
|
846
|
+
return this.ValidateParam(modelDefinition.type, value, name, fieldErrors, isBodyParam, parent, metadata);
|
|
655
847
|
}
|
|
656
848
|
const fieldPath = parent + name;
|
|
657
|
-
|
|
849
|
+
const childPath = this.buildChildPath(parent, name);
|
|
850
|
+
if (!this.isRecord(value)) {
|
|
658
851
|
fieldErrors[fieldPath] = {
|
|
659
852
|
message: `invalid object`,
|
|
660
853
|
value,
|
|
@@ -665,7 +858,7 @@ class ValidationService {
|
|
|
665
858
|
const keysOnPropertiesModelDefinition = new Set(Object.keys(properties));
|
|
666
859
|
const allPropertiesOnData = new Set(Object.keys(value));
|
|
667
860
|
Object.entries(properties).forEach(([key, property]) => {
|
|
668
|
-
const validatedParam = this.ValidateParam(property, value[key], key, fieldErrors, isBodyParam,
|
|
861
|
+
const validatedParam = this.ValidateParam(property, value[key], key, fieldErrors, isBodyParam, childPath, metadata);
|
|
669
862
|
// Add value from validator if it's not undefined or if value is required and unfedined is valid type
|
|
670
863
|
if (validatedParam !== undefined || (property.dataType === 'undefined' && property.required)) {
|
|
671
864
|
value[key] = validatedParam;
|
|
@@ -682,7 +875,7 @@ class ValidationService {
|
|
|
682
875
|
Object.keys(value).forEach((key) => {
|
|
683
876
|
if (isAnExcessProperty(key)) {
|
|
684
877
|
if (this.config.noImplicitAdditionalProperties === 'throw-on-extras') {
|
|
685
|
-
fieldErrors[`${
|
|
878
|
+
fieldErrors[`${childPath}${key}`] = {
|
|
686
879
|
message: `"${key}" is an excess property and therefore is not allowed`,
|
|
687
880
|
value: key,
|
|
688
881
|
};
|
|
@@ -702,13 +895,13 @@ class ValidationService {
|
|
|
702
895
|
else {
|
|
703
896
|
Object.keys(value).forEach((key) => {
|
|
704
897
|
if (isAnExcessProperty(key)) {
|
|
705
|
-
const validatedValue = this.ValidateParam(additionalProperties, value[key], key, fieldErrors, isBodyParam,
|
|
898
|
+
const validatedValue = this.ValidateParam(additionalProperties, value[key], key, fieldErrors, isBodyParam, childPath, metadata);
|
|
706
899
|
// Add value from validator if it's not undefined or if value is required and unfedined is valid type
|
|
707
900
|
if (validatedValue !== undefined || (additionalProperties.dataType === 'undefined' && additionalProperties.required)) {
|
|
708
901
|
value[key] = validatedValue;
|
|
709
902
|
}
|
|
710
903
|
else {
|
|
711
|
-
fieldErrors[`${
|
|
904
|
+
fieldErrors[`${childPath}${key}`] = {
|
|
712
905
|
message: `No matching model found in additionalProperties to validate ${key}`,
|
|
713
906
|
value: key,
|
|
714
907
|
};
|
|
@@ -754,14 +947,13 @@ class ValidationService {
|
|
|
754
947
|
return new Date(obj.getTime());
|
|
755
948
|
}
|
|
756
949
|
if (obj instanceof RegExp) {
|
|
757
|
-
|
|
950
|
+
// Preserve the existing instance instead of reconstructing a pattern from untrusted data.
|
|
951
|
+
return obj;
|
|
758
952
|
}
|
|
759
|
-
if (obj
|
|
760
|
-
const
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
}
|
|
764
|
-
return cloneArr;
|
|
953
|
+
if (Array.isArray(obj)) {
|
|
954
|
+
const arrayValues = obj;
|
|
955
|
+
const clonedArray = arrayValues.map(value => this.deepClone(value));
|
|
956
|
+
return clonedArray;
|
|
765
957
|
}
|
|
766
958
|
if (Buffer && obj instanceof Buffer) {
|
|
767
959
|
return Buffer.from(obj);
|