From 62f4d67bd5b9d747948384df6cedcbedada6c85a Mon Sep 17 00:00:00 2001 From: Christian Friedow Date: Mon, 11 Mar 2024 09:14:15 +0100 Subject: [PATCH 1/8] test: if the validateArray function throws if a non-array value is provided --- tests/unit/swagger/templateHelpers.spec.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/unit/swagger/templateHelpers.spec.ts b/tests/unit/swagger/templateHelpers.spec.ts index 29616ca15..2941467b6 100644 --- a/tests/unit/swagger/templateHelpers.spec.ts +++ b/tests/unit/swagger/templateHelpers.spec.ts @@ -810,6 +810,16 @@ describe('ValidationService', () => { expect(error[`${name}.$2`].value).to.equal(true); }); + it('should throw if a non-array value is provided', () => { + const name = 'name'; + const value: any = 'some primitive string'; + const error: FieldErrors = {}; + const result = new ValidationService({}).validateArray(name, value, error, { noImplicitAdditionalProperties: 'ignore' }, { dataType: 'string' }); + expect(result).to.deep.equal(undefined); + expect(error[name].message).to.equal('invalid array'); + expect(error[name].value).to.equal('some primitive string'); + }); + it('should invalid array nested value', () => { const name = 'name'; const value = [{ a: 123 }, { a: 'bcd' }]; From 83153a6ae4f09073ece5e0971bf0fc60327abda5 Mon Sep 17 00:00:00 2001 From: Christian Friedow Date: Mon, 11 Mar 2024 09:14:48 +0100 Subject: [PATCH 2/8] feat: add body coercion config parameter --- packages/cli/src/cli.ts | 5 +++++ .../cli/src/routeGeneration/routeGenerator.ts | 11 +++++++---- packages/runtime/src/config.ts | 10 ++++++++-- .../src/routeGeneration/additionalProps.ts | 5 ----- .../src/routeGeneration/templateHelpers.ts | 18 +++++++++--------- .../src/routeGeneration/validationConfig.ts | 6 ++++++ 6 files changed, 35 insertions(+), 20 deletions(-) delete mode 100644 packages/runtime/src/routeGeneration/additionalProps.ts create mode 100644 packages/runtime/src/routeGeneration/validationConfig.ts diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index a1b3a31e8..5f2f551b9 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -175,6 +175,7 @@ type RouteGeneratorImpl = new (metadata: Tsoa.Metadata, options: ExtendedRoutesC export interface ExtendedRoutesConfig extends RoutesConfig { entryFile: Config['entryFile']; noImplicitAdditionalProperties: Exclude; + bodyCoercion: Exclude; controllerPathGlobs?: Config['controllerPathGlobs']; multerOpts?: Config['multerOpts']; rootSecurity?: Config['spec']['rootSecurity']; @@ -201,12 +202,16 @@ const validateRoutesConfig = async (config: Config): Promise { - constructor(protected readonly metadata: Tsoa.Metadata, protected readonly options: Config) {} + constructor( + protected readonly metadata: Tsoa.Metadata, + protected readonly options: Config, + ) {} /** * This is the entrypoint for a generator to create a custom set of routes @@ -103,8 +106,8 @@ export abstract class AbstractRouteGenerator 0, - uploadFileName: uploadFilesWithDifferentFieldParameter.map((parameter) => ({ - 'name': parameter.name, + uploadFileName: uploadFilesWithDifferentFieldParameter.map(parameter => ({ + name: parameter.name, })), uploadFiles: !!uploadFilesParameter, uploadFilesName: uploadFilesParameter?.name, @@ -119,7 +122,7 @@ export abstract class AbstractRouteGenerator controller.methods.some( diff --git a/packages/runtime/src/config.ts b/packages/runtime/src/config.ts index 09682b2c9..eeac0f12c 100644 --- a/packages/runtime/src/config.ts +++ b/packages/runtime/src/config.ts @@ -50,12 +50,11 @@ export interface Config { */ multerOpts?: MulterOpts; - /* * OpenAPI number type to be used for TypeScript's 'number', when there isn't a type annotation * @default double */ - defaultNumberType?: 'double' | 'float' | 'integer' | 'long' + defaultNumberType?: 'double' | 'float' | 'integer' | 'long'; } /** @@ -258,4 +257,11 @@ export interface RoutesConfig { * @default false */ esm?: boolean; + + /* + * Whether to implicitly coerce body parameters into an accepted type. + * + * @default true + */ + bodyCoercion?: boolean; } diff --git a/packages/runtime/src/routeGeneration/additionalProps.ts b/packages/runtime/src/routeGeneration/additionalProps.ts deleted file mode 100644 index e8c6c0a08..000000000 --- a/packages/runtime/src/routeGeneration/additionalProps.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Config } from '../config'; - -export interface AdditionalProps { - noImplicitAdditionalProperties: Exclude; -} diff --git a/packages/runtime/src/routeGeneration/templateHelpers.ts b/packages/runtime/src/routeGeneration/templateHelpers.ts index e8c548967..b9b042577 100644 --- a/packages/runtime/src/routeGeneration/templateHelpers.ts +++ b/packages/runtime/src/routeGeneration/templateHelpers.ts @@ -1,19 +1,19 @@ import validator from 'validator'; import { assertNever } from '../utils/assertNever'; -import { AdditionalProps } from './additionalProps'; +import { ValidationConfig } from './validationConfig'; import { TsoaRoute, isDefaultForAdditionalPropertiesAllowed } from './tsoa-route'; import { Tsoa } from '../metadataGeneration/tsoa'; import ValidatorKey = Tsoa.ValidatorKey; // for backwards compatibility with custom templates -export function ValidateParam(property: TsoaRoute.PropertySchema, value: any, generatedModels: TsoaRoute.Models, name = '', fieldErrors: FieldErrors, parent = '', swaggerConfig: AdditionalProps) { +export function ValidateParam(property: TsoaRoute.PropertySchema, value: any, generatedModels: TsoaRoute.Models, name = '', fieldErrors: FieldErrors, parent = '', swaggerConfig: ValidationConfig) { return new ValidationService(generatedModels).ValidateParam(property, value, name, fieldErrors, parent, swaggerConfig); } export class ValidationService { constructor(private readonly models: TsoaRoute.Models) {} - public ValidateParam(property: TsoaRoute.PropertySchema, rawValue: any, name = '', fieldErrors: FieldErrors, parent = '', minimalSwaggerConfig: AdditionalProps) { + public ValidateParam(property: TsoaRoute.PropertySchema, rawValue: any, name = '', fieldErrors: FieldErrors, parent = '', minimalSwaggerConfig: ValidationConfig) { let value = rawValue; // If undefined is allowed type, we can move to value validation if (value === undefined && property.dataType !== 'undefined') { @@ -85,7 +85,7 @@ export class ValidationService { name: string, value: any, fieldErrors: FieldErrors, - swaggerConfig: AdditionalProps, + swaggerConfig: ValidationConfig, nestedProperties: { [name: string]: TsoaRoute.PropertySchema } | undefined, additionalProperties: TsoaRoute.PropertySchema | boolean | undefined, parent: string, @@ -416,7 +416,7 @@ export class ValidationService { return; } - public validateArray(name: string, value: any[], fieldErrors: FieldErrors, swaggerConfig: AdditionalProps, schema?: TsoaRoute.PropertySchema, validators?: ArrayValidator, parent = '') { + public validateArray(name: string, value: any[], fieldErrors: FieldErrors, swaggerConfig: ValidationConfig, schema?: TsoaRoute.PropertySchema, validators?: ArrayValidator, parent = '') { if (!schema || value === undefined) { const message = validators && validators.isArray && validators.isArray.errorMsg ? validators.isArray.errorMsg : `invalid array`; fieldErrors[parent + name] = { @@ -481,7 +481,7 @@ export class ValidationService { return Buffer.from(value); } - public validateUnion(name: string, value: any, fieldErrors: FieldErrors, swaggerConfig: AdditionalProps, property: TsoaRoute.PropertySchema, parent = ''): any { + public validateUnion(name: string, value: any, fieldErrors: FieldErrors, swaggerConfig: ValidationConfig, property: TsoaRoute.PropertySchema, parent = ''): any { if (!property.subSchemas) { throw new Error( 'internal tsoa error: ' + @@ -513,7 +513,7 @@ export class ValidationService { return; } - public validateIntersection(name: string, value: any, fieldErrors: FieldErrors, swaggerConfig: AdditionalProps, subSchemas: TsoaRoute.PropertySchema[] | undefined, parent = ''): any { + public validateIntersection(name: string, value: any, fieldErrors: FieldErrors, swaggerConfig: ValidationConfig, subSchemas: TsoaRoute.PropertySchema[] | undefined, parent = ''): any { if (!subSchemas) { throw new Error( 'internal tsoa error: ' + @@ -673,7 +673,7 @@ export class ValidationService { return { dataType: 'refObject', properties: { ...a.properties, ...b.properties }, additionalProperties: a.additionalProperties || b.additionalProperties || false }; } - private getExcessPropertiesFor(modelDefinition: TsoaRoute.RefObjectModelSchema, properties: string[], config: AdditionalProps): string[] { + private getExcessPropertiesFor(modelDefinition: TsoaRoute.RefObjectModelSchema, properties: string[], config: ValidationConfig): string[] { const modelProperties = new Set(Object.keys(modelDefinition.properties)); if (modelDefinition.additionalProperties) { @@ -685,7 +685,7 @@ export class ValidationService { } } - public validateModel(input: { name: string; value: any; modelDefinition: TsoaRoute.ModelSchema; fieldErrors: FieldErrors; parent?: string; minimalSwaggerConfig: AdditionalProps }): any { + public validateModel(input: { name: string; value: any; modelDefinition: TsoaRoute.ModelSchema; fieldErrors: FieldErrors; parent?: string; minimalSwaggerConfig: ValidationConfig }): any { const { name, value, modelDefinition, fieldErrors, parent = '', minimalSwaggerConfig: swaggerConfig } = input; const previousErrors = Object.keys(fieldErrors).length; diff --git a/packages/runtime/src/routeGeneration/validationConfig.ts b/packages/runtime/src/routeGeneration/validationConfig.ts new file mode 100644 index 000000000..f93173bbd --- /dev/null +++ b/packages/runtime/src/routeGeneration/validationConfig.ts @@ -0,0 +1,6 @@ +import { Config, RoutesConfig } from '../config'; + +export interface ValidationConfig { + noImplicitAdditionalProperties: Exclude; + bodyCoercion: Exclude; +} From 266ae0c56809bfde165a3208bedc7cb7a80898c7 Mon Sep 17 00:00:00 2001 From: Christian Friedow Date: Mon, 11 Mar 2024 09:15:20 +0100 Subject: [PATCH 3/8] test: fix tests --- ...validationConfig.ts => additionalProps.ts} | 2 +- .../src/routeGeneration/templateHelpers.ts | 73 ++++---- tests/esm/prepare.ts | 1 + .../unit/templating/routeGenerator.spec.ts | 1 + .../serverlessRouteGenerator.ts | 12 +- tests/prepare.ts | 15 ++ tests/unit/swagger/templateHelpers.spec.ts | 164 ++++++++++++++---- tests/unit/templating/routeGenerator.spec.ts | 5 + tests/unit/templating/templateHelpers.spec.ts | 9 + 9 files changed, 206 insertions(+), 76 deletions(-) rename packages/runtime/src/routeGeneration/{validationConfig.ts => additionalProps.ts} (85%) diff --git a/packages/runtime/src/routeGeneration/validationConfig.ts b/packages/runtime/src/routeGeneration/additionalProps.ts similarity index 85% rename from packages/runtime/src/routeGeneration/validationConfig.ts rename to packages/runtime/src/routeGeneration/additionalProps.ts index f93173bbd..c19667b02 100644 --- a/packages/runtime/src/routeGeneration/validationConfig.ts +++ b/packages/runtime/src/routeGeneration/additionalProps.ts @@ -1,6 +1,6 @@ import { Config, RoutesConfig } from '../config'; -export interface ValidationConfig { +export interface AdditionalProps { noImplicitAdditionalProperties: Exclude; bodyCoercion: Exclude; } diff --git a/packages/runtime/src/routeGeneration/templateHelpers.ts b/packages/runtime/src/routeGeneration/templateHelpers.ts index b9b042577..529c2ebbc 100644 --- a/packages/runtime/src/routeGeneration/templateHelpers.ts +++ b/packages/runtime/src/routeGeneration/templateHelpers.ts @@ -1,19 +1,19 @@ import validator from 'validator'; import { assertNever } from '../utils/assertNever'; -import { ValidationConfig } from './validationConfig'; +import { AdditionalProps } from './additionalProps'; import { TsoaRoute, isDefaultForAdditionalPropertiesAllowed } from './tsoa-route'; import { Tsoa } from '../metadataGeneration/tsoa'; import ValidatorKey = Tsoa.ValidatorKey; // for backwards compatibility with custom templates -export function ValidateParam(property: TsoaRoute.PropertySchema, value: any, generatedModels: TsoaRoute.Models, name = '', fieldErrors: FieldErrors, parent = '', swaggerConfig: ValidationConfig) { +export function ValidateParam(property: TsoaRoute.PropertySchema, value: any, generatedModels: TsoaRoute.Models, name = '', fieldErrors: FieldErrors, parent = '', swaggerConfig: AdditionalProps) { return new ValidationService(generatedModels).ValidateParam(property, value, name, fieldErrors, parent, swaggerConfig); } export class ValidationService { constructor(private readonly models: TsoaRoute.Models) {} - public ValidateParam(property: TsoaRoute.PropertySchema, rawValue: any, name = '', fieldErrors: FieldErrors, parent = '', minimalSwaggerConfig: ValidationConfig) { + public ValidateParam(property: TsoaRoute.PropertySchema, rawValue: any, name = '', fieldErrors: FieldErrors, parent = '', minimalSwaggerConfig: AdditionalProps) { let value = rawValue; // If undefined is allowed type, we can move to value validation if (value === undefined && property.dataType !== 'undefined') { @@ -46,21 +46,21 @@ export class ValidationService { case 'string': return this.validateString(name, value, fieldErrors, property.validators as StringValidator, parent); case 'boolean': - return this.validateBool(name, value, fieldErrors, property.validators as BooleanValidator, parent); + return this.validateBool(name, value, fieldErrors, minimalSwaggerConfig, property.validators as BooleanValidator, parent); case 'integer': case 'long': - return this.validateInt(name, value, fieldErrors, property.validators as IntegerValidator, parent); + return this.validateInt(name, value, fieldErrors, minimalSwaggerConfig, property.validators as IntegerValidator, parent); case 'float': case 'double': - return this.validateFloat(name, value, fieldErrors, property.validators as FloatValidator, parent); + return this.validateFloat(name, value, fieldErrors, minimalSwaggerConfig, property.validators as FloatValidator, parent); case 'enum': return this.validateEnum(name, value, fieldErrors, property.enums, parent); case 'array': return this.validateArray(name, value, fieldErrors, minimalSwaggerConfig, property.array, property.validators as ArrayValidator, parent); case 'date': - return this.validateDate(name, value, fieldErrors, property.validators as DateValidator, parent); + return this.validateDate(name, value, fieldErrors, minimalSwaggerConfig, property.validators as DateValidator, parent); case 'datetime': - return this.validateDateTime(name, value, fieldErrors, property.validators as DateTimeValidator, parent); + return this.validateDateTime(name, value, fieldErrors, minimalSwaggerConfig, property.validators as DateTimeValidator, parent); case 'buffer': return this.validateBuffer(name, value); case 'union': @@ -85,7 +85,7 @@ export class ValidationService { name: string, value: any, fieldErrors: FieldErrors, - swaggerConfig: ValidationConfig, + swaggerConfig: AdditionalProps, nestedProperties: { [name: string]: TsoaRoute.PropertySchema } | undefined, additionalProperties: TsoaRoute.PropertySchema | boolean | undefined, parent: string, @@ -154,8 +154,8 @@ export class ValidationService { return value; } - public validateInt(name: string, value: any, fieldErrors: FieldErrors, validators?: IntegerValidator, parent = '') { - if (!validator.isInt(String(value))) { + public validateInt(name: string, value: any, fieldErrors: FieldErrors, swaggerConfig: AdditionalProps, validators?: IntegerValidator, parent = '') { + if ((swaggerConfig.bodyCoercion === false && typeof value !== 'number') || !validator.isInt(String(value))) { let message = `invalid integer number`; if (validators) { if (validators.isInt && validators.isInt.errorMsg) { @@ -197,8 +197,8 @@ export class ValidationService { return numberValue; } - public validateFloat(name: string, value: any, fieldErrors: FieldErrors, validators?: FloatValidator, parent = '') { - if (!validator.isFloat(String(value))) { + public validateFloat(name: string, value: any, fieldErrors: FieldErrors, swaggerConfig: AdditionalProps, validators?: FloatValidator, parent = '') { + if ((swaggerConfig.bodyCoercion === false && typeof value !== 'number') || !validator.isFloat(String(value))) { let message = 'invalid float number'; if (validators) { if (validators.isFloat && validators.isFloat.errorMsg) { @@ -263,8 +263,8 @@ export class ValidationService { return members[enumMatchIndex]; } - public validateDate(name: string, value: any, fieldErrors: FieldErrors, validators?: DateValidator, parent = '') { - if (!validator.isISO8601(String(value), { strict: true })) { + public validateDate(name: string, value: any, fieldErrors: FieldErrors, swaggerConfig: AdditionalProps, validators?: DateValidator, parent = '') { + if ((swaggerConfig.bodyCoercion === false && typeof value !== 'string') || !validator.isISO8601(String(value), { strict: true })) { const message = validators && validators.isDate && validators.isDate.errorMsg ? validators.isDate.errorMsg : `invalid ISO 8601 date format, i.e. YYYY-MM-DD`; fieldErrors[parent + name] = { message, @@ -300,8 +300,8 @@ export class ValidationService { return dateValue; } - public validateDateTime(name: string, value: any, fieldErrors: FieldErrors, validators?: DateTimeValidator, parent = '') { - if (!validator.isISO8601(String(value), { strict: true })) { + public validateDateTime(name: string, value: any, fieldErrors: FieldErrors, swaggerConfig: AdditionalProps, validators?: DateTimeValidator, parent = '') { + if ((swaggerConfig.bodyCoercion === false && typeof value !== 'string') || !validator.isISO8601(String(value), { strict: true })) { const message = validators && validators.isDateTime && validators.isDateTime.errorMsg ? validators.isDateTime.errorMsg : `invalid ISO 8601 datetime format, i.e. YYYY-MM-DDTHH:mm:ss`; fieldErrors[parent + name] = { message, @@ -381,18 +381,21 @@ export class ValidationService { return stringValue; } - public validateBool(name: string, value: any, fieldErrors: FieldErrors, validators?: BooleanValidator, parent = '') { - if (value === undefined || value === null) { - return false; - } + public validateBool(name: string, value: any, fieldErrors: FieldErrors, swaggerConfig: AdditionalProps, validators?: BooleanValidator, parent = '') { if (value === true || value === false) { return value; } - if (String(value).toLowerCase() === 'true') { - return true; - } - if (String(value).toLowerCase() === 'false') { - return false; + + if (swaggerConfig.bodyCoercion === true) { + if (value === undefined || value === null) { + return false; + } + if (String(value).toLowerCase() === 'true') { + return true; + } + if (String(value).toLowerCase() === 'false') { + return false; + } } const message = validators && validators.isBoolean && validators.isBoolean.errorMsg ? validators.isBoolean.errorMsg : `invalid boolean value`; @@ -416,8 +419,8 @@ export class ValidationService { return; } - public validateArray(name: string, value: any[], fieldErrors: FieldErrors, swaggerConfig: ValidationConfig, schema?: TsoaRoute.PropertySchema, validators?: ArrayValidator, parent = '') { - if (!schema || value === undefined) { + public validateArray(name: string, value: any[], fieldErrors: FieldErrors, swaggerConfig: AdditionalProps, schema?: TsoaRoute.PropertySchema, validators?: ArrayValidator, parent = '') { + if ((swaggerConfig.bodyCoercion === false && !Array.isArray(value)) || !schema || value === undefined) { const message = validators && validators.isArray && validators.isArray.errorMsg ? validators.isArray.errorMsg : `invalid array`; fieldErrors[parent + name] = { message, @@ -481,7 +484,7 @@ export class ValidationService { return Buffer.from(value); } - public validateUnion(name: string, value: any, fieldErrors: FieldErrors, swaggerConfig: ValidationConfig, property: TsoaRoute.PropertySchema, parent = ''): any { + public validateUnion(name: string, value: any, fieldErrors: FieldErrors, swaggerConfig: AdditionalProps, property: TsoaRoute.PropertySchema, parent = ''): any { if (!property.subSchemas) { throw new Error( 'internal tsoa error: ' + @@ -513,7 +516,7 @@ export class ValidationService { return; } - public validateIntersection(name: string, value: any, fieldErrors: FieldErrors, swaggerConfig: ValidationConfig, subSchemas: TsoaRoute.PropertySchema[] | undefined, parent = ''): any { + public validateIntersection(name: string, value: any, fieldErrors: FieldErrors, swaggerConfig: AdditionalProps, subSchemas: TsoaRoute.PropertySchema[] | undefined, parent = ''): any { if (!subSchemas) { throw new Error( 'internal tsoa error: ' + @@ -527,7 +530,10 @@ export class ValidationService { subSchemas.forEach(subSchema => { const subFieldError: FieldErrors = {}; - const cleanValue = this.ValidateParam(subSchema, JSON.parse(JSON.stringify(value)), name, subFieldError, parent, { noImplicitAdditionalProperties: 'silently-remove-extras' }); + const cleanValue = this.ValidateParam(subSchema, JSON.parse(JSON.stringify(value)), name, subFieldError, parent, { + noImplicitAdditionalProperties: 'silently-remove-extras', + bodyCoercion: swaggerConfig.bodyCoercion, + }); cleanValues = { ...cleanValues, ...cleanValue, @@ -556,6 +562,7 @@ export class ValidationService { fieldErrors: requiredPropError, minimalSwaggerConfig: { noImplicitAdditionalProperties: 'ignore', + bodyCoercion: swaggerConfig.bodyCoercion, }, }); return requiredPropError; @@ -673,7 +680,7 @@ export class ValidationService { return { dataType: 'refObject', properties: { ...a.properties, ...b.properties }, additionalProperties: a.additionalProperties || b.additionalProperties || false }; } - private getExcessPropertiesFor(modelDefinition: TsoaRoute.RefObjectModelSchema, properties: string[], config: ValidationConfig): string[] { + private getExcessPropertiesFor(modelDefinition: TsoaRoute.RefObjectModelSchema, properties: string[], config: AdditionalProps): string[] { const modelProperties = new Set(Object.keys(modelDefinition.properties)); if (modelDefinition.additionalProperties) { @@ -685,7 +692,7 @@ export class ValidationService { } } - public validateModel(input: { name: string; value: any; modelDefinition: TsoaRoute.ModelSchema; fieldErrors: FieldErrors; parent?: string; minimalSwaggerConfig: ValidationConfig }): any { + public validateModel(input: { name: string; value: any; modelDefinition: TsoaRoute.ModelSchema; fieldErrors: FieldErrors; parent?: string; minimalSwaggerConfig: AdditionalProps }): any { const { name, value, modelDefinition, fieldErrors, parent = '', minimalSwaggerConfig: swaggerConfig } = input; const previousErrors = Object.keys(fieldErrors).length; diff --git a/tests/esm/prepare.ts b/tests/esm/prepare.ts index dad3d0213..5041e0c8e 100644 --- a/tests/esm/prepare.ts +++ b/tests/esm/prepare.ts @@ -29,6 +29,7 @@ const log = async (label: string, fn: () => Promise) => { generateRoutes( { noImplicitAdditionalProperties: 'silently-remove-extras', + bodyCoercion: true, basePath: '/v1', entryFile: './fixtures/express/server.ts', middleware: 'express', diff --git a/tests/esm/unit/templating/routeGenerator.spec.ts b/tests/esm/unit/templating/routeGenerator.spec.ts index 833bc726d..b9ba35eb2 100644 --- a/tests/esm/unit/templating/routeGenerator.spec.ts +++ b/tests/esm/unit/templating/routeGenerator.spec.ts @@ -14,6 +14,7 @@ describe('RouteGenerator', () => { const routesConfig: ExtendedRoutesConfig = { entryFile: 'index.ts', noImplicitAdditionalProperties: 'silently-remove-extras', + bodyCoercion: true, routesDir: 'dist/routes', controllerPathGlobs: ['fixtures/controllers/*.ts'], routeGenerator: DummyRouteGenerator, diff --git a/tests/fixtures/custom/custom-route-generator/serverlessRouteGenerator.ts b/tests/fixtures/custom/custom-route-generator/serverlessRouteGenerator.ts index 82c647875..7fa59a99e 100644 --- a/tests/fixtures/custom/custom-route-generator/serverlessRouteGenerator.ts +++ b/tests/fixtures/custom/custom-route-generator/serverlessRouteGenerator.ts @@ -71,19 +71,19 @@ export default class ServerlessRouteGenerator extends AbstractRouteGenerator { // This would need to generate a CDK "Stack" that takes the tsoa metadata as input and generates a valid serverless CDK infrastructure stack from template const templateFileName = this.options.stackTemplate; const fileName = `${this.options.routesDir}/stack.ts`; const context = this.buildContext() as unknown as any; - context.controllers = context.controllers.map((controller) => { - controller.actions = controller.actions.map((action) => { + context.controllers = context.controllers.map(controller => { + controller.actions = controller.actions.map(action => { return { ...action, - handlerFolderName:`${this.options.routesDir}/${controller.name}` - } + handlerFolderName: `${this.options.routesDir}/${controller.name}`, + }; }); return controller; }); @@ -95,7 +95,7 @@ export default class ServerlessRouteGenerator extends AbstractRouteGenerator(label: string, fn: () => Promise) => { generateRoutes( { noImplicitAdditionalProperties: 'silently-remove-extras', + bodyCoercion: true, authenticationModule: './fixtures/express/authentication.ts', basePath: '/v1', entryFile: './fixtures/express/server.ts', @@ -44,6 +45,7 @@ const log = async (label: string, fn: () => Promise) => { generateRoutes( { noImplicitAdditionalProperties: 'silently-remove-extras', + bodyCoercion: true, authenticationModule: './fixtures/express-router/authentication.ts', entryFile: './fixtures/express-router/server.ts', middleware: 'express', @@ -58,6 +60,7 @@ const log = async (label: string, fn: () => Promise) => { generateRoutes( { noImplicitAdditionalProperties: 'throw-on-extras', + bodyCoercion: true, authenticationModule: './fixtures/express-openapi3/authentication.ts', basePath: '/v1', entryFile: './fixtures/server.ts', @@ -73,6 +76,7 @@ const log = async (label: string, fn: () => Promise) => { generateRoutes( { noImplicitAdditionalProperties: 'silently-remove-extras', + bodyCoercion: true, authenticationModule: './fixtures/express/authentication.ts', basePath: '/v1', controllerPathGlobs: ['./fixtures/controllers/*'], @@ -88,6 +92,7 @@ const log = async (label: string, fn: () => Promise) => { log('Express Route Generation, rootSecurity', () => generateRoutes({ noImplicitAdditionalProperties: 'silently-remove-extras', + bodyCoercion: true, authenticationModule: './fixtures/express/authentication.ts', basePath: '/v1', entryFile: './fixtures/express-root-security/server.ts', @@ -99,6 +104,7 @@ const log = async (label: string, fn: () => Promise) => { log('Koa Route Generation', () => generateRoutes({ noImplicitAdditionalProperties: 'silently-remove-extras', + bodyCoercion: true, authenticationModule: './fixtures/koa/authentication.ts', basePath: '/v1', entryFile: './fixtures/koa/server.ts', @@ -110,6 +116,7 @@ const log = async (label: string, fn: () => Promise) => { log('Koa Route Generation (with multerOpts)', () => generateRoutes({ noImplicitAdditionalProperties: 'silently-remove-extras', + bodyCoercion: true, basePath: '/v1', entryFile: './fixtures/koa-multer-options/server.ts', middleware: 'koa', @@ -122,6 +129,7 @@ const log = async (label: string, fn: () => Promise) => { log('Koa Route Generation (but noImplicitAdditionalProperties is set to "throw-on-extras")', () => generateRoutes({ noImplicitAdditionalProperties: 'throw-on-extras', + bodyCoercion: true, authenticationModule: './fixtures/koaNoAdditional/authentication.ts', basePath: '/v1', entryFile: './fixtures/koaNoAdditional/server.ts', @@ -132,6 +140,7 @@ const log = async (label: string, fn: () => Promise) => { log('Hapi Route Generation', () => generateRoutes({ noImplicitAdditionalProperties: 'silently-remove-extras', + bodyCoercion: true, authenticationModule: './fixtures/hapi/authentication.ts', basePath: '/v1', entryFile: './fixtures/hapi/server.ts', @@ -143,6 +152,7 @@ const log = async (label: string, fn: () => Promise) => { generateRoutes( { noImplicitAdditionalProperties: 'silently-remove-extras', + bodyCoercion: true, authenticationModule: './fixtures/custom/authentication.ts', basePath: '/v1', entryFile: './fixtures/custom/server.ts', @@ -159,6 +169,7 @@ const log = async (label: string, fn: () => Promise) => { log('Inversify Route Generation', () => generateRoutes({ noImplicitAdditionalProperties: 'silently-remove-extras', + bodyCoercion: true, authenticationModule: './fixtures/inversify/authentication.ts', basePath: '/v1', entryFile: './fixtures/inversify/server.ts', @@ -171,6 +182,7 @@ const log = async (label: string, fn: () => Promise) => { generateRoutes({ controllerPathGlobs: ['fixtures/inversify-cpg/*Controller.ts'], noImplicitAdditionalProperties: 'silently-remove-extras', + bodyCoercion: true, authenticationModule: './fixtures/inversify-cpg/authentication.ts', basePath: '/v1', entryFile: '', @@ -182,6 +194,7 @@ const log = async (label: string, fn: () => Promise) => { log('Inversify Route Generation using dynamic container creation', () => generateRoutes({ noImplicitAdditionalProperties: 'silently-remove-extras', + bodyCoercion: true, authenticationModule: './fixtures/inversify-dynamic-container/authentication.ts', basePath: '/v1', entryFile: './fixtures/inversify-dynamic-container/server.ts', @@ -193,6 +206,7 @@ const log = async (label: string, fn: () => Promise) => { log('Inversify Async IoC Route Generation', () => generateRoutes({ noImplicitAdditionalProperties: 'silently-remove-extras', + bodyCoercion: true, basePath: '/v1', entryFile: './fixtures/inversify-async/server.ts', iocModule: './fixtures/inversify-async/ioc.ts', @@ -203,6 +217,7 @@ const log = async (label: string, fn: () => Promise) => { log('Serverless Route Generation', () => generateRoutes({ noImplicitAdditionalProperties: 'silently-remove-extras', + bodyCoercion: true, basePath: '/v1', entryFile: './fixtures/custom/server.ts', routesDir: './fixtures/custom/custom-route-generator/routes', diff --git a/tests/unit/swagger/templateHelpers.spec.ts b/tests/unit/swagger/templateHelpers.spec.ts index 2941467b6..ce099ba07 100644 --- a/tests/unit/swagger/templateHelpers.spec.ts +++ b/tests/unit/swagger/templateHelpers.spec.ts @@ -15,6 +15,7 @@ describe('ValidationService', () => { const v = new ValidationService({}); const minimalSwaggerConfig: AdditionalProps = { noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, }; const error: FieldErrors = {}; const result = v.validateModel({ @@ -40,6 +41,7 @@ describe('ValidationService', () => { const v = new ValidationService({}); const minimalSwaggerConfig: AdditionalProps = { noImplicitAdditionalProperties: 'throw-on-extras', + bodyCoercion: true, }; const errorDictionary: FieldErrors = {}; const nameOfAdditionalProperty = 'I am the bad key name'; @@ -81,6 +83,7 @@ describe('ValidationService', () => { const v = new ValidationService({}); const minimalSwaggerConfig: AdditionalProps = { noImplicitAdditionalProperties: 'silently-remove-extras', + bodyCoercion: true, }; const errorDictionary: FieldErrors = {}; const nameOfAdditionalProperty = 'I am the bad key name'; @@ -119,6 +122,7 @@ describe('ValidationService', () => { const v = new ValidationService({}); const minimalSwaggerConfig: AdditionalProps = { noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, }; const errorDictionary: FieldErrors = {}; const nameOfAdditionalProperty = 'I am the bad key name'; @@ -153,6 +157,7 @@ describe('ValidationService', () => { const error: FieldErrors = {}; const minimalSwaggerConfig: AdditionalProps = { noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, }; const result = v.validateModel({ name: '', value: {}, modelDefinition, fieldErrors: error, minimalSwaggerConfig }); expect(Object.keys(error)).to.be.empty; @@ -170,6 +175,7 @@ describe('ValidationService', () => { const minimalSwaggerConfig: AdditionalProps = { // we're setting this to the "throw" to demonstrate that explicit additionalProperties should always be allowed noImplicitAdditionalProperties: 'throw-on-extras', + bodyCoercion: true, }; const result = v.validateModel({ name: '', value: { a: 's' }, modelDefinition, fieldErrors: error, minimalSwaggerConfig }); expect(Object.keys(error)).to.be.empty; @@ -190,6 +196,7 @@ describe('ValidationService', () => { // This test should ignore this, otherwise there's a problem the code // when the model has additionalProperties, that should take precedence since it's explicit noImplicitAdditionalProperties: 'throw-on-extras', + bodyCoercion: true, }; const result = v.validateModel({ name: '', value: {}, modelDefinition, fieldErrors: error, minimalSwaggerConfig }); expect(Object.keys(error)).to.be.empty; @@ -213,6 +220,7 @@ describe('ValidationService', () => { // This test should ignore this, otherwise there's a problem the code // when the model has additionalProperties, that should take precedence since it's explicit noImplicitAdditionalProperties: 'throw-on-extras', + bodyCoercion: true, }; const result = v.validateModel({ name: '', value: { a: 9 }, modelDefinition, fieldErrors: error, minimalSwaggerConfig }); expect(Object.keys(error)).to.be.empty; @@ -247,7 +255,7 @@ describe('ValidationService', () => { 'body', error, undefined, - { noImplicitAdditionalProperties: 'ignore' }, + { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, ); expect(result).to.deep.equal({ a: 'value', b: undefined }); @@ -278,7 +286,7 @@ describe('ValidationService', () => { 'body', error, undefined, - { noImplicitAdditionalProperties: 'ignore' }, + { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, ); expect(error['body.a'].message).to.equal(`'a' is required`); @@ -291,6 +299,7 @@ describe('ValidationService', () => { const propertySchema: TsoaRoute.PropertySchema = { dataType: 'integer', default: '666', required: false, validators: {} }; const minimalSwaggerConfig: AdditionalProps = { noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, }; const result = new ValidationService({}).ValidateParam(propertySchema, value, 'defaultProp', {}, undefined, minimalSwaggerConfig); expect(result).to.equal(666); @@ -301,6 +310,7 @@ describe('ValidationService', () => { const propertySchema: TsoaRoute.PropertySchema = { dataType: 'integer', default: '666', required: false, validators: {} }; const minimalSwaggerConfig: AdditionalProps = { noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, }; const result = new ValidationService({}).ValidateParam(propertySchema, value, 'defaultProp', {}, undefined, minimalSwaggerConfig); expect(result).to.equal(123); @@ -311,6 +321,7 @@ describe('ValidationService', () => { const propertySchema: TsoaRoute.PropertySchema = { dataType: 'integer', default: '666', required: true, validators: {} }; const minimalSwaggerConfig: AdditionalProps = { noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, }; const result = new ValidationService({}).ValidateParam(propertySchema, value, 'defaultProp', {}, undefined, minimalSwaggerConfig); expect(result).to.equal(666); @@ -320,7 +331,11 @@ describe('ValidationService', () => { describe('Integer validate', () => { it('should integer value', () => { const value = '10'; - const result = new ValidationService({}).validateInt('name', value, {}); + const minimalSwaggerConfig: AdditionalProps = { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }; + const result = new ValidationService({}).validateInt('name', value, {}, minimalSwaggerConfig); expect(result).to.equal(Number(value)); }); @@ -328,7 +343,11 @@ describe('ValidationService', () => { const name = 'name'; const value = '10.0'; const error: FieldErrors = {}; - const result = new ValidationService({}).validateInt(name, value, error); + const minimalSwaggerConfig: AdditionalProps = { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }; + const result = new ValidationService({}).validateInt(name, value, error, minimalSwaggerConfig); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`invalid integer number`); }); @@ -337,8 +356,12 @@ describe('ValidationService', () => { const name = 'name'; const value = '11'; const error: FieldErrors = {}; + const minimalSwaggerConfig: AdditionalProps = { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }; const validator = { minimum: { value: 10 }, maximum: { value: 12 } }; - const result = new ValidationService({}).validateInt(name, value, error, validator); + const result = new ValidationService({}).validateInt(name, value, error, minimalSwaggerConfig, validator); expect(result).to.equal(Number(value)); }); @@ -347,7 +370,11 @@ describe('ValidationService', () => { const value = '11'; const error: FieldErrors = {}; const validator = { minimum: { value: 12 } }; - const result = new ValidationService({}).validateInt(name, value, error, validator); + const minimalSwaggerConfig: AdditionalProps = { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }; + const result = new ValidationService({}).validateInt(name, value, error, minimalSwaggerConfig, validator); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`min 12`); }); @@ -357,7 +384,11 @@ describe('ValidationService', () => { const value = '11'; const error: FieldErrors = {}; const validator = { maximum: { value: 10 } }; - const result = new ValidationService({}).validateInt(name, value, error, validator); + const minimalSwaggerConfig: AdditionalProps = { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }; + const result = new ValidationService({}).validateInt(name, value, error, minimalSwaggerConfig, validator); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`max 10`); }); @@ -366,7 +397,11 @@ describe('ValidationService', () => { describe('Float validate', () => { it('should float value', () => { const value = '10'; - const result = new ValidationService({}).validateFloat('name', value, {}); + const minimalSwaggerConfig: AdditionalProps = { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }; + const result = new ValidationService({}).validateFloat('name', value, {}, minimalSwaggerConfig); expect(result).to.equal(Number(value)); }); @@ -374,7 +409,11 @@ describe('ValidationService', () => { const name = 'name'; const value = 'Hello'; const error: FieldErrors = {}; - const result = new ValidationService({}).validateFloat(name, value, error); + const minimalSwaggerConfig: AdditionalProps = { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }; + const result = new ValidationService({}).validateFloat(name, value, error, minimalSwaggerConfig); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`invalid float number`); }); @@ -384,7 +423,11 @@ describe('ValidationService', () => { const value = '11.5'; const error: FieldErrors = {}; const validator = { minimum: { value: 10 }, maximum: { value: 12 } }; - const result = new ValidationService({}).validateFloat(name, value, error, validator); + const minimalSwaggerConfig: AdditionalProps = { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }; + const result = new ValidationService({}).validateFloat(name, value, error, minimalSwaggerConfig, validator); expect(result).to.equal(Number(value)); }); @@ -393,7 +436,11 @@ describe('ValidationService', () => { const value = '12.4'; const error: FieldErrors = {}; const validator = { minimum: { value: 12.5 } }; - const result = new ValidationService({}).validateFloat(name, value, error, validator); + const minimalSwaggerConfig: AdditionalProps = { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }; + const result = new ValidationService({}).validateFloat(name, value, error, minimalSwaggerConfig, validator); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`min 12.5`); }); @@ -403,7 +450,11 @@ describe('ValidationService', () => { const value = '10.6'; const error: FieldErrors = {}; const validator = { maximum: { value: 10.5 } }; - const result = new ValidationService({}).validateFloat(name, value, error, validator); + const minimalSwaggerConfig: AdditionalProps = { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }; + const result = new ValidationService({}).validateFloat(name, value, error, minimalSwaggerConfig, validator); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`max 10.5`); }); @@ -724,7 +775,11 @@ describe('ValidationService', () => { describe('Date validate', () => { it('should date value', () => { const value = '2017-01-01'; - const result = new ValidationService({}).validateDate('name', value, {}); + const minimalSwaggerConfig: AdditionalProps = { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }; + const result = new ValidationService({}).validateDate('name', value, {}, minimalSwaggerConfig); expect(result).to.deep.equal(new Date(value)); }); @@ -732,7 +787,11 @@ describe('ValidationService', () => { const name = 'name'; const value = '2017-33-11'; const error: FieldErrors = {}; - const result = new ValidationService({}).validateDate(name, value, error); + const minimalSwaggerConfig: AdditionalProps = { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }; + const result = new ValidationService({}).validateDate(name, value, error, minimalSwaggerConfig); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`invalid ISO 8601 date format, i.e. YYYY-MM-DD`); }); @@ -741,7 +800,11 @@ describe('ValidationService', () => { const name = 'name'; const value = '2017-06-01'; const error: FieldErrors = {}; - const result = new ValidationService({}).validateDate(name, value, error, { minDate: { value: '2017-07-01' } }); + const minimalSwaggerConfig: AdditionalProps = { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }; + const result = new ValidationService({}).validateDate(name, value, error, minimalSwaggerConfig, { minDate: { value: '2017-07-01' } }); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`minDate '2017-07-01'`); }); @@ -750,7 +813,11 @@ describe('ValidationService', () => { const name = 'name'; const value = '2017-06-01'; const error: FieldErrors = {}; - const result = new ValidationService({}).validateDate(name, value, error, { maxDate: { value: '2017-05-01' } }); + const minimalSwaggerConfig: AdditionalProps = { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }; + const result = new ValidationService({}).validateDate(name, value, error, minimalSwaggerConfig, { maxDate: { value: '2017-05-01' } }); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`maxDate '2017-05-01'`); }); @@ -759,7 +826,11 @@ describe('ValidationService', () => { describe('DateTime validate', () => { it('should datetime value', () => { const value = '2017-12-30T00:00:00'; - const result = new ValidationService({}).validateDateTime('name', value, {}); + const minimalSwaggerConfig: AdditionalProps = { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }; + const result = new ValidationService({}).validateDateTime('name', value, {}, minimalSwaggerConfig); expect(result).to.deep.equal(new Date(value)); }); @@ -767,7 +838,11 @@ describe('ValidationService', () => { const name = 'name'; const value = '2017-12-309i'; const error: FieldErrors = {}; - const result = new ValidationService({}).validateDateTime(name, value, error); + const minimalSwaggerConfig: AdditionalProps = { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }; + const result = new ValidationService({}).validateDateTime(name, value, error, minimalSwaggerConfig); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`invalid ISO 8601 datetime format, i.e. YYYY-MM-DDTHH:mm:ss`); }); @@ -776,7 +851,11 @@ describe('ValidationService', () => { const name = 'name'; const value = '2017-12-30T00:00:00'; const error: FieldErrors = {}; - const result = new ValidationService({}).validateDateTime(name, value, error, { minDate: { value: '2017-12-31T00:00:00' } }); + const minimalSwaggerConfig: AdditionalProps = { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }; + const result = new ValidationService({}).validateDateTime(name, value, error, minimalSwaggerConfig, { minDate: { value: '2017-12-31T00:00:00' } }); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`minDate '2017-12-31T00:00:00'`); }); @@ -785,7 +864,11 @@ describe('ValidationService', () => { const name = 'name'; const value = '2017-12-30T00:00:00'; const error: FieldErrors = {}; - const result = new ValidationService({}).validateDateTime(name, value, error, { maxDate: { value: '2017-12-29T00:00:00' } }); + const minimalSwaggerConfig: AdditionalProps = { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }; + const result = new ValidationService({}).validateDateTime(name, value, error, minimalSwaggerConfig, { maxDate: { value: '2017-12-29T00:00:00' } }); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`maxDate '2017-12-29T00:00:00'`); }); @@ -794,7 +877,7 @@ describe('ValidationService', () => { describe('Array validate', () => { it('should array value', () => { const value = ['A', 'B', 'C']; - const result = new ValidationService({}).validateArray('name', value, {}, { noImplicitAdditionalProperties: 'ignore' }, { dataType: 'string' }); + const result = new ValidationService({}).validateArray('name', value, {}, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, { dataType: 'string' }); expect(result).to.deep.equal(value); }); @@ -802,7 +885,7 @@ describe('ValidationService', () => { const name = 'name'; const value = ['A', 10, true]; const error: FieldErrors = {}; - const result = new ValidationService({}).validateArray(name, value, error, { noImplicitAdditionalProperties: 'ignore' }, { dataType: 'integer' }); + const result = new ValidationService({}).validateArray(name, value, error, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, { dataType: 'integer' }); expect(result).to.deep.equal(undefined); expect(error[`${name}.$0`].message).to.equal('invalid integer number'); expect(error[`${name}.$0`].value).to.equal('A'); @@ -810,16 +893,6 @@ describe('ValidationService', () => { expect(error[`${name}.$2`].value).to.equal(true); }); - it('should throw if a non-array value is provided', () => { - const name = 'name'; - const value: any = 'some primitive string'; - const error: FieldErrors = {}; - const result = new ValidationService({}).validateArray(name, value, error, { noImplicitAdditionalProperties: 'ignore' }, { dataType: 'string' }); - expect(result).to.deep.equal(undefined); - expect(error[name].message).to.equal('invalid array'); - expect(error[name].value).to.equal('some primitive string'); - }); - it('should invalid array nested value', () => { const name = 'name'; const value = [{ a: 123 }, { a: 'bcd' }]; @@ -831,7 +904,7 @@ describe('ValidationService', () => { a: { dataType: 'string', required: true }, }, }, - }).validateArray(name, value, error, { noImplicitAdditionalProperties: 'ignore' }, { ref: 'ExampleModel' }); + }).validateArray(name, value, error, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, { ref: 'ExampleModel' }); expect(result).to.deep.equal(undefined); expect(error).to.deep.equal({ [`${name}.$0.a`]: { @@ -845,7 +918,7 @@ describe('ValidationService', () => { const name = 'name'; const value = [80, 10, 199]; const error: FieldErrors = {}; - const result = new ValidationService({}).validateArray(name, value, error, { noImplicitAdditionalProperties: 'ignore' }, { dataType: 'integer' }, { minItems: { value: 4 } }); + const result = new ValidationService({}).validateArray(name, value, error, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, { dataType: 'integer' }, { minItems: { value: 4 } }); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`minItems 4`); }); @@ -854,7 +927,7 @@ describe('ValidationService', () => { const name = 'name'; const value = [80, 10, 199]; const error: FieldErrors = {}; - const result = new ValidationService({}).validateArray(name, value, error, { noImplicitAdditionalProperties: 'ignore' }, { dataType: 'integer' }, { maxItems: { value: 2 } }); + const result = new ValidationService({}).validateArray(name, value, error, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, { dataType: 'integer' }, { maxItems: { value: 2 } }); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`maxItems 2`); }); @@ -863,7 +936,7 @@ describe('ValidationService', () => { const name = 'name'; const value = [10, 10, 20]; const error: FieldErrors = {}; - const result = new ValidationService({}).validateArray(name, value, error, { noImplicitAdditionalProperties: 'ignore' }, { dataType: 'integer' }, { uniqueItems: {} }); + const result = new ValidationService({}).validateArray(name, value, error, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, { dataType: 'integer' }, { uniqueItems: {} }); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`required unique array`); }); @@ -876,6 +949,7 @@ describe('ValidationService', () => { const v = new ValidationService({ enumModel }); const minimalSwaggerConfig: AdditionalProps = { noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, }; const fieldErrors = {}; const result = v.validateArray('name', ['foo', 'bar', 'foo', 'foobar'], fieldErrors, minimalSwaggerConfig, { dataType: 'refEnum', ref: 'enumModel' }); @@ -883,6 +957,16 @@ describe('ValidationService', () => { expect(result).to.be.undefined; expect(fieldErrors).to.deep.equal({ 'name.$3': { message: "should be one of the following; ['foo','bar']", value: 'foobar' } }); }); + + it('should throw if bodyCoercion is false and a non-array value is provided', () => { + const name = 'name'; + const value: any = 'some primitive string'; + const error: FieldErrors = {}; + const result = new ValidationService({}).validateArray(name, value, error, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }, { dataType: 'string' }); + expect(result).to.deep.equal(undefined); + expect(error[name].message).to.equal('invalid array'); + expect(error[name].value).to.equal('some primitive string'); + }); }); describe('Union validate', () => { @@ -907,6 +991,7 @@ describe('ValidationService', () => { const error: FieldErrors = {}; const minimalSwaggerConfig: AdditionalProps = { noImplicitAdditionalProperties: 'silently-remove-extras', + bodyCoercion: true, }; const schema: TsoaRoute.PropertySchema = { subSchemas: [{ ref: 'TypeA' }, { ref: 'TypeB' }] }; const resultA = v.validateUnion(name, { type: 'A', a: 100 }, error, minimalSwaggerConfig, schema); @@ -920,6 +1005,7 @@ describe('ValidationService', () => { const errors = {}; const minimalSwaggerConfig: AdditionalProps = { noImplicitAdditionalProperties: 'silently-remove-extras', + bodyCoercion: true, }; const schema: TsoaRoute.PropertySchema = { dataType: 'union', subSchemas: [{ dataType: 'integer' }, { dataType: 'string' }], required: true, validators: { minimum: { value: 5 } } }; @@ -938,6 +1024,7 @@ describe('ValidationService', () => { describe('throw on extras', () => { const minimalSwaggerConfig: AdditionalProps = { noImplicitAdditionalProperties: 'throw-on-extras', + bodyCoercion: true, }; it('should validate intersection with 3 or more types', () => { const refName = 'ExampleModel'; @@ -1304,6 +1391,7 @@ describe('ValidationService', () => { const propertySchema: TsoaRoute.PropertySchema = { dataType: 'undefined', required: true, validators: {} }; const minimalSwaggerConfig: AdditionalProps = { noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, }; const fieldErrors: FieldErrors = {}; const result = new ValidationService({}).ValidateParam(propertySchema, value, 'defaultProp', fieldErrors, undefined, minimalSwaggerConfig); @@ -1316,6 +1404,7 @@ describe('ValidationService', () => { const propertySchema: TsoaRoute.PropertySchema = { dataType: 'undefined', required: true, validators: {} }; const minimalSwaggerConfig: AdditionalProps = { noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, }; const fieldErrors: FieldErrors = {}; const result = new ValidationService({}).ValidateParam(propertySchema, value, 'defaultProp', fieldErrors, undefined, minimalSwaggerConfig); @@ -1334,6 +1423,7 @@ describe('ValidationService', () => { const error = {}; const minimalSwaggerConfig: AdditionalProps = { noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, }; const result = v.validateModel({ name: '', value: {}, modelDefinition, fieldErrors: error, minimalSwaggerConfig }); expect(Object.keys(error)).to.be.empty; @@ -1352,6 +1442,7 @@ describe('ValidationService', () => { const error = {}; const minimalSwaggerConfig: AdditionalProps = { noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, }; const result = v.validateModel({ name: '', value: {}, modelDefinition, fieldErrors: error, minimalSwaggerConfig }); expect(Object.keys(error)).to.be.empty; @@ -1364,6 +1455,7 @@ describe('ValidationService', () => { const propertySchema: TsoaRoute.PropertySchema = { dataType: 'undefined', required: false, validators: {} }; const minimalSwaggerConfig: AdditionalProps = { noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, }; const fieldErrors: FieldErrors = {}; const result = new ValidationService({}).ValidateParam(propertySchema, value, 'defaultProp', fieldErrors, undefined, minimalSwaggerConfig); diff --git a/tests/unit/templating/routeGenerator.spec.ts b/tests/unit/templating/routeGenerator.spec.ts index 1dcdb36f8..4c6b388ed 100644 --- a/tests/unit/templating/routeGenerator.spec.ts +++ b/tests/unit/templating/routeGenerator.spec.ts @@ -40,6 +40,7 @@ describe('RouteGenerator', () => { }, }, { + bodyCoercion: true, entryFile: 'mockEntryFile', routesDir: 'mockRoutesDir', noImplicitAdditionalProperties: 'silently-remove-extras', @@ -86,6 +87,7 @@ describe('RouteGenerator', () => { referenceTypeMap: {}, }, { + bodyCoercion: true, entryFile: 'mockEntryFile', routesDir: '.', noImplicitAdditionalProperties: 'silently-remove-extras', @@ -111,6 +113,7 @@ describe('RouteGenerator', () => { referenceTypeMap: {}, }, { + bodyCoercion: true, entryFile: 'mockEntryFile', routesDir: '.', noImplicitAdditionalProperties: 'silently-remove-extras', @@ -137,6 +140,7 @@ describe('RouteGenerator', () => { referenceTypeMap: {}, }, { + bodyCoercion: true, entryFile: 'mockEntryFile', routesDir: '.', noImplicitAdditionalProperties: 'silently-remove-extras', @@ -163,6 +167,7 @@ describe('RouteGenerator', () => { referenceTypeMap: {}, }, { + bodyCoercion: true, entryFile: 'mockEntryFile', routesDir: '.', noImplicitAdditionalProperties: 'silently-remove-extras', diff --git a/tests/unit/templating/templateHelpers.spec.ts b/tests/unit/templating/templateHelpers.spec.ts index 1602f0e70..afca5d40b 100644 --- a/tests/unit/templating/templateHelpers.spec.ts +++ b/tests/unit/templating/templateHelpers.spec.ts @@ -43,6 +43,7 @@ it('should allow additionalProperties (on a union) if noImplicitAdditionalProper const v = new ValidationService(models); const minimalSwaggerConfig: AdditionalProps = { noImplicitAdditionalProperties: 'silently-remove-extras', + bodyCoercion: true, }; const errorDictionary: FieldErrors = {}; const nameOfAdditionalProperty = 'I am the bad key name'; @@ -96,6 +97,7 @@ it('should throw if the data has additionalProperties (on a union) if noImplicit const v = new ValidationService(models); const minimalSwaggerConfig: AdditionalProps = { noImplicitAdditionalProperties: 'throw-on-extras', + bodyCoercion: true, }; const errorDictionary: FieldErrors = {}; const nameOfAdditionalProperty = 'I am the bad key name' as keyof TypeAliasModel1; @@ -155,6 +157,7 @@ it('should throw if the data has additionalProperties (on a intersection) if noI const v = new ValidationService(models); const minimalSwaggerConfig: AdditionalProps = { noImplicitAdditionalProperties: 'throw-on-extras', + bodyCoercion: true, }; const errorDictionary: FieldErrors = {}; const nameOfAdditionalProperty = 'extraKeyName' as keyof (TypeAliasModel1 & TypeAliasModel2); // pretend this is fine @@ -239,6 +242,7 @@ it('should throw if the data has additionalProperties (on a nested Object) if no const v = new ValidationService(models); const minimalSwaggerConfig: AdditionalProps = { noImplicitAdditionalProperties: 'throw-on-extras', + bodyCoercion: true, }; const errorDictionary: FieldErrors = {}; const dataToValidate = { @@ -308,6 +312,7 @@ it('should not throw if the data has additionalProperties (on a intersection) if const v = new ValidationService(models); const minimalSwaggerConfig: AdditionalProps = { noImplicitAdditionalProperties: 'silently-remove-extras', + bodyCoercion: true, }; const errorDictionary: FieldErrors = {}; const nameOfAdditionalProperty = 'extraKeyName' as keyof (TypeAliasModel1 & TypeAliasModel2); // pretend this is fine @@ -365,6 +370,7 @@ it('should not throw if the data has additionalProperties (on a intersection) if const v = new ValidationService(models); const minimalSwaggerConfig: AdditionalProps = { noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, }; const errorDictionary: FieldErrors = {}; const nameOfAdditionalProperty = 'extraKeyName' as keyof (TypeAliasModel1 & TypeAliasModel2); // pretend this is fine @@ -438,6 +444,7 @@ it('should not throw if the data has additionalProperties (on a nested Object) i const v = new ValidationService(models); const minimalSwaggerConfig: AdditionalProps = { noImplicitAdditionalProperties: 'silently-remove-extras', + bodyCoercion: true, }; const errorDictionary: FieldErrors = {}; const dataToValidate = { @@ -533,6 +540,7 @@ it('should not throw if the data has additionalProperties (on a nested Object) i const v = new ValidationService(models); const minimalSwaggerConfig: AdditionalProps = { noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, }; const errorDictionary: FieldErrors = {}; const dataToValidate = { @@ -593,6 +601,7 @@ it('should throw if properties on nOl are missing', () => { const errors = {}; const minimalSwaggerConfig: AdditionalProps = { noImplicitAdditionalProperties: 'silently-remove-extras', + bodyCoercion: true, }; v.validateNestedObjectLiteral('nested', {}, errors, minimalSwaggerConfig, schema, true, 'Model.'); From d48ffc4aca6599db6be72514f6450a80c2f4f0ed Mon Sep 17 00:00:00 2001 From: Christian Friedow Date: Mon, 11 Mar 2024 09:16:07 +0100 Subject: [PATCH 4/8] test: add tests for disabled body coercion on ints, floats and dates --- tests/unit/swagger/templateHelpers.spec.ts | 45 +++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/tests/unit/swagger/templateHelpers.spec.ts b/tests/unit/swagger/templateHelpers.spec.ts index ce099ba07..0ee6262e4 100644 --- a/tests/unit/swagger/templateHelpers.spec.ts +++ b/tests/unit/swagger/templateHelpers.spec.ts @@ -392,6 +392,16 @@ describe('ValidationService', () => { expect(result).to.equal(undefined); expect(error[name].message).to.equal(`max 10`); }); + + it('should return an error if bodyCoercion is false and a non-number value is provided', () => { + const name = 'name'; + const value: any = '10'; + const error: FieldErrors = {}; + const result = new ValidationService({}).validateInt(name, value, error, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }); + expect(result).to.deep.equal(undefined); + expect(error[name].message).to.equal('invalid integer number'); + expect(error[name].value).to.equal('10'); + }); }); describe('Float validate', () => { @@ -458,6 +468,19 @@ describe('ValidationService', () => { expect(result).to.equal(undefined); expect(error[name].message).to.equal(`max 10.5`); }); + + it('should return an error if bodyCoercion is false and a non-number value is provided', () => { + const name = 'name'; + const value: any = '10.1'; + const error: FieldErrors = {}; + const result = new ValidationService({}).validateFloat(name, value, error, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }); + expect(result).to.deep.equal(undefined); + expect(error[name].message).to.equal('invalid float number'); + expect(error[name].value).to.equal('10.1'); + }); + }); + + }); describe('Enum validate', () => { @@ -821,6 +844,16 @@ describe('ValidationService', () => { expect(result).to.equal(undefined); expect(error[name].message).to.equal(`maxDate '2017-05-01'`); }); + + it('should return an error if bodyCoercion is false and a non-string value is provided', () => { + const name = 'name'; + const value: any = 1234; + const error: FieldErrors = {}; + const result = new ValidationService({}).validateDate(name, value, error, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }); + expect(result).to.deep.equal(undefined); + expect(error[name].message).to.equal('invalid ISO 8601 date format, i.e. YYYY-MM-DD'); + expect(error[name].value).to.equal(1234); + }); }); describe('DateTime validate', () => { @@ -872,6 +905,16 @@ describe('ValidationService', () => { expect(result).to.equal(undefined); expect(error[name].message).to.equal(`maxDate '2017-12-29T00:00:00'`); }); + + it('should return an error if bodyCoercion is false and a non-string value is provided', () => { + const name = 'name'; + const value: any = 1234; + const error: FieldErrors = {}; + const result = new ValidationService({}).validateDateTime(name, value, error, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }); + expect(result).to.deep.equal(undefined); + expect(error[name].message).to.equal('invalid ISO 8601 datetime format, i.e. YYYY-MM-DDTHH:mm:ss'); + expect(error[name].value).to.equal(1234); + }); }); describe('Array validate', () => { @@ -958,7 +1001,7 @@ describe('ValidationService', () => { expect(fieldErrors).to.deep.equal({ 'name.$3': { message: "should be one of the following; ['foo','bar']", value: 'foobar' } }); }); - it('should throw if bodyCoercion is false and a non-array value is provided', () => { + it('should return an error if bodyCoercion is false and a non-array value is provided', () => { const name = 'name'; const value: any = 'some primitive string'; const error: FieldErrors = {}; From eaba5dc90c3116fb66d09308be675539e306d254 Mon Sep 17 00:00:00 2001 From: Christian Friedow Date: Mon, 11 Mar 2024 09:16:41 +0100 Subject: [PATCH 5/8] test: add tests for boolean validation --- tests/unit/swagger/templateHelpers.spec.ts | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/unit/swagger/templateHelpers.spec.ts b/tests/unit/swagger/templateHelpers.spec.ts index 0ee6262e4..c6230d659 100644 --- a/tests/unit/swagger/templateHelpers.spec.ts +++ b/tests/unit/swagger/templateHelpers.spec.ts @@ -480,7 +480,46 @@ describe('ValidationService', () => { }); }); + describe('Boolean validate', () => { + it('should return true when submitted true', () => { + const value = true; + const minimalSwaggerConfig: AdditionalProps = { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }; + const result = new ValidationService({}).validateBool('name', value, {}, minimalSwaggerConfig); + expect(result).to.equal(true); + }); + + it('should return false when submitted false', () => { + const value = false; + const minimalSwaggerConfig: AdditionalProps = { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }; + const result = new ValidationService({}).validateBool('name', value, {}, minimalSwaggerConfig); + expect(result).to.equal(false); + }); + + it('should coerce strings to boolean values if body coercion is enabled', () => { + const value = 'false'; + const minimalSwaggerConfig: AdditionalProps = { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }; + const result = new ValidationService({}).validateBool('name', value, {}, minimalSwaggerConfig); + expect(result).to.equal(false); + }); + it('should return an error a non-boolean value is provided and body coercion is disabled', () => { + const name = 'name'; + const value = 'false'; + const error: FieldErrors = {}; + const result = new ValidationService({}).validateBool(name, value, error, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }); + expect(result).to.deep.equal(undefined); + expect(error[name].message).to.equal('invalid boolean value'); + expect(error[name].value).to.equal('false'); + }); }); describe('Enum validate', () => { From 6b12fa2c5df7f60fd2d1628a51cd1ed0480c26c0 Mon Sep 17 00:00:00 2001 From: Christian Friedow Date: Mon, 11 Mar 2024 09:17:18 +0100 Subject: [PATCH 6/8] feat: add isBodyParam argument to validation functions --- .../express/expressTemplateService.ts | 21 ++- .../templates/hapi/hapiTemplateService.ts | 18 +-- .../templates/koa/koaTemplateService.ts | 22 +-- .../src/routeGeneration/templateHelpers.ts | 120 ++++++++++----- .../templates/models.hbs | 12 +- .../custom/custom-tsoa-template.ts.hbs | 12 +- tests/unit/swagger/templateHelpers.spec.ts | 140 +++++++++++------- tests/unit/templating/templateHelpers.spec.ts | 20 +-- 8 files changed, 220 insertions(+), 145 deletions(-) diff --git a/packages/cli/src/routeGeneration/templates/express/expressTemplateService.ts b/packages/cli/src/routeGeneration/templates/express/expressTemplateService.ts index 270ae2a84..79f48a86d 100644 --- a/packages/cli/src/routeGeneration/templates/express/expressTemplateService.ts +++ b/packages/cli/src/routeGeneration/templates/express/expressTemplateService.ts @@ -64,30 +64,30 @@ export class ExpressTemplateService extends TemplateService param.dataType === 'file'); if (files.length > 0) { const requestFiles = request.files as { [fileName: string]: Express.Multer.File[] }; - const fileArgs = this.validationService.ValidateParam(param, requestFiles[name], name, fieldErrors, undefined, this.minimalSwaggerConfig); + const fileArgs = this.validationService.ValidateParam(param, requestFiles[name], name, fieldErrors, false, undefined, this.minimalSwaggerConfig); return fileArgs.length === 1 ? fileArgs[0] : fileArgs; } else if (param.dataType === 'array' && param.array && param.array.dataType === 'file') { - return this.validationService.ValidateParam(param, request.files, name, fieldErrors, undefined, this.minimalSwaggerConfig); + return this.validationService.ValidateParam(param, request.files, name, fieldErrors, false, undefined, this.minimalSwaggerConfig); } else { - return this.validationService.ValidateParam(param, request.body[name], name, fieldErrors, undefined, this.minimalSwaggerConfig); + return this.validationService.ValidateParam(param, request.body[name], name, fieldErrors, false, undefined, this.minimalSwaggerConfig); } } case 'res': @@ -123,5 +123,4 @@ export class ExpressTemplateService extends TemplateService { @@ -76,27 +76,27 @@ export class HapiTemplateService extends TemplateService { diff --git a/packages/cli/src/routeGeneration/templates/koa/koaTemplateService.ts b/packages/cli/src/routeGeneration/templates/koa/koaTemplateService.ts index 6cda579d0..097ca48b7 100644 --- a/packages/cli/src/routeGeneration/templates/koa/koaTemplateService.ts +++ b/packages/cli/src/routeGeneration/templates/koa/koaTemplateService.ts @@ -63,40 +63,40 @@ export class KoaTemplateService extends TemplateService param.dataType === 'file'); const contextRequest = context.request as any; if (files.length > 0) { - const fileArgs = this.validationService.ValidateParam(param, contextRequest.files[name], name, errorFields, undefined, this.minimalSwaggerConfig); + const fileArgs = this.validationService.ValidateParam(param, contextRequest.files[name], name, errorFields, false, undefined, this.minimalSwaggerConfig); return fileArgs.length === 1 ? fileArgs[0] : fileArgs; } else if (param.dataType === 'array' && param.array && param.array.dataType === 'file') { - return this.validationService.ValidateParam(param, contextRequest.files, name, errorFields, undefined, this.minimalSwaggerConfig); + return this.validationService.ValidateParam(param, contextRequest.files, name, errorFields, false, undefined, this.minimalSwaggerConfig); } else { - return this.validationService.ValidateParam(param, contextRequest.body[name], name, errorFields, undefined, this.minimalSwaggerConfig); + return this.validationService.ValidateParam(param, contextRequest.body[name], name, errorFields, false, undefined, this.minimalSwaggerConfig); } } case 'res': diff --git a/packages/runtime/src/routeGeneration/templateHelpers.ts b/packages/runtime/src/routeGeneration/templateHelpers.ts index 529c2ebbc..5538534e4 100644 --- a/packages/runtime/src/routeGeneration/templateHelpers.ts +++ b/packages/runtime/src/routeGeneration/templateHelpers.ts @@ -6,14 +6,23 @@ import { Tsoa } from '../metadataGeneration/tsoa'; import ValidatorKey = Tsoa.ValidatorKey; // for backwards compatibility with custom templates -export function ValidateParam(property: TsoaRoute.PropertySchema, value: any, generatedModels: TsoaRoute.Models, name = '', fieldErrors: FieldErrors, parent = '', swaggerConfig: AdditionalProps) { - return new ValidationService(generatedModels).ValidateParam(property, value, name, fieldErrors, parent, swaggerConfig); +export function ValidateParam( + property: TsoaRoute.PropertySchema, + value: any, + generatedModels: TsoaRoute.Models, + name = '', + fieldErrors: FieldErrors, + isBodyParam: boolean, + parent = '', + swaggerConfig: AdditionalProps, +) { + return new ValidationService(generatedModels).ValidateParam(property, value, name, fieldErrors, isBodyParam, parent, swaggerConfig); } export class ValidationService { constructor(private readonly models: TsoaRoute.Models) {} - public ValidateParam(property: TsoaRoute.PropertySchema, rawValue: any, name = '', fieldErrors: FieldErrors, parent = '', minimalSwaggerConfig: AdditionalProps) { + public ValidateParam(property: TsoaRoute.PropertySchema, rawValue: any, name = '', fieldErrors: FieldErrors, isBodyParam: boolean, parent = '', minimalSwaggerConfig: AdditionalProps) { let value = rawValue; // If undefined is allowed type, we can move to value validation if (value === undefined && property.dataType !== 'undefined') { @@ -46,36 +55,36 @@ export class ValidationService { case 'string': return this.validateString(name, value, fieldErrors, property.validators as StringValidator, parent); case 'boolean': - return this.validateBool(name, value, fieldErrors, minimalSwaggerConfig, property.validators as BooleanValidator, parent); + return this.validateBool(name, value, fieldErrors, isBodyParam, minimalSwaggerConfig, property.validators as BooleanValidator, parent); case 'integer': case 'long': - return this.validateInt(name, value, fieldErrors, minimalSwaggerConfig, property.validators as IntegerValidator, parent); + return this.validateInt(name, value, fieldErrors, isBodyParam, minimalSwaggerConfig, property.validators as IntegerValidator, parent); case 'float': case 'double': - return this.validateFloat(name, value, fieldErrors, minimalSwaggerConfig, property.validators as FloatValidator, parent); + return this.validateFloat(name, value, fieldErrors, isBodyParam, minimalSwaggerConfig, property.validators as FloatValidator, parent); case 'enum': return this.validateEnum(name, value, fieldErrors, property.enums, parent); case 'array': - return this.validateArray(name, value, fieldErrors, minimalSwaggerConfig, property.array, property.validators as ArrayValidator, parent); + return this.validateArray(name, value, fieldErrors, isBodyParam, minimalSwaggerConfig, property.array, property.validators as ArrayValidator, parent); case 'date': - return this.validateDate(name, value, fieldErrors, minimalSwaggerConfig, property.validators as DateValidator, parent); + return this.validateDate(name, value, fieldErrors, isBodyParam, minimalSwaggerConfig, property.validators as DateValidator, parent); case 'datetime': - return this.validateDateTime(name, value, fieldErrors, minimalSwaggerConfig, property.validators as DateTimeValidator, parent); + return this.validateDateTime(name, value, fieldErrors, isBodyParam, minimalSwaggerConfig, property.validators as DateTimeValidator, parent); case 'buffer': return this.validateBuffer(name, value); case 'union': - return this.validateUnion(name, value, fieldErrors, minimalSwaggerConfig, property, parent); + return this.validateUnion(name, value, fieldErrors, isBodyParam, minimalSwaggerConfig, property, parent); case 'intersection': - return this.validateIntersection(name, value, fieldErrors, minimalSwaggerConfig, property.subSchemas, parent); + return this.validateIntersection(name, value, fieldErrors, isBodyParam, minimalSwaggerConfig, property.subSchemas, parent); case 'undefined': return this.validateUndefined(name, value, fieldErrors, parent); case 'any': return value; case 'nestedObjectLiteral': - return this.validateNestedObjectLiteral(name, value, fieldErrors, minimalSwaggerConfig, property.nestedProperties, property.additionalProperties, parent); + return this.validateNestedObjectLiteral(name, value, fieldErrors, isBodyParam, minimalSwaggerConfig, property.nestedProperties, property.additionalProperties, parent); default: if (property.ref) { - return this.validateModel({ name, value, modelDefinition: this.models[property.ref], fieldErrors, parent, minimalSwaggerConfig }); + return this.validateModel({ name, value, modelDefinition: this.models[property.ref], fieldErrors, isBodyParam, parent, minimalSwaggerConfig }); } return value; } @@ -85,6 +94,7 @@ export class ValidationService { name: string, value: any, fieldErrors: FieldErrors, + isBodyParam: boolean, swaggerConfig: AdditionalProps, nestedProperties: { [name: string]: TsoaRoute.PropertySchema } | undefined, additionalProperties: TsoaRoute.PropertySchema | boolean | undefined, @@ -128,7 +138,7 @@ export class ValidationService { } Object.keys(nestedProperties).forEach(key => { - const validatedProp = this.ValidateParam(nestedProperties[key], value[key], key, fieldErrors, parent + name + '.', swaggerConfig); + const validatedProp = this.ValidateParam(nestedProperties[key], value[key], key, fieldErrors, isBodyParam, parent + name + '.', swaggerConfig); // Add value from validator if it's not undefined or if value is required and unfedined is valid type if (validatedProp !== undefined || (nestedProperties[key].dataType === 'undefined' && nestedProperties[key].required)) { @@ -139,7 +149,7 @@ export class ValidationService { if (typeof additionalProperties === 'object' && typeof value === 'object') { const keys = Object.keys(value).filter(key => typeof nestedProperties[key] === 'undefined'); keys.forEach(key => { - const validatedProp = this.ValidateParam(additionalProperties, value[key], key, fieldErrors, parent + name + '.', swaggerConfig); + const validatedProp = this.ValidateParam(additionalProperties, value[key], key, fieldErrors, isBodyParam, parent + name + '.', swaggerConfig); // Add value from validator if it's not undefined or if value is required and unfedined is valid type if (validatedProp !== undefined || (additionalProperties.dataType === 'undefined' && additionalProperties.required)) { value[key] = validatedProp; @@ -154,8 +164,8 @@ export class ValidationService { return value; } - public validateInt(name: string, value: any, fieldErrors: FieldErrors, swaggerConfig: AdditionalProps, validators?: IntegerValidator, parent = '') { - if ((swaggerConfig.bodyCoercion === false && typeof value !== 'number') || !validator.isInt(String(value))) { + public validateInt(name: string, value: any, fieldErrors: FieldErrors, isBodyParam: boolean, swaggerConfig: AdditionalProps, validators?: IntegerValidator, parent = '') { + if ((isBodyParam && swaggerConfig.bodyCoercion === false && typeof value !== 'number') || !validator.isInt(String(value))) { let message = `invalid integer number`; if (validators) { if (validators.isInt && validators.isInt.errorMsg) { @@ -197,8 +207,8 @@ export class ValidationService { return numberValue; } - public validateFloat(name: string, value: any, fieldErrors: FieldErrors, swaggerConfig: AdditionalProps, validators?: FloatValidator, parent = '') { - if ((swaggerConfig.bodyCoercion === false && typeof value !== 'number') || !validator.isFloat(String(value))) { + public validateFloat(name: string, value: any, fieldErrors: FieldErrors, isBodyParam: boolean, swaggerConfig: AdditionalProps, validators?: FloatValidator, parent = '') { + if ((isBodyParam && swaggerConfig.bodyCoercion === false && typeof value !== 'number') || !validator.isFloat(String(value))) { let message = 'invalid float number'; if (validators) { if (validators.isFloat && validators.isFloat.errorMsg) { @@ -263,8 +273,8 @@ export class ValidationService { return members[enumMatchIndex]; } - public validateDate(name: string, value: any, fieldErrors: FieldErrors, swaggerConfig: AdditionalProps, validators?: DateValidator, parent = '') { - if ((swaggerConfig.bodyCoercion === false && typeof value !== 'string') || !validator.isISO8601(String(value), { strict: true })) { + public validateDate(name: string, value: any, fieldErrors: FieldErrors, isBodyParam: boolean, swaggerConfig: AdditionalProps, validators?: DateValidator, parent = '') { + if ((isBodyParam && swaggerConfig.bodyCoercion === false && typeof value !== 'string') || !validator.isISO8601(String(value), { strict: true })) { const message = validators && validators.isDate && validators.isDate.errorMsg ? validators.isDate.errorMsg : `invalid ISO 8601 date format, i.e. YYYY-MM-DD`; fieldErrors[parent + name] = { message, @@ -300,8 +310,8 @@ export class ValidationService { return dateValue; } - public validateDateTime(name: string, value: any, fieldErrors: FieldErrors, swaggerConfig: AdditionalProps, validators?: DateTimeValidator, parent = '') { - if ((swaggerConfig.bodyCoercion === false && typeof value !== 'string') || !validator.isISO8601(String(value), { strict: true })) { + public validateDateTime(name: string, value: any, fieldErrors: FieldErrors, isBodyParam: boolean, swaggerConfig: AdditionalProps, validators?: DateTimeValidator, parent = '') { + if ((isBodyParam && swaggerConfig.bodyCoercion === false && typeof value !== 'string') || !validator.isISO8601(String(value), { strict: true })) { const message = validators && validators.isDateTime && validators.isDateTime.errorMsg ? validators.isDateTime.errorMsg : `invalid ISO 8601 datetime format, i.e. YYYY-MM-DDTHH:mm:ss`; fieldErrors[parent + name] = { message, @@ -381,12 +391,12 @@ export class ValidationService { return stringValue; } - public validateBool(name: string, value: any, fieldErrors: FieldErrors, swaggerConfig: AdditionalProps, validators?: BooleanValidator, parent = '') { + public validateBool(name: string, value: any, fieldErrors: FieldErrors, isBodyParam: boolean, swaggerConfig: AdditionalProps, validators?: BooleanValidator, parent = '') { if (value === true || value === false) { return value; } - if (swaggerConfig.bodyCoercion === true) { + if (!isBodyParam || swaggerConfig.bodyCoercion === true) { if (value === undefined || value === null) { return false; } @@ -419,8 +429,17 @@ export class ValidationService { return; } - public validateArray(name: string, value: any[], fieldErrors: FieldErrors, swaggerConfig: AdditionalProps, schema?: TsoaRoute.PropertySchema, validators?: ArrayValidator, parent = '') { - if ((swaggerConfig.bodyCoercion === false && !Array.isArray(value)) || !schema || value === undefined) { + public validateArray( + name: string, + value: any[], + fieldErrors: FieldErrors, + isBodyParam: boolean, + swaggerConfig: AdditionalProps, + schema?: TsoaRoute.PropertySchema, + validators?: ArrayValidator, + parent = '', + ) { + if ((isBodyParam && swaggerConfig.bodyCoercion === false && !Array.isArray(value)) || !schema || value === undefined) { const message = validators && validators.isArray && validators.isArray.errorMsg ? validators.isArray.errorMsg : `invalid array`; fieldErrors[parent + name] = { message, @@ -433,10 +452,10 @@ export class ValidationService { const previousErrors = Object.keys(fieldErrors).length; if (Array.isArray(value)) { arrayValue = value.map((elementValue, index) => { - return this.ValidateParam(schema, elementValue, `$${index}`, fieldErrors, name + '.', swaggerConfig); + return this.ValidateParam(schema, elementValue, `$${index}`, fieldErrors, isBodyParam, name + '.', swaggerConfig); }); } else { - arrayValue = [this.ValidateParam(schema, value, '$0', fieldErrors, name + '.', swaggerConfig)]; + arrayValue = [this.ValidateParam(schema, value, '$0', fieldErrors, isBodyParam, name + '.', swaggerConfig)]; } if (Object.keys(fieldErrors).length > previousErrors) { @@ -484,7 +503,7 @@ export class ValidationService { return Buffer.from(value); } - public validateUnion(name: string, value: any, fieldErrors: FieldErrors, swaggerConfig: AdditionalProps, property: TsoaRoute.PropertySchema, parent = ''): any { + public validateUnion(name: string, value: any, fieldErrors: FieldErrors, isBodyParam: boolean, swaggerConfig: AdditionalProps, property: TsoaRoute.PropertySchema, parent = ''): any { if (!property.subSchemas) { throw new Error( 'internal tsoa error: ' + @@ -501,7 +520,15 @@ export class ValidationService { // Clean value if it's not undefined or use undefined directly if it's undefined. // Value can be undefined if undefined is allowed datatype of the union const validateableValue = value ? JSON.parse(JSON.stringify(value)) : value; - const cleanValue = this.ValidateParam({ ...subSchema, validators: { ...property.validators, ...subSchema.validators } }, validateableValue, name, subFieldError, parent, swaggerConfig); + const cleanValue = this.ValidateParam( + { ...subSchema, validators: { ...property.validators, ...subSchema.validators } }, + validateableValue, + name, + subFieldError, + isBodyParam, + parent, + swaggerConfig, + ); subFieldErrors.push(subFieldError); if (Object.keys(subFieldError).length === 0) { @@ -516,7 +543,15 @@ export class ValidationService { return; } - public validateIntersection(name: string, value: any, fieldErrors: FieldErrors, swaggerConfig: AdditionalProps, subSchemas: TsoaRoute.PropertySchema[] | undefined, parent = ''): any { + public validateIntersection( + name: string, + value: any, + fieldErrors: FieldErrors, + isBodyParam: boolean, + swaggerConfig: AdditionalProps, + subSchemas: TsoaRoute.PropertySchema[] | undefined, + parent = '', + ): any { if (!subSchemas) { throw new Error( 'internal tsoa error: ' + @@ -530,7 +565,7 @@ export class ValidationService { subSchemas.forEach(subSchema => { const subFieldError: FieldErrors = {}; - const cleanValue = this.ValidateParam(subSchema, JSON.parse(JSON.stringify(value)), name, subFieldError, parent, { + const cleanValue = this.ValidateParam(subSchema, JSON.parse(JSON.stringify(value)), name, subFieldError, isBodyParam, parent, { noImplicitAdditionalProperties: 'silently-remove-extras', bodyCoercion: swaggerConfig.bodyCoercion, }); @@ -560,6 +595,7 @@ export class ValidationService { value: JSON.parse(JSON.stringify(value)), modelDefinition: schema, fieldErrors: requiredPropError, + isBodyParam, minimalSwaggerConfig: { noImplicitAdditionalProperties: 'ignore', bodyCoercion: swaggerConfig.bodyCoercion, @@ -692,8 +728,16 @@ export class ValidationService { } } - public validateModel(input: { name: string; value: any; modelDefinition: TsoaRoute.ModelSchema; fieldErrors: FieldErrors; parent?: string; minimalSwaggerConfig: AdditionalProps }): any { - const { name, value, modelDefinition, fieldErrors, parent = '', minimalSwaggerConfig: swaggerConfig } = input; + public validateModel(input: { + name: string; + value: any; + modelDefinition: TsoaRoute.ModelSchema; + fieldErrors: FieldErrors; + isBodyParam: boolean; + parent?: string; + minimalSwaggerConfig: AdditionalProps; + }): any { + const { name, value, modelDefinition, fieldErrors, isBodyParam, parent = '', minimalSwaggerConfig: swaggerConfig } = input; const previousErrors = Object.keys(fieldErrors).length; if (modelDefinition) { @@ -702,7 +746,7 @@ export class ValidationService { } if (modelDefinition.dataType === 'refAlias') { - return this.ValidateParam(modelDefinition.type, value, name, fieldErrors, parent, swaggerConfig); + return this.ValidateParam(modelDefinition.type, value, name, fieldErrors, isBodyParam, parent, swaggerConfig); } const fieldPath = parent + name; @@ -720,7 +764,7 @@ export class ValidationService { const allPropertiesOnData = new Set(Object.keys(value)); Object.entries(properties).forEach(([key, property]) => { - const validatedParam = this.ValidateParam(property, value[key], key, fieldErrors, fieldPath + '.', swaggerConfig); + const validatedParam = this.ValidateParam(property, value[key], key, fieldErrors, isBodyParam, fieldPath + '.', swaggerConfig); // Add value from validator if it's not undefined or if value is required and unfedined is valid type if (validatedParam !== undefined || (property.dataType === 'undefined' && property.required)) { @@ -756,7 +800,7 @@ export class ValidationService { } else { Object.keys(value).forEach((key: string) => { if (isAnExcessProperty(key)) { - const validatedValue = this.ValidateParam(additionalProperties, value[key], key, fieldErrors, fieldPath + '.', swaggerConfig); + const validatedValue = this.ValidateParam(additionalProperties, value[key], key, fieldErrors, isBodyParam, fieldPath + '.', swaggerConfig); // Add value from validator if it's not undefined or if value is required and unfedined is valid type if (validatedValue !== undefined || (additionalProperties.dataType === 'undefined' && additionalProperties.required)) { value[key] = validatedValue; diff --git a/tests/fixtures/custom/custom-route-generator/templates/models.hbs b/tests/fixtures/custom/custom-route-generator/templates/models.hbs index c90863d64..a6450b638 100644 --- a/tests/fixtures/custom/custom-route-generator/templates/models.hbs +++ b/tests/fixtures/custom/custom-route-generator/templates/models.hbs @@ -36,17 +36,17 @@ export function getValidatedArgs(args: any, event: any): any[] { case 'request': return event; case 'request-prop': - return validationService.ValidateParam(args[key], event[name], name, fieldErrors, undefined, {{{json minimalSwaggerConfig}}}); + return validationService.ValidateParam(args[key], event[name], name, fieldErrors, false, undefined, {{{json minimalSwaggerConfig}}}); case 'query': - return validationService.ValidateParam(args[key], event.queryStringParameters[name], name, fieldErrors, undefined, {{{json minimalSwaggerConfig}}}); + return validationService.ValidateParam(args[key], event.queryStringParameters[name], name, fieldErrors, false, undefined, {{{json minimalSwaggerConfig}}}); case 'path': - return validationService.ValidateParam(args[key], event.pathParameters[name], name, fieldErrors, undefined, {{{json minimalSwaggerConfig}}}); + return validationService.ValidateParam(args[key], event.pathParameters[name], name, fieldErrors, false, undefined, {{{json minimalSwaggerConfig}}}); case 'header': - return validationService.ValidateParam(args[key], event.headers[name], name, fieldErrors, undefined, {{{json minimalSwaggerConfig}}}); + return validationService.ValidateParam(args[key], event.headers[name], name, fieldErrors, false, undefined, {{{json minimalSwaggerConfig}}}); case 'body': - return validationService.ValidateParam(args[key], eventBody, name, fieldErrors, undefined, {{{json minimalSwaggerConfig}}}); + return validationService.ValidateParam(args[key], eventBody, name, fieldErrors, true, undefined, {{{json minimalSwaggerConfig}}}); case 'body-prop': - return validationService.ValidateParam(args[key], eventBody[name], name, fieldErrors, 'body.', {{{json minimalSwaggerConfig}}}); + return validationService.ValidateParam(args[key], eventBody[name], name, fieldErrors, true, 'body.', {{{json minimalSwaggerConfig}}}); case 'formData': throw new Error('Multi-part form data not supported yet'); case 'res': diff --git a/tests/fixtures/custom/custom-tsoa-template.ts.hbs b/tests/fixtures/custom/custom-tsoa-template.ts.hbs index c94c3639a..049d8fa37 100644 --- a/tests/fixtures/custom/custom-tsoa-template.ts.hbs +++ b/tests/fixtures/custom/custom-tsoa-template.ts.hbs @@ -128,17 +128,17 @@ export function RegisterRoutes(app: any) { case 'request': return request; case 'request-prop': - return ValidateParam(args[key], request[name], models, name, errorFields, undefined, {{{json minimalSwaggerConfig}}}); + return ValidateParam(args[key], request[name], models, name, errorFields, false, undefined, {{{json minimalSwaggerConfig}}}); case 'query': - return ValidateParam(args[key], request.query[name], models, name, errorFields, undefined, {{{json minimalSwaggerConfig}}}); + return ValidateParam(args[key], request.query[name], models, name, errorFields, false, undefined, {{{json minimalSwaggerConfig}}}); case 'path': - return ValidateParam(args[key], request.params[name], models, name, errorFields, undefined, {{{json minimalSwaggerConfig}}}); + return ValidateParam(args[key], request.params[name], models, name, errorFields, false, undefined, {{{json minimalSwaggerConfig}}}); case 'header': - return ValidateParam(args[key], request.header(name), models, name, errorFields, undefined, {{{json minimalSwaggerConfig}}}); + return ValidateParam(args[key], request.header(name), models, name, errorFields, false, undefined, {{{json minimalSwaggerConfig}}}); case 'body': - return ValidateParam(args[key], request.body, models, name, errorFields, undefined, {{{json minimalSwaggerConfig}}}); + return ValidateParam(args[key], request.body, models, name, errorFields, true, undefined, {{{json minimalSwaggerConfig}}}); case 'body-prop': - return ValidateParam(args[key], request.body[name], models, name, errorFields, undefined, {{{json minimalSwaggerConfig}}}); + return ValidateParam(args[key], request.body[name], models, name, errorFields, true, undefined, {{{json minimalSwaggerConfig}}}); } }); diff --git a/tests/unit/swagger/templateHelpers.spec.ts b/tests/unit/swagger/templateHelpers.spec.ts index c6230d659..5a64bb523 100644 --- a/tests/unit/swagger/templateHelpers.spec.ts +++ b/tests/unit/swagger/templateHelpers.spec.ts @@ -20,6 +20,7 @@ describe('ValidationService', () => { const error: FieldErrors = {}; const result = v.validateModel({ fieldErrors: error, + isBodyParam: true, minimalSwaggerConfig, name: '', modelDefinition, @@ -53,6 +54,7 @@ describe('ValidationService', () => { // Act v.validateModel({ fieldErrors: errorDictionary, + isBodyParam: true, minimalSwaggerConfig, name: '', modelDefinition, @@ -95,6 +97,7 @@ describe('ValidationService', () => { // Act v.validateModel({ fieldErrors: errorDictionary, + isBodyParam: true, minimalSwaggerConfig, name: '', modelDefinition, @@ -134,6 +137,7 @@ describe('ValidationService', () => { // Act const result = v.validateModel({ fieldErrors: errorDictionary, + isBodyParam: true, minimalSwaggerConfig, name: '', modelDefinition, @@ -159,7 +163,7 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true, }; - const result = v.validateModel({ name: '', value: {}, modelDefinition, fieldErrors: error, minimalSwaggerConfig }); + const result = v.validateModel({ name: '', value: {}, modelDefinition, fieldErrors: error, isBodyParam: true, minimalSwaggerConfig }); expect(Object.keys(error)).to.be.empty; expect(result).to.eql({}); }); @@ -177,7 +181,15 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'throw-on-extras', bodyCoercion: true, }; - const result = v.validateModel({ name: '', value: { a: 's' }, modelDefinition, fieldErrors: error, minimalSwaggerConfig }); + const result = v.validateModel({ + name: '', + value: { a: 's' }, + modelDefinition, + fieldErrors: error, + + isBodyParam: true, + minimalSwaggerConfig, + }); expect(Object.keys(error)).to.be.empty; expect(result).to.eql({ a: 's' }); }); @@ -198,7 +210,7 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'throw-on-extras', bodyCoercion: true, }; - const result = v.validateModel({ name: '', value: {}, modelDefinition, fieldErrors: error, minimalSwaggerConfig }); + const result = v.validateModel({ name: '', value: {}, modelDefinition, fieldErrors: error, isBodyParam: true, minimalSwaggerConfig }); expect(Object.keys(error)).to.be.empty; expect(result).to.eql({}); }); @@ -222,7 +234,7 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'throw-on-extras', bodyCoercion: true, }; - const result = v.validateModel({ name: '', value: { a: 9 }, modelDefinition, fieldErrors: error, minimalSwaggerConfig }); + const result = v.validateModel({ name: '', value: { a: 9 }, modelDefinition, fieldErrors: error, isBodyParam: true, minimalSwaggerConfig }); expect(Object.keys(error)).to.be.empty; expect(result).to.eql({ a: 9 }); }); @@ -254,6 +266,7 @@ describe('ValidationService', () => { }, 'body', error, + true, undefined, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, ); @@ -285,6 +298,7 @@ describe('ValidationService', () => { }, 'body', error, + true, undefined, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, ); @@ -301,7 +315,7 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true, }; - const result = new ValidationService({}).ValidateParam(propertySchema, value, 'defaultProp', {}, undefined, minimalSwaggerConfig); + const result = new ValidationService({}).ValidateParam(propertySchema, value, 'defaultProp', {}, true, undefined, minimalSwaggerConfig); expect(result).to.equal(666); }); @@ -312,7 +326,7 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true, }; - const result = new ValidationService({}).ValidateParam(propertySchema, value, 'defaultProp', {}, undefined, minimalSwaggerConfig); + const result = new ValidationService({}).ValidateParam(propertySchema, value, 'defaultProp', {}, true, undefined, minimalSwaggerConfig); expect(result).to.equal(123); }); @@ -323,7 +337,7 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true, }; - const result = new ValidationService({}).ValidateParam(propertySchema, value, 'defaultProp', {}, undefined, minimalSwaggerConfig); + const result = new ValidationService({}).ValidateParam(propertySchema, value, 'defaultProp', {}, true, undefined, minimalSwaggerConfig); expect(result).to.equal(666); }); }); @@ -335,7 +349,7 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true, }; - const result = new ValidationService({}).validateInt('name', value, {}, minimalSwaggerConfig); + const result = new ValidationService({}).validateInt('name', value, {}, true, minimalSwaggerConfig); expect(result).to.equal(Number(value)); }); @@ -347,7 +361,7 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true, }; - const result = new ValidationService({}).validateInt(name, value, error, minimalSwaggerConfig); + const result = new ValidationService({}).validateInt(name, value, error, true, minimalSwaggerConfig); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`invalid integer number`); }); @@ -361,7 +375,7 @@ describe('ValidationService', () => { bodyCoercion: true, }; const validator = { minimum: { value: 10 }, maximum: { value: 12 } }; - const result = new ValidationService({}).validateInt(name, value, error, minimalSwaggerConfig, validator); + const result = new ValidationService({}).validateInt(name, value, error, true, minimalSwaggerConfig, validator); expect(result).to.equal(Number(value)); }); @@ -374,7 +388,7 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true, }; - const result = new ValidationService({}).validateInt(name, value, error, minimalSwaggerConfig, validator); + const result = new ValidationService({}).validateInt(name, value, error, true, minimalSwaggerConfig, validator); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`min 12`); }); @@ -388,7 +402,7 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true, }; - const result = new ValidationService({}).validateInt(name, value, error, minimalSwaggerConfig, validator); + const result = new ValidationService({}).validateInt(name, value, error, true, minimalSwaggerConfig, validator); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`max 10`); }); @@ -397,7 +411,7 @@ describe('ValidationService', () => { const name = 'name'; const value: any = '10'; const error: FieldErrors = {}; - const result = new ValidationService({}).validateInt(name, value, error, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }); + const result = new ValidationService({}).validateInt(name, value, error, true, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }); expect(result).to.deep.equal(undefined); expect(error[name].message).to.equal('invalid integer number'); expect(error[name].value).to.equal('10'); @@ -411,7 +425,7 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true, }; - const result = new ValidationService({}).validateFloat('name', value, {}, minimalSwaggerConfig); + const result = new ValidationService({}).validateFloat('name', value, {}, true, minimalSwaggerConfig); expect(result).to.equal(Number(value)); }); @@ -423,7 +437,7 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true, }; - const result = new ValidationService({}).validateFloat(name, value, error, minimalSwaggerConfig); + const result = new ValidationService({}).validateFloat(name, value, error, true, minimalSwaggerConfig); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`invalid float number`); }); @@ -437,7 +451,7 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true, }; - const result = new ValidationService({}).validateFloat(name, value, error, minimalSwaggerConfig, validator); + const result = new ValidationService({}).validateFloat(name, value, error, true, minimalSwaggerConfig, validator); expect(result).to.equal(Number(value)); }); @@ -450,7 +464,7 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true, }; - const result = new ValidationService({}).validateFloat(name, value, error, minimalSwaggerConfig, validator); + const result = new ValidationService({}).validateFloat(name, value, error, true, minimalSwaggerConfig, validator); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`min 12.5`); }); @@ -464,7 +478,7 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true, }; - const result = new ValidationService({}).validateFloat(name, value, error, minimalSwaggerConfig, validator); + const result = new ValidationService({}).validateFloat(name, value, error, true, minimalSwaggerConfig, validator); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`max 10.5`); }); @@ -473,7 +487,7 @@ describe('ValidationService', () => { const name = 'name'; const value: any = '10.1'; const error: FieldErrors = {}; - const result = new ValidationService({}).validateFloat(name, value, error, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }); + const result = new ValidationService({}).validateFloat(name, value, error, true, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }); expect(result).to.deep.equal(undefined); expect(error[name].message).to.equal('invalid float number'); expect(error[name].value).to.equal('10.1'); @@ -487,7 +501,7 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true, }; - const result = new ValidationService({}).validateBool('name', value, {}, minimalSwaggerConfig); + const result = new ValidationService({}).validateBool('name', value, {}, true, minimalSwaggerConfig); expect(result).to.equal(true); }); @@ -497,7 +511,7 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true, }; - const result = new ValidationService({}).validateBool('name', value, {}, minimalSwaggerConfig); + const result = new ValidationService({}).validateBool('name', value, {}, true, minimalSwaggerConfig); expect(result).to.equal(false); }); @@ -507,7 +521,7 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true, }; - const result = new ValidationService({}).validateBool('name', value, {}, minimalSwaggerConfig); + const result = new ValidationService({}).validateBool('name', value, {}, true, minimalSwaggerConfig); expect(result).to.equal(false); }); @@ -515,7 +529,7 @@ describe('ValidationService', () => { const name = 'name'; const value = 'false'; const error: FieldErrors = {}; - const result = new ValidationService({}).validateBool(name, value, error, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }); + const result = new ValidationService({}).validateBool(name, value, error, true, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }); expect(result).to.deep.equal(undefined); expect(error[name].message).to.equal('invalid boolean value'); expect(error[name].value).to.equal('false'); @@ -841,7 +855,7 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true, }; - const result = new ValidationService({}).validateDate('name', value, {}, minimalSwaggerConfig); + const result = new ValidationService({}).validateDate('name', value, {}, true, minimalSwaggerConfig); expect(result).to.deep.equal(new Date(value)); }); @@ -853,7 +867,7 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true, }; - const result = new ValidationService({}).validateDate(name, value, error, minimalSwaggerConfig); + const result = new ValidationService({}).validateDate(name, value, error, true, minimalSwaggerConfig); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`invalid ISO 8601 date format, i.e. YYYY-MM-DD`); }); @@ -866,7 +880,7 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true, }; - const result = new ValidationService({}).validateDate(name, value, error, minimalSwaggerConfig, { minDate: { value: '2017-07-01' } }); + const result = new ValidationService({}).validateDate(name, value, error, true, minimalSwaggerConfig, { minDate: { value: '2017-07-01' } }); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`minDate '2017-07-01'`); }); @@ -879,7 +893,7 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true, }; - const result = new ValidationService({}).validateDate(name, value, error, minimalSwaggerConfig, { maxDate: { value: '2017-05-01' } }); + const result = new ValidationService({}).validateDate(name, value, error, true, minimalSwaggerConfig, { maxDate: { value: '2017-05-01' } }); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`maxDate '2017-05-01'`); }); @@ -888,7 +902,7 @@ describe('ValidationService', () => { const name = 'name'; const value: any = 1234; const error: FieldErrors = {}; - const result = new ValidationService({}).validateDate(name, value, error, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }); + const result = new ValidationService({}).validateDate(name, value, error, true, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }); expect(result).to.deep.equal(undefined); expect(error[name].message).to.equal('invalid ISO 8601 date format, i.e. YYYY-MM-DD'); expect(error[name].value).to.equal(1234); @@ -902,7 +916,7 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true, }; - const result = new ValidationService({}).validateDateTime('name', value, {}, minimalSwaggerConfig); + const result = new ValidationService({}).validateDateTime('name', value, {}, true, minimalSwaggerConfig); expect(result).to.deep.equal(new Date(value)); }); @@ -914,7 +928,7 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true, }; - const result = new ValidationService({}).validateDateTime(name, value, error, minimalSwaggerConfig); + const result = new ValidationService({}).validateDateTime(name, value, error, true, minimalSwaggerConfig); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`invalid ISO 8601 datetime format, i.e. YYYY-MM-DDTHH:mm:ss`); }); @@ -927,7 +941,7 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true, }; - const result = new ValidationService({}).validateDateTime(name, value, error, minimalSwaggerConfig, { minDate: { value: '2017-12-31T00:00:00' } }); + const result = new ValidationService({}).validateDateTime(name, value, error, true, minimalSwaggerConfig, { minDate: { value: '2017-12-31T00:00:00' } }); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`minDate '2017-12-31T00:00:00'`); }); @@ -940,7 +954,7 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true, }; - const result = new ValidationService({}).validateDateTime(name, value, error, minimalSwaggerConfig, { maxDate: { value: '2017-12-29T00:00:00' } }); + const result = new ValidationService({}).validateDateTime(name, value, error, true, minimalSwaggerConfig, { maxDate: { value: '2017-12-29T00:00:00' } }); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`maxDate '2017-12-29T00:00:00'`); }); @@ -949,7 +963,7 @@ describe('ValidationService', () => { const name = 'name'; const value: any = 1234; const error: FieldErrors = {}; - const result = new ValidationService({}).validateDateTime(name, value, error, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }); + const result = new ValidationService({}).validateDateTime(name, value, error, true, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }); expect(result).to.deep.equal(undefined); expect(error[name].message).to.equal('invalid ISO 8601 datetime format, i.e. YYYY-MM-DDTHH:mm:ss'); expect(error[name].value).to.equal(1234); @@ -959,7 +973,7 @@ describe('ValidationService', () => { describe('Array validate', () => { it('should array value', () => { const value = ['A', 'B', 'C']; - const result = new ValidationService({}).validateArray('name', value, {}, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, { dataType: 'string' }); + const result = new ValidationService({}).validateArray('name', value, {}, true, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, { dataType: 'string' }); expect(result).to.deep.equal(value); }); @@ -967,7 +981,7 @@ describe('ValidationService', () => { const name = 'name'; const value = ['A', 10, true]; const error: FieldErrors = {}; - const result = new ValidationService({}).validateArray(name, value, error, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, { dataType: 'integer' }); + const result = new ValidationService({}).validateArray(name, value, error, true, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, { dataType: 'integer' }); expect(result).to.deep.equal(undefined); expect(error[`${name}.$0`].message).to.equal('invalid integer number'); expect(error[`${name}.$0`].value).to.equal('A'); @@ -986,7 +1000,7 @@ describe('ValidationService', () => { a: { dataType: 'string', required: true }, }, }, - }).validateArray(name, value, error, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, { ref: 'ExampleModel' }); + }).validateArray(name, value, error, true, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, { ref: 'ExampleModel' }); expect(result).to.deep.equal(undefined); expect(error).to.deep.equal({ [`${name}.$0.a`]: { @@ -1000,7 +1014,15 @@ describe('ValidationService', () => { const name = 'name'; const value = [80, 10, 199]; const error: FieldErrors = {}; - const result = new ValidationService({}).validateArray(name, value, error, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, { dataType: 'integer' }, { minItems: { value: 4 } }); + const result = new ValidationService({}).validateArray( + name, + value, + error, + true, + { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, + { dataType: 'integer' }, + { minItems: { value: 4 } }, + ); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`minItems 4`); }); @@ -1009,7 +1031,15 @@ describe('ValidationService', () => { const name = 'name'; const value = [80, 10, 199]; const error: FieldErrors = {}; - const result = new ValidationService({}).validateArray(name, value, error, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, { dataType: 'integer' }, { maxItems: { value: 2 } }); + const result = new ValidationService({}).validateArray( + name, + value, + error, + true, + { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, + { dataType: 'integer' }, + { maxItems: { value: 2 } }, + ); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`maxItems 2`); }); @@ -1018,7 +1048,7 @@ describe('ValidationService', () => { const name = 'name'; const value = [10, 10, 20]; const error: FieldErrors = {}; - const result = new ValidationService({}).validateArray(name, value, error, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, { dataType: 'integer' }, { uniqueItems: {} }); + const result = new ValidationService({}).validateArray(name, value, error, true, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, { dataType: 'integer' }, { uniqueItems: {} }); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`required unique array`); }); @@ -1034,7 +1064,7 @@ describe('ValidationService', () => { bodyCoercion: true, }; const fieldErrors = {}; - const result = v.validateArray('name', ['foo', 'bar', 'foo', 'foobar'], fieldErrors, minimalSwaggerConfig, { dataType: 'refEnum', ref: 'enumModel' }); + const result = v.validateArray('name', ['foo', 'bar', 'foo', 'foobar'], fieldErrors, true, minimalSwaggerConfig, { dataType: 'refEnum', ref: 'enumModel' }); expect(Object.keys(fieldErrors)).to.not.be.empty; expect(result).to.be.undefined; expect(fieldErrors).to.deep.equal({ 'name.$3': { message: "should be one of the following; ['foo','bar']", value: 'foobar' } }); @@ -1044,7 +1074,7 @@ describe('ValidationService', () => { const name = 'name'; const value: any = 'some primitive string'; const error: FieldErrors = {}; - const result = new ValidationService({}).validateArray(name, value, error, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }, { dataType: 'string' }); + const result = new ValidationService({}).validateArray(name, value, error, true, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }, { dataType: 'string' }); expect(result).to.deep.equal(undefined); expect(error[name].message).to.equal('invalid array'); expect(error[name].value).to.equal('some primitive string'); @@ -1076,8 +1106,8 @@ describe('ValidationService', () => { bodyCoercion: true, }; const schema: TsoaRoute.PropertySchema = { subSchemas: [{ ref: 'TypeA' }, { ref: 'TypeB' }] }; - const resultA = v.validateUnion(name, { type: 'A', a: 100 }, error, minimalSwaggerConfig, schema); - const resultB = v.validateUnion(name, { type: 'B', b: 20 }, error, minimalSwaggerConfig, schema); + const resultA = v.validateUnion(name, { type: 'A', a: 100 }, error, true, minimalSwaggerConfig, schema); + const resultB = v.validateUnion(name, { type: 'B', b: 20 }, error, true, minimalSwaggerConfig, schema); expect(resultA).to.deep.equal({ type: 'A', a: 100 }); expect(resultB).to.deep.equal({ type: 'B', b: 20 }); }); @@ -1091,7 +1121,7 @@ describe('ValidationService', () => { }; const schema: TsoaRoute.PropertySchema = { dataType: 'union', subSchemas: [{ dataType: 'integer' }, { dataType: 'string' }], required: true, validators: { minimum: { value: 5 } } }; - const result = v.validateUnion('union', 2, errors, minimalSwaggerConfig, schema); + const result = v.validateUnion('union', 2, errors, true, minimalSwaggerConfig, schema); expect(errors).to.deep.equal({ union: { message: 'Could not match the union against any of the items. Issues: [{"union":{"message":"min 5","value":2}},{"union":{"message":"invalid string value","value":2}}]', @@ -1161,7 +1191,7 @@ describe('ValidationService', () => { // Act const name = 'dataToValidate'; - const validatedData = v.validateIntersection('and', dataToValidate, errorDictionary, minimalSwaggerConfig, subSchemas, name + '.'); + const validatedData = v.validateIntersection('and', dataToValidate, errorDictionary, true, minimalSwaggerConfig, subSchemas, name + '.'); // Assert const expectedValues = { ...dataToValidate, dateTimeValue: new Date('2017-01-01T00:00:00') }; @@ -1175,7 +1205,7 @@ describe('ValidationService', () => { }; const subSchemas2 = subSchemas.concat([{ ref: 'TypeAliasModelDate' }]); - const validatedData2 = v.validateIntersection('and', dataToValidate2, errorDictionary2, minimalSwaggerConfig, subSchemas2, name + '.'); + const validatedData2 = v.validateIntersection('and', dataToValidate2, errorDictionary2, true, minimalSwaggerConfig, subSchemas2, name + '.'); const expectedValues2 = { ...expectedValues, dateValue: new Date('2017-01-01') }; expect(errorDictionary2).to.deep.equal({}); @@ -1241,7 +1271,7 @@ describe('ValidationService', () => { }; const withUnionErrorDictionary1 = {}; - withUnionValidationService.validateIntersection('union', withUnionDataToValidate1, withUnionErrorDictionary1, minimalSwaggerConfig, withUnionsSubSchemas, withUnionsName + '.'); + withUnionValidationService.validateIntersection('union', withUnionDataToValidate1, withUnionErrorDictionary1, true, minimalSwaggerConfig, withUnionsSubSchemas, withUnionsName + '.'); // Assert expect(withUnionErrorDictionary1).to.deep.equal({ @@ -1263,6 +1293,7 @@ describe('ValidationService', () => { 'union', withUnionDataToValidate2, withUnionErrorDictionary2, + true, minimalSwaggerConfig, withUnionsSubSchemas, withUnionsName + '.', @@ -1283,6 +1314,7 @@ describe('ValidationService', () => { 'union', withUnionDataToValidate3, withUnionErrorDictionary3, + true, minimalSwaggerConfig, withUnionsSubSchemas, withUnionsName + '.', @@ -1415,7 +1447,7 @@ describe('ValidationService', () => { // Act const errorDictionary: FieldErrors = {}; - const validatedData = v.validateIntersection('and', input, errorDictionary, minimalSwaggerConfig, subSchemas, refName + '.'); + const validatedData = v.validateIntersection('and', input, errorDictionary, true, minimalSwaggerConfig, subSchemas, refName + '.'); // Assert expect(errorDictionary, `validInputs[${i}] returned errors`).to.deep.equal({}); @@ -1450,7 +1482,7 @@ describe('ValidationService', () => { // Act const errorDictionary: FieldErrors = {}; - const validatedData = v.validateIntersection('and', invalidInput, errorDictionary, minimalSwaggerConfig, subSchemas, refName + '.'); + const validatedData = v.validateIntersection('and', invalidInput, errorDictionary, true, minimalSwaggerConfig, subSchemas, refName + '.'); // Assert expect(errorDictionary, `${name}[${i}] did not return errors`).to.not.deep.equal({}); @@ -1476,7 +1508,7 @@ describe('ValidationService', () => { bodyCoercion: true, }; const fieldErrors: FieldErrors = {}; - const result = new ValidationService({}).ValidateParam(propertySchema, value, 'defaultProp', fieldErrors, undefined, minimalSwaggerConfig); + const result = new ValidationService({}).ValidateParam(propertySchema, value, 'defaultProp', fieldErrors, true, undefined, minimalSwaggerConfig); expect(Object.keys(fieldErrors)).to.be.empty; expect(result).to.be.undefined; }); @@ -1489,7 +1521,7 @@ describe('ValidationService', () => { bodyCoercion: true, }; const fieldErrors: FieldErrors = {}; - const result = new ValidationService({}).ValidateParam(propertySchema, value, 'defaultProp', fieldErrors, undefined, minimalSwaggerConfig); + const result = new ValidationService({}).ValidateParam(propertySchema, value, 'defaultProp', fieldErrors, true, undefined, minimalSwaggerConfig); expect(Object.keys(fieldErrors)).to.not.be.empty; expect(result).to.be.undefined; }); @@ -1507,7 +1539,7 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true, }; - const result = v.validateModel({ name: '', value: {}, modelDefinition, fieldErrors: error, minimalSwaggerConfig }); + const result = v.validateModel({ name: '', value: {}, modelDefinition, fieldErrors: error, isBodyParam: true, minimalSwaggerConfig }); expect(Object.keys(error)).to.be.empty; // use JSON strngify to allow comparison of undefined values expect(JSON.stringify(result, replacer)).to.equal(JSON.stringify(result)); @@ -1526,7 +1558,7 @@ describe('ValidationService', () => { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true, }; - const result = v.validateModel({ name: '', value: {}, modelDefinition, fieldErrors: error, minimalSwaggerConfig }); + const result = v.validateModel({ name: '', value: {}, modelDefinition, fieldErrors: error, isBodyParam: true, minimalSwaggerConfig }); expect(Object.keys(error)).to.be.empty; // use JSON strngify to allow comparison of undefined values expect(JSON.stringify(result, replacer)).to.equal(JSON.stringify({ a: undefined }, replacer)); @@ -1540,7 +1572,7 @@ describe('ValidationService', () => { bodyCoercion: true, }; const fieldErrors: FieldErrors = {}; - const result = new ValidationService({}).ValidateParam(propertySchema, value, 'defaultProp', fieldErrors, undefined, minimalSwaggerConfig); + const result = new ValidationService({}).ValidateParam(propertySchema, value, 'defaultProp', fieldErrors, true, undefined, minimalSwaggerConfig); expect(Object.keys(fieldErrors)).to.not.be.empty; expect(result).to.be.undefined; }); diff --git a/tests/unit/templating/templateHelpers.spec.ts b/tests/unit/templating/templateHelpers.spec.ts index afca5d40b..2c5f1bd5b 100644 --- a/tests/unit/templating/templateHelpers.spec.ts +++ b/tests/unit/templating/templateHelpers.spec.ts @@ -54,7 +54,7 @@ it('should allow additionalProperties (on a union) if noImplicitAdditionalProper // Act const name = 'dataToValidate'; - const result = v.validateUnion('or', dataToValidate, errorDictionary, minimalSwaggerConfig, unionProperty, name + '.'); + const result = v.validateUnion('or', dataToValidate, errorDictionary, true, minimalSwaggerConfig, unionProperty, name + '.'); // Assert expect(errorDictionary).to.deep.eq({}); @@ -108,7 +108,7 @@ it('should throw if the data has additionalProperties (on a union) if noImplicit // Act const name = 'dataToValidate'; - v.validateUnion('or', dataToValidate, errorDictionary, minimalSwaggerConfig, unionPropertySchema, name + '.'); + v.validateUnion('or', dataToValidate, errorDictionary, true, minimalSwaggerConfig, unionPropertySchema, name + '.'); // Assert const errorKeys = Object.keys(errorDictionary); @@ -170,7 +170,7 @@ it('should throw if the data has additionalProperties (on a intersection) if noI // Act const name = 'dataToValidate'; - v.validateIntersection('and', dataToValidate, errorDictionary, minimalSwaggerConfig, subSchemas, name + '.'); + v.validateIntersection('and', dataToValidate, errorDictionary, true, minimalSwaggerConfig, subSchemas, name + '.'); // Assert const errorKeys = Object.keys(errorDictionary); @@ -263,7 +263,7 @@ it('should throw if the data has additionalProperties (on a nested Object) if no }; // Act - const result = v.validateNestedObjectLiteral('objLiteral', dataToValidate, errorDictionary, minimalSwaggerConfig, models[refName].properties.objLiteral.nestedProperties, false, refName + '.'); + const result = v.validateNestedObjectLiteral('objLiteral', dataToValidate, errorDictionary, true, minimalSwaggerConfig, models[refName].properties.objLiteral.nestedProperties, false, refName + '.'); // Assert expect(errorDictionary).to.deep.eq({ @@ -324,7 +324,7 @@ it('should not throw if the data has additionalProperties (on a intersection) if // Act const name = 'dataToValidate'; - const result = v.validateIntersection('and', dataToValidate, errorDictionary, minimalSwaggerConfig, subSchemas, name + '.'); + const result = v.validateIntersection('and', dataToValidate, errorDictionary, true, minimalSwaggerConfig, subSchemas, name + '.'); // Assert expect(errorDictionary).to.deep.eq({}); @@ -382,7 +382,7 @@ it('should not throw if the data has additionalProperties (on a intersection) if // Act const name = 'dataToValidate'; - const result = v.validateIntersection('and', dataToValidate, errorDictionary, minimalSwaggerConfig, subSchemas, name + '.'); + const result = v.validateIntersection('and', dataToValidate, errorDictionary, true, minimalSwaggerConfig, subSchemas, name + '.'); // Assert expect(errorDictionary).to.deep.eq({}); @@ -465,7 +465,7 @@ it('should not throw if the data has additionalProperties (on a nested Object) i }; // Act - const result = v.validateNestedObjectLiteral('objLiteral', dataToValidate, errorDictionary, minimalSwaggerConfig, models[refName].properties.objLiteral.nestedProperties, false, refName + '.'); + const result = v.validateNestedObjectLiteral('objLiteral', dataToValidate, errorDictionary, true, minimalSwaggerConfig, models[refName].properties.objLiteral.nestedProperties, false, refName + '.'); // Assert expect(errorDictionary).to.deep.eq({}); @@ -561,7 +561,7 @@ it('should not throw if the data has additionalProperties (on a nested Object) i }; // Act - const result = v.validateNestedObjectLiteral('objLiteral', dataToValidate, errorDictionary, minimalSwaggerConfig, models[refName].properties.objLiteral.nestedProperties, false, refName + '.'); + const result = v.validateNestedObjectLiteral('objLiteral', dataToValidate, errorDictionary, true, minimalSwaggerConfig, models[refName].properties.objLiteral.nestedProperties, false, refName + '.'); // Assert expect(errorDictionary).to.deep.eq({}); @@ -604,7 +604,7 @@ it('should throw if properties on nOl are missing', () => { bodyCoercion: true, }; - v.validateNestedObjectLiteral('nested', {}, errors, minimalSwaggerConfig, schema, true, 'Model.'); + v.validateNestedObjectLiteral('nested', {}, errors, true, minimalSwaggerConfig, schema, true, 'Model.'); expect(Object.keys(errors).length).to.equal(2); @@ -615,7 +615,7 @@ it('should throw if properties on nOl are missing', () => { const nestedErrors = {}; - v.validateNestedObjectLiteral('nested', { street: {} }, nestedErrors, minimalSwaggerConfig, schema, true, 'Model.'); + v.validateNestedObjectLiteral('nested', { street: {} }, nestedErrors, true, minimalSwaggerConfig, schema, true, 'Model.'); expect(nestedErrors).to.deep.eq({ 'Model.nested.country': { From 0e76169e902564781b44a316b4b12fb49a06b539 Mon Sep 17 00:00:00 2001 From: Christian Friedow Date: Wed, 13 Mar 2024 15:35:46 +0100 Subject: [PATCH 7/8] refactor: extract function to check for js type if body coercion is disabled --- .../runtime/src/routeGeneration/templateHelpers.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/runtime/src/routeGeneration/templateHelpers.ts b/packages/runtime/src/routeGeneration/templateHelpers.ts index 5538534e4..feddf7aca 100644 --- a/packages/runtime/src/routeGeneration/templateHelpers.ts +++ b/packages/runtime/src/routeGeneration/templateHelpers.ts @@ -90,6 +90,10 @@ export class ValidationService { } } + public hasCorrectJsType(value: any, type: 'object' | 'boolean' | 'number' | 'string', isBodyParam: boolean, bodyCoercion: boolean) { + return !isBodyParam || bodyCoercion || typeof value === type; + } + public validateNestedObjectLiteral( name: string, value: any, @@ -165,7 +169,7 @@ export class ValidationService { } public validateInt(name: string, value: any, fieldErrors: FieldErrors, isBodyParam: boolean, swaggerConfig: AdditionalProps, validators?: IntegerValidator, parent = '') { - if ((isBodyParam && swaggerConfig.bodyCoercion === false && typeof value !== 'number') || !validator.isInt(String(value))) { + if (!this.hasCorrectJsType(value, 'number', isBodyParam, swaggerConfig.bodyCoercion) || !validator.isInt(String(value))) { let message = `invalid integer number`; if (validators) { if (validators.isInt && validators.isInt.errorMsg) { @@ -208,7 +212,7 @@ export class ValidationService { } public validateFloat(name: string, value: any, fieldErrors: FieldErrors, isBodyParam: boolean, swaggerConfig: AdditionalProps, validators?: FloatValidator, parent = '') { - if ((isBodyParam && swaggerConfig.bodyCoercion === false && typeof value !== 'number') || !validator.isFloat(String(value))) { + if (!this.hasCorrectJsType(value, 'number', isBodyParam, swaggerConfig.bodyCoercion) || !validator.isFloat(String(value))) { let message = 'invalid float number'; if (validators) { if (validators.isFloat && validators.isFloat.errorMsg) { @@ -274,7 +278,7 @@ export class ValidationService { } public validateDate(name: string, value: any, fieldErrors: FieldErrors, isBodyParam: boolean, swaggerConfig: AdditionalProps, validators?: DateValidator, parent = '') { - if ((isBodyParam && swaggerConfig.bodyCoercion === false && typeof value !== 'string') || !validator.isISO8601(String(value), { strict: true })) { + if (!this.hasCorrectJsType(value, 'string', isBodyParam, swaggerConfig.bodyCoercion) || !validator.isISO8601(String(value), { strict: true })) { const message = validators && validators.isDate && validators.isDate.errorMsg ? validators.isDate.errorMsg : `invalid ISO 8601 date format, i.e. YYYY-MM-DD`; fieldErrors[parent + name] = { message, @@ -311,7 +315,7 @@ export class ValidationService { } public validateDateTime(name: string, value: any, fieldErrors: FieldErrors, isBodyParam: boolean, swaggerConfig: AdditionalProps, validators?: DateTimeValidator, parent = '') { - if ((isBodyParam && swaggerConfig.bodyCoercion === false && typeof value !== 'string') || !validator.isISO8601(String(value), { strict: true })) { + if (!this.hasCorrectJsType(value, 'string', isBodyParam, swaggerConfig.bodyCoercion) || !validator.isISO8601(String(value), { strict: true })) { const message = validators && validators.isDateTime && validators.isDateTime.errorMsg ? validators.isDateTime.errorMsg : `invalid ISO 8601 datetime format, i.e. YYYY-MM-DDTHH:mm:ss`; fieldErrors[parent + name] = { message, From ddce959212bd800d38bdef16f976043044ee80e9 Mon Sep 17 00:00:00 2001 From: Christian Friedow Date: Thu, 14 Mar 2024 12:29:24 +0100 Subject: [PATCH 8/8] refactor: move swaggerConfig as config to be a parameter on the ValidationService --- .../src/routeGeneration/templateHelpers.ts | 154 ++- .../express/expressTemplateService.ts | 27 +- .../templates/hapi/hapiTemplateService.ts | 23 +- .../templates/koa/koaTemplateService.ts | 27 +- .../templates/templateService.ts | 4 +- .../templates/models.hbs | 14 +- tests/unit/swagger/templateHelpers.spec.ts | 931 +++++++++++------- tests/unit/templating/templateHelpers.spec.ts | 74 +- 8 files changed, 733 insertions(+), 521 deletions(-) diff --git a/packages/runtime/src/routeGeneration/templateHelpers.ts b/packages/runtime/src/routeGeneration/templateHelpers.ts index feddf7aca..2c78859e2 100644 --- a/packages/runtime/src/routeGeneration/templateHelpers.ts +++ b/packages/runtime/src/routeGeneration/templateHelpers.ts @@ -14,15 +14,18 @@ export function ValidateParam( fieldErrors: FieldErrors, isBodyParam: boolean, parent = '', - swaggerConfig: AdditionalProps, + config: AdditionalProps, ) { - return new ValidationService(generatedModels).ValidateParam(property, value, name, fieldErrors, isBodyParam, parent, swaggerConfig); + return new ValidationService(generatedModels, config).ValidateParam(property, value, name, fieldErrors, isBodyParam, parent); } export class ValidationService { - constructor(private readonly models: TsoaRoute.Models) {} + constructor( + private readonly models: TsoaRoute.Models, + private readonly config: AdditionalProps, + ) {} - public ValidateParam(property: TsoaRoute.PropertySchema, rawValue: any, name = '', fieldErrors: FieldErrors, isBodyParam: boolean, parent = '', minimalSwaggerConfig: AdditionalProps) { + public ValidateParam(property: TsoaRoute.PropertySchema, rawValue: any, name = '', fieldErrors: FieldErrors, isBodyParam: boolean, parent = '') { let value = rawValue; // If undefined is allowed type, we can move to value validation if (value === undefined && property.dataType !== 'undefined') { @@ -55,43 +58,43 @@ export class ValidationService { case 'string': return this.validateString(name, value, fieldErrors, property.validators as StringValidator, parent); case 'boolean': - return this.validateBool(name, value, fieldErrors, isBodyParam, minimalSwaggerConfig, property.validators as BooleanValidator, parent); + return this.validateBool(name, value, fieldErrors, isBodyParam, property.validators as BooleanValidator, parent); case 'integer': case 'long': - return this.validateInt(name, value, fieldErrors, isBodyParam, minimalSwaggerConfig, property.validators as IntegerValidator, parent); + return this.validateInt(name, value, fieldErrors, isBodyParam, property.validators as IntegerValidator, parent); case 'float': case 'double': - return this.validateFloat(name, value, fieldErrors, isBodyParam, minimalSwaggerConfig, property.validators as FloatValidator, parent); + return this.validateFloat(name, value, fieldErrors, isBodyParam, property.validators as FloatValidator, parent); case 'enum': return this.validateEnum(name, value, fieldErrors, property.enums, parent); case 'array': - return this.validateArray(name, value, fieldErrors, isBodyParam, minimalSwaggerConfig, property.array, property.validators as ArrayValidator, parent); + return this.validateArray(name, value, fieldErrors, isBodyParam, property.array, property.validators as ArrayValidator, parent); case 'date': - return this.validateDate(name, value, fieldErrors, isBodyParam, minimalSwaggerConfig, property.validators as DateValidator, parent); + return this.validateDate(name, value, fieldErrors, isBodyParam, property.validators as DateValidator, parent); case 'datetime': - return this.validateDateTime(name, value, fieldErrors, isBodyParam, minimalSwaggerConfig, property.validators as DateTimeValidator, parent); + return this.validateDateTime(name, value, fieldErrors, isBodyParam, property.validators as DateTimeValidator, parent); case 'buffer': return this.validateBuffer(name, value); case 'union': - return this.validateUnion(name, value, fieldErrors, isBodyParam, minimalSwaggerConfig, property, parent); + return this.validateUnion(name, value, fieldErrors, isBodyParam, property, parent); case 'intersection': - return this.validateIntersection(name, value, fieldErrors, isBodyParam, minimalSwaggerConfig, property.subSchemas, parent); + return this.validateIntersection(name, value, fieldErrors, isBodyParam, property.subSchemas, parent); case 'undefined': return this.validateUndefined(name, value, fieldErrors, parent); case 'any': return value; case 'nestedObjectLiteral': - return this.validateNestedObjectLiteral(name, value, fieldErrors, isBodyParam, minimalSwaggerConfig, property.nestedProperties, property.additionalProperties, parent); + return this.validateNestedObjectLiteral(name, value, fieldErrors, isBodyParam, property.nestedProperties, property.additionalProperties, parent); default: if (property.ref) { - return this.validateModel({ name, value, modelDefinition: this.models[property.ref], fieldErrors, isBodyParam, parent, minimalSwaggerConfig }); + return this.validateModel({ name, value, modelDefinition: this.models[property.ref], fieldErrors, isBodyParam, parent }); } return value; } } - public hasCorrectJsType(value: any, type: 'object' | 'boolean' | 'number' | 'string', isBodyParam: boolean, bodyCoercion: boolean) { - return !isBodyParam || bodyCoercion || typeof value === type; + public hasCorrectJsType(value: any, type: 'object' | 'boolean' | 'number' | 'string', isBodyParam: boolean) { + return !isBodyParam || this.config.bodyCoercion || typeof value === type; } public validateNestedObjectLiteral( @@ -99,7 +102,6 @@ export class ValidationService { value: any, fieldErrors: FieldErrors, isBodyParam: boolean, - swaggerConfig: AdditionalProps, nestedProperties: { [name: string]: TsoaRoute.PropertySchema } | undefined, additionalProperties: TsoaRoute.PropertySchema | boolean | undefined, parent: string, @@ -123,9 +125,9 @@ export class ValidationService { ); } - const propHandling = swaggerConfig.noImplicitAdditionalProperties; + const propHandling = this.config.noImplicitAdditionalProperties; if (propHandling !== 'ignore') { - const excessProps = this.getExcessPropertiesFor({ dataType: 'refObject', properties: nestedProperties, additionalProperties }, Object.keys(value), swaggerConfig); + const excessProps = this.getExcessPropertiesFor({ dataType: 'refObject', properties: nestedProperties, additionalProperties }, Object.keys(value)); if (excessProps.length > 0) { if (propHandling === 'silently-remove-extras') { excessProps.forEach(excessProp => { @@ -142,7 +144,7 @@ export class ValidationService { } Object.keys(nestedProperties).forEach(key => { - const validatedProp = this.ValidateParam(nestedProperties[key], value[key], key, fieldErrors, isBodyParam, parent + name + '.', swaggerConfig); + const validatedProp = this.ValidateParam(nestedProperties[key], value[key], key, fieldErrors, isBodyParam, parent + name + '.'); // Add value from validator if it's not undefined or if value is required and unfedined is valid type if (validatedProp !== undefined || (nestedProperties[key].dataType === 'undefined' && nestedProperties[key].required)) { @@ -153,7 +155,7 @@ export class ValidationService { if (typeof additionalProperties === 'object' && typeof value === 'object') { const keys = Object.keys(value).filter(key => typeof nestedProperties[key] === 'undefined'); keys.forEach(key => { - const validatedProp = this.ValidateParam(additionalProperties, value[key], key, fieldErrors, isBodyParam, parent + name + '.', swaggerConfig); + const validatedProp = this.ValidateParam(additionalProperties, value[key], key, fieldErrors, isBodyParam, parent + name + '.'); // Add value from validator if it's not undefined or if value is required and unfedined is valid type if (validatedProp !== undefined || (additionalProperties.dataType === 'undefined' && additionalProperties.required)) { value[key] = validatedProp; @@ -168,8 +170,8 @@ export class ValidationService { return value; } - public validateInt(name: string, value: any, fieldErrors: FieldErrors, isBodyParam: boolean, swaggerConfig: AdditionalProps, validators?: IntegerValidator, parent = '') { - if (!this.hasCorrectJsType(value, 'number', isBodyParam, swaggerConfig.bodyCoercion) || !validator.isInt(String(value))) { + public validateInt(name: string, value: any, fieldErrors: FieldErrors, isBodyParam: boolean, validators?: IntegerValidator, parent = '') { + if (!this.hasCorrectJsType(value, 'number', isBodyParam) || !validator.isInt(String(value))) { let message = `invalid integer number`; if (validators) { if (validators.isInt && validators.isInt.errorMsg) { @@ -211,8 +213,8 @@ export class ValidationService { return numberValue; } - public validateFloat(name: string, value: any, fieldErrors: FieldErrors, isBodyParam: boolean, swaggerConfig: AdditionalProps, validators?: FloatValidator, parent = '') { - if (!this.hasCorrectJsType(value, 'number', isBodyParam, swaggerConfig.bodyCoercion) || !validator.isFloat(String(value))) { + public validateFloat(name: string, value: any, fieldErrors: FieldErrors, isBodyParam: boolean, validators?: FloatValidator, parent = '') { + if (!this.hasCorrectJsType(value, 'number', isBodyParam) || !validator.isFloat(String(value))) { let message = 'invalid float number'; if (validators) { if (validators.isFloat && validators.isFloat.errorMsg) { @@ -277,8 +279,8 @@ export class ValidationService { return members[enumMatchIndex]; } - public validateDate(name: string, value: any, fieldErrors: FieldErrors, isBodyParam: boolean, swaggerConfig: AdditionalProps, validators?: DateValidator, parent = '') { - if (!this.hasCorrectJsType(value, 'string', isBodyParam, swaggerConfig.bodyCoercion) || !validator.isISO8601(String(value), { strict: true })) { + public validateDate(name: string, value: any, fieldErrors: FieldErrors, isBodyParam: boolean, validators?: DateValidator, parent = '') { + if (!this.hasCorrectJsType(value, 'string', isBodyParam) || !validator.isISO8601(String(value), { strict: true })) { const message = validators && validators.isDate && validators.isDate.errorMsg ? validators.isDate.errorMsg : `invalid ISO 8601 date format, i.e. YYYY-MM-DD`; fieldErrors[parent + name] = { message, @@ -314,8 +316,8 @@ export class ValidationService { return dateValue; } - public validateDateTime(name: string, value: any, fieldErrors: FieldErrors, isBodyParam: boolean, swaggerConfig: AdditionalProps, validators?: DateTimeValidator, parent = '') { - if (!this.hasCorrectJsType(value, 'string', isBodyParam, swaggerConfig.bodyCoercion) || !validator.isISO8601(String(value), { strict: true })) { + public validateDateTime(name: string, value: any, fieldErrors: FieldErrors, isBodyParam: boolean, validators?: DateTimeValidator, parent = '') { + if (!this.hasCorrectJsType(value, 'string', isBodyParam) || !validator.isISO8601(String(value), { strict: true })) { const message = validators && validators.isDateTime && validators.isDateTime.errorMsg ? validators.isDateTime.errorMsg : `invalid ISO 8601 datetime format, i.e. YYYY-MM-DDTHH:mm:ss`; fieldErrors[parent + name] = { message, @@ -395,12 +397,12 @@ export class ValidationService { return stringValue; } - public validateBool(name: string, value: any, fieldErrors: FieldErrors, isBodyParam: boolean, swaggerConfig: AdditionalProps, validators?: BooleanValidator, parent = '') { + public validateBool(name: string, value: any, fieldErrors: FieldErrors, isBodyParam: boolean, validators?: BooleanValidator, parent = '') { if (value === true || value === false) { return value; } - if (!isBodyParam || swaggerConfig.bodyCoercion === true) { + if (!isBodyParam || this.config.bodyCoercion === true) { if (value === undefined || value === null) { return false; } @@ -433,17 +435,8 @@ export class ValidationService { return; } - public validateArray( - name: string, - value: any[], - fieldErrors: FieldErrors, - isBodyParam: boolean, - swaggerConfig: AdditionalProps, - schema?: TsoaRoute.PropertySchema, - validators?: ArrayValidator, - parent = '', - ) { - if ((isBodyParam && swaggerConfig.bodyCoercion === false && !Array.isArray(value)) || !schema || value === undefined) { + public validateArray(name: string, value: any[], fieldErrors: FieldErrors, isBodyParam: boolean, schema?: TsoaRoute.PropertySchema, validators?: ArrayValidator, parent = '') { + if ((isBodyParam && this.config.bodyCoercion === false && !Array.isArray(value)) || !schema || value === undefined) { const message = validators && validators.isArray && validators.isArray.errorMsg ? validators.isArray.errorMsg : `invalid array`; fieldErrors[parent + name] = { message, @@ -456,10 +449,10 @@ export class ValidationService { const previousErrors = Object.keys(fieldErrors).length; if (Array.isArray(value)) { arrayValue = value.map((elementValue, index) => { - return this.ValidateParam(schema, elementValue, `$${index}`, fieldErrors, isBodyParam, name + '.', swaggerConfig); + return this.ValidateParam(schema, elementValue, `$${index}`, fieldErrors, isBodyParam, name + '.'); }); } else { - arrayValue = [this.ValidateParam(schema, value, '$0', fieldErrors, isBodyParam, name + '.', swaggerConfig)]; + arrayValue = [this.ValidateParam(schema, value, '$0', fieldErrors, isBodyParam, name + '.')]; } if (Object.keys(fieldErrors).length > previousErrors) { @@ -507,7 +500,7 @@ export class ValidationService { return Buffer.from(value); } - public validateUnion(name: string, value: any, fieldErrors: FieldErrors, isBodyParam: boolean, swaggerConfig: AdditionalProps, property: TsoaRoute.PropertySchema, parent = ''): any { + public validateUnion(name: string, value: any, fieldErrors: FieldErrors, isBodyParam: boolean, property: TsoaRoute.PropertySchema, parent = ''): any { if (!property.subSchemas) { throw new Error( 'internal tsoa error: ' + @@ -524,15 +517,7 @@ export class ValidationService { // Clean value if it's not undefined or use undefined directly if it's undefined. // Value can be undefined if undefined is allowed datatype of the union const validateableValue = value ? JSON.parse(JSON.stringify(value)) : value; - const cleanValue = this.ValidateParam( - { ...subSchema, validators: { ...property.validators, ...subSchema.validators } }, - validateableValue, - name, - subFieldError, - isBodyParam, - parent, - swaggerConfig, - ); + const cleanValue = this.ValidateParam({ ...subSchema, validators: { ...property.validators, ...subSchema.validators } }, validateableValue, name, subFieldError, isBodyParam, parent); subFieldErrors.push(subFieldError); if (Object.keys(subFieldError).length === 0) { @@ -547,15 +532,7 @@ export class ValidationService { return; } - public validateIntersection( - name: string, - value: any, - fieldErrors: FieldErrors, - isBodyParam: boolean, - swaggerConfig: AdditionalProps, - subSchemas: TsoaRoute.PropertySchema[] | undefined, - parent = '', - ): any { + public validateIntersection(name: string, value: any, fieldErrors: FieldErrors, isBodyParam: boolean, subSchemas: TsoaRoute.PropertySchema[] | undefined, parent = ''): any { if (!subSchemas) { throw new Error( 'internal tsoa error: ' + @@ -569,10 +546,10 @@ export class ValidationService { subSchemas.forEach(subSchema => { const subFieldError: FieldErrors = {}; - const cleanValue = this.ValidateParam(subSchema, JSON.parse(JSON.stringify(value)), name, subFieldError, isBodyParam, parent, { + const cleanValue = new ValidationService(this.models, { noImplicitAdditionalProperties: 'silently-remove-extras', - bodyCoercion: swaggerConfig.bodyCoercion, - }); + bodyCoercion: this.config.bodyCoercion, + }).ValidateParam(subSchema, JSON.parse(JSON.stringify(value)), name, subFieldError, isBodyParam, parent); cleanValues = { ...cleanValues, ...cleanValue, @@ -594,27 +571,26 @@ export class ValidationService { const getRequiredPropError = (schema: TsoaRoute.ModelSchema) => { const requiredPropError = {}; - this.validateModel({ + new ValidationService(this.models, { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: this.config.bodyCoercion, + }).validateModel({ name, value: JSON.parse(JSON.stringify(value)), modelDefinition: schema, fieldErrors: requiredPropError, isBodyParam, - minimalSwaggerConfig: { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: swaggerConfig.bodyCoercion, - }, }); return requiredPropError; }; const schemasWithRequiredProps = schemas.filter(schema => Object.keys(getRequiredPropError(schema)).length === 0); - if (swaggerConfig.noImplicitAdditionalProperties === 'ignore') { + if (this.config.noImplicitAdditionalProperties === 'ignore') { return { ...value, ...cleanValues }; } - if (swaggerConfig.noImplicitAdditionalProperties === 'silently-remove-extras') { + if (this.config.noImplicitAdditionalProperties === 'silently-remove-extras') { if (schemasWithRequiredProps.length > 0) { return cleanValues; } else { @@ -626,7 +602,7 @@ export class ValidationService { } } - if (schemasWithRequiredProps.length > 0 && schemasWithRequiredProps.some(schema => this.getExcessPropertiesFor(schema, Object.keys(value), swaggerConfig).length === 0)) { + if (schemasWithRequiredProps.length > 0 && schemasWithRequiredProps.some(schema => this.getExcessPropertiesFor(schema, Object.keys(value)).length === 0)) { return cleanValues; } else { fieldErrors[parent + name] = { @@ -720,28 +696,20 @@ export class ValidationService { return { dataType: 'refObject', properties: { ...a.properties, ...b.properties }, additionalProperties: a.additionalProperties || b.additionalProperties || false }; } - private getExcessPropertiesFor(modelDefinition: TsoaRoute.RefObjectModelSchema, properties: string[], config: AdditionalProps): string[] { + private getExcessPropertiesFor(modelDefinition: TsoaRoute.RefObjectModelSchema, properties: string[]): string[] { const modelProperties = new Set(Object.keys(modelDefinition.properties)); if (modelDefinition.additionalProperties) { return []; - } else if (config.noImplicitAdditionalProperties === 'ignore') { + } else if (this.config.noImplicitAdditionalProperties === 'ignore') { return []; } else { return [...properties].filter(property => !modelProperties.has(property)); } } - public validateModel(input: { - name: string; - value: any; - modelDefinition: TsoaRoute.ModelSchema; - fieldErrors: FieldErrors; - isBodyParam: boolean; - parent?: string; - minimalSwaggerConfig: AdditionalProps; - }): any { - const { name, value, modelDefinition, fieldErrors, isBodyParam, parent = '', minimalSwaggerConfig: swaggerConfig } = input; + public validateModel(input: { name: string; value: any; modelDefinition: TsoaRoute.ModelSchema; fieldErrors: FieldErrors; isBodyParam: boolean; parent?: string }): any { + const { name, value, modelDefinition, fieldErrors, isBodyParam, parent = '' } = input; const previousErrors = Object.keys(fieldErrors).length; if (modelDefinition) { @@ -750,7 +718,7 @@ export class ValidationService { } if (modelDefinition.dataType === 'refAlias') { - return this.ValidateParam(modelDefinition.type, value, name, fieldErrors, isBodyParam, parent, swaggerConfig); + return this.ValidateParam(modelDefinition.type, value, name, fieldErrors, isBodyParam, parent); } const fieldPath = parent + name; @@ -768,7 +736,7 @@ export class ValidationService { const allPropertiesOnData = new Set(Object.keys(value)); Object.entries(properties).forEach(([key, property]) => { - const validatedParam = this.ValidateParam(property, value[key], key, fieldErrors, isBodyParam, fieldPath + '.', swaggerConfig); + const validatedParam = this.ValidateParam(property, value[key], key, fieldErrors, isBodyParam, fieldPath + '.'); // Add value from validator if it's not undefined or if value is required and unfedined is valid type if (validatedParam !== undefined || (property.dataType === 'undefined' && property.required)) { @@ -787,24 +755,24 @@ export class ValidationService { } else if (additionalProperties === false) { Object.keys(value).forEach((key: string) => { if (isAnExcessProperty(key)) { - if (swaggerConfig.noImplicitAdditionalProperties === 'throw-on-extras') { + if (this.config.noImplicitAdditionalProperties === 'throw-on-extras') { fieldErrors[`${fieldPath}.${key}`] = { message: `"${key}" is an excess property and therefore is not allowed`, value: key, }; - } else if (swaggerConfig.noImplicitAdditionalProperties === 'silently-remove-extras') { + } else if (this.config.noImplicitAdditionalProperties === 'silently-remove-extras') { delete value[key]; - } else if (swaggerConfig.noImplicitAdditionalProperties === 'ignore') { + } else if (this.config.noImplicitAdditionalProperties === 'ignore') { // then it's okay to have additionalProperties } else { - assertNever(swaggerConfig.noImplicitAdditionalProperties); + assertNever(this.config.noImplicitAdditionalProperties); } } }); } else { Object.keys(value).forEach((key: string) => { if (isAnExcessProperty(key)) { - const validatedValue = this.ValidateParam(additionalProperties, value[key], key, fieldErrors, isBodyParam, fieldPath + '.', swaggerConfig); + const validatedValue = this.ValidateParam(additionalProperties, value[key], key, fieldErrors, isBodyParam, fieldPath + '.'); // Add value from validator if it's not undefined or if value is required and unfedined is valid type if (validatedValue !== undefined || (additionalProperties.dataType === 'undefined' && additionalProperties.required)) { value[key] = validatedValue; diff --git a/packages/runtime/src/routeGeneration/templates/express/expressTemplateService.ts b/packages/runtime/src/routeGeneration/templates/express/expressTemplateService.ts index a1c1ecec4..e8f7d2955 100644 --- a/packages/runtime/src/routeGeneration/templates/express/expressTemplateService.ts +++ b/packages/runtime/src/routeGeneration/templates/express/expressTemplateService.ts @@ -29,13 +29,6 @@ type ExpressReturnHandlerParameters = { }; export class ExpressTemplateService extends TemplateService { - constructor( - readonly models: any, - private readonly minimalSwaggerConfig: any, - ) { - super(models); - } - async apiHandler(params: ExpressApiHandlerParameters) { const { methodName, controller, response, validatedArgs, successStatus, next } = params; const promise = this.buildPromise(methodName, controller, validatedArgs); @@ -67,30 +60,30 @@ export class ExpressTemplateService extends TemplateService param.dataType === 'file'); if (param.dataType === 'file' && files.length > 0) { const requestFiles = request.files as { [fileName: string]: Express.Multer.File[] }; - const fileArgs = this.validationService.ValidateParam(param, requestFiles[name], name, fieldErrors, false, undefined, this.minimalSwaggerConfig); + const fileArgs = this.validationService.ValidateParam(param, requestFiles[name], name, fieldErrors, false, undefined); return fileArgs.length === 1 ? fileArgs[0] : fileArgs; } else if (param.dataType === 'array' && param.array && param.array.dataType === 'file') { - return this.validationService.ValidateParam(param, request.files, name, fieldErrors, false, undefined, this.minimalSwaggerConfig); + return this.validationService.ValidateParam(param, request.files, name, fieldErrors, false, undefined); } - return this.validationService.ValidateParam(param, request.body[name], name, fieldErrors, false, undefined, this.minimalSwaggerConfig); + return this.validationService.ValidateParam(param, request.body[name], name, fieldErrors, false, undefined); } case 'res': return (status: number | undefined, data: any, headers: any) => { diff --git a/packages/runtime/src/routeGeneration/templates/hapi/hapiTemplateService.ts b/packages/runtime/src/routeGeneration/templates/hapi/hapiTemplateService.ts index 95b0d200f..6bd6f7dd8 100644 --- a/packages/runtime/src/routeGeneration/templates/hapi/hapiTemplateService.ts +++ b/packages/runtime/src/routeGeneration/templates/hapi/hapiTemplateService.ts @@ -6,6 +6,7 @@ import { FieldErrors } from '../../templateHelpers'; import { TsoaRoute } from '../../tsoa-route'; import { ValidateError } from '../../templateHelpers'; import { TemplateService } from '../templateService'; +import { AdditionalProps } from '../../additionalProps'; const hapiTsoaResponsed = Symbol('@tsoa:template_service:hapi:responsed'); @@ -32,14 +33,14 @@ type HapiReturnHandlerParameters = { export class HapiTemplateService extends TemplateService { constructor( - readonly models: any, - private readonly minimalSwaggerConfig: any, + protected readonly models: TsoaRoute.Models, + protected readonly config: AdditionalProps, private readonly hapi: { boomify: Function; isBoom: Function; }, ) { - super(models); + super(models, config); } async apiHandler(params: HapiApiHandlerParameters) { @@ -83,27 +84,27 @@ export class HapiTemplateService extends TemplateService { diff --git a/packages/runtime/src/routeGeneration/templates/koa/koaTemplateService.ts b/packages/runtime/src/routeGeneration/templates/koa/koaTemplateService.ts index 2093d26ac..2e708ae65 100644 --- a/packages/runtime/src/routeGeneration/templates/koa/koaTemplateService.ts +++ b/packages/runtime/src/routeGeneration/templates/koa/koaTemplateService.ts @@ -31,13 +31,6 @@ type KoaReturnHandlerParameters = { }; export class KoaTemplateService extends TemplateService { - constructor( - readonly models: any, - private readonly minimalSwaggerConfig: any, - ) { - super(models); - } - async apiHandler(params: KoaApiHandlerParameters) { const { methodName, controller, context, validatedArgs, successStatus } = params; const promise = this.buildPromise(methodName, controller, validatedArgs); @@ -70,36 +63,36 @@ export class KoaTemplateService extends TemplateService param.dataType === 'file'); const contextRequest = context.request as any; if (param.dataType === 'file' && files.length > 0) { - const fileArgs = this.validationService.ValidateParam(param, contextRequest.files[name], name, errorFields, false, undefined, this.minimalSwaggerConfig); + const fileArgs = this.validationService.ValidateParam(param, contextRequest.files[name], name, errorFields, false, undefined); return fileArgs.length === 1 ? fileArgs[0] : fileArgs; } else if (param.dataType === 'array' && param.array && param.array.dataType === 'file') { - return this.validationService.ValidateParam(param, contextRequest.files, name, errorFields, false, undefined, this.minimalSwaggerConfig); + return this.validationService.ValidateParam(param, contextRequest.files, name, errorFields, false, undefined); } - return this.validationService.ValidateParam(param, contextRequest.body[name], name, errorFields, false, undefined, this.minimalSwaggerConfig); + return this.validationService.ValidateParam(param, contextRequest.body[name], name, errorFields, false, undefined); } case 'res': return async (status: number | undefined, data: any, headers: any): Promise => { diff --git a/packages/runtime/src/routeGeneration/templates/templateService.ts b/packages/runtime/src/routeGeneration/templates/templateService.ts index b4a7eb003..8e2eeb9d7 100644 --- a/packages/runtime/src/routeGeneration/templates/templateService.ts +++ b/packages/runtime/src/routeGeneration/templates/templateService.ts @@ -1,14 +1,16 @@ import { Controller } from '../../interfaces/controller'; import { TsoaRoute } from '../tsoa-route'; import { ValidationService } from '../templateHelpers'; +import { AdditionalProps } from '../additionalProps'; export abstract class TemplateService { protected validationService: ValidationService; constructor( protected readonly models: TsoaRoute.Models, + protected readonly config: AdditionalProps, ) { - this.validationService = new ValidationService(models); + this.validationService = new ValidationService(models, config); } abstract apiHandler(params: ApiHandlerParameters): Promise; diff --git a/tests/fixtures/custom/custom-route-generator/templates/models.hbs b/tests/fixtures/custom/custom-route-generator/templates/models.hbs index a6450b638..4f34f0e07 100644 --- a/tests/fixtures/custom/custom-route-generator/templates/models.hbs +++ b/tests/fixtures/custom/custom-route-generator/templates/models.hbs @@ -25,7 +25,7 @@ const models: TsoaRoute.Models = { {{/each}} }; -const validationService = new ValidationService(models); +const validationService = new ValidationService(models, {{{json minimalSwaggerConfig}}}); export function getValidatedArgs(args: any, event: any): any[] { const fieldErrors: FieldErrors = {}; @@ -36,17 +36,17 @@ export function getValidatedArgs(args: any, event: any): any[] { case 'request': return event; case 'request-prop': - return validationService.ValidateParam(args[key], event[name], name, fieldErrors, false, undefined, {{{json minimalSwaggerConfig}}}); + return validationService.ValidateParam(args[key], event[name], name, fieldErrors, false, undefined); case 'query': - return validationService.ValidateParam(args[key], event.queryStringParameters[name], name, fieldErrors, false, undefined, {{{json minimalSwaggerConfig}}}); + return validationService.ValidateParam(args[key], event.queryStringParameters[name], name, fieldErrors, false, undefined); case 'path': - return validationService.ValidateParam(args[key], event.pathParameters[name], name, fieldErrors, false, undefined, {{{json minimalSwaggerConfig}}}); + return validationService.ValidateParam(args[key], event.pathParameters[name], name, fieldErrors, false, undefined); case 'header': - return validationService.ValidateParam(args[key], event.headers[name], name, fieldErrors, false, undefined, {{{json minimalSwaggerConfig}}}); + return validationService.ValidateParam(args[key], event.headers[name], name, fieldErrors, false, undefined); case 'body': - return validationService.ValidateParam(args[key], eventBody, name, fieldErrors, true, undefined, {{{json minimalSwaggerConfig}}}); + return validationService.ValidateParam(args[key], eventBody, name, fieldErrors, true, undefined); case 'body-prop': - return validationService.ValidateParam(args[key], eventBody[name], name, fieldErrors, true, 'body.', {{{json minimalSwaggerConfig}}}); + return validationService.ValidateParam(args[key], eventBody[name], name, fieldErrors, true, 'body.'); case 'formData': throw new Error('Multi-part form data not supported yet'); case 'res': diff --git a/tests/unit/swagger/templateHelpers.spec.ts b/tests/unit/swagger/templateHelpers.spec.ts index 5a64bb523..639770515 100644 --- a/tests/unit/swagger/templateHelpers.spec.ts +++ b/tests/unit/swagger/templateHelpers.spec.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; import 'mocha'; -import { TsoaRoute, FieldErrors, ValidationService, AdditionalProps } from '@tsoa/runtime'; +import { TsoaRoute, FieldErrors, ValidationService } from '@tsoa/runtime'; import { TypeAliasDate, TypeAliasDateTime, TypeAliasModel1, TypeAliasModel2 } from 'fixtures/testModel'; describe('ValidationService', () => { @@ -12,16 +12,17 @@ describe('ValidationService', () => { a: { dataType: 'string', required: true }, }, }; - const v = new ValidationService({}); - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; + const v = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ); const error: FieldErrors = {}; const result = v.validateModel({ fieldErrors: error, isBodyParam: true, - minimalSwaggerConfig, name: '', modelDefinition, value: { a: 's' }, @@ -39,11 +40,13 @@ describe('ValidationService', () => { a: { dataType: 'string', required: true }, }, }; - const v = new ValidationService({}); - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'throw-on-extras', - bodyCoercion: true, - }; + const v = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'throw-on-extras', + bodyCoercion: true, + }, + ); const errorDictionary: FieldErrors = {}; const nameOfAdditionalProperty = 'I am the bad key name'; const dataToValidate = { @@ -55,7 +58,6 @@ describe('ValidationService', () => { v.validateModel({ fieldErrors: errorDictionary, isBodyParam: true, - minimalSwaggerConfig, name: '', modelDefinition, value: dataToValidate, @@ -82,11 +84,13 @@ describe('ValidationService', () => { a: { dataType: 'string', required: true }, }, }; - const v = new ValidationService({}); - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'silently-remove-extras', - bodyCoercion: true, - }; + const v = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'silently-remove-extras', + bodyCoercion: true, + }, + ); const errorDictionary: FieldErrors = {}; const nameOfAdditionalProperty = 'I am the bad key name'; const dataToValidate = { @@ -98,7 +102,6 @@ describe('ValidationService', () => { v.validateModel({ fieldErrors: errorDictionary, isBodyParam: true, - minimalSwaggerConfig, name: '', modelDefinition, value: dataToValidate, @@ -122,11 +125,13 @@ describe('ValidationService', () => { a: { dataType: 'string', required: true }, }, }; - const v = new ValidationService({}); - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; + const v = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ); const errorDictionary: FieldErrors = {}; const nameOfAdditionalProperty = 'I am the bad key name'; const dataToValidate = { @@ -138,7 +143,6 @@ describe('ValidationService', () => { const result = v.validateModel({ fieldErrors: errorDictionary, isBodyParam: true, - minimalSwaggerConfig, name: '', modelDefinition, value: dataToValidate, @@ -157,13 +161,15 @@ describe('ValidationService', () => { a: { dataType: 'string' }, }, }; - const v = new ValidationService({}); + const v = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ); const error: FieldErrors = {}; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; - const result = v.validateModel({ name: '', value: {}, modelDefinition, fieldErrors: error, isBodyParam: true, minimalSwaggerConfig }); + const result = v.validateModel({ name: '', value: {}, modelDefinition, fieldErrors: error, isBodyParam: true }); expect(Object.keys(error)).to.be.empty; expect(result).to.eql({}); }); @@ -174,13 +180,15 @@ describe('ValidationService', () => { properties: {}, additionalProperties: { dataType: 'any' }, }; - const v = new ValidationService({}); + const v = new ValidationService( + {}, + { + // we're setting this to the "throw" to demonstrate that explicit additionalProperties should always be allowed + noImplicitAdditionalProperties: 'throw-on-extras', + bodyCoercion: true, + }, + ); const error: FieldErrors = {}; - const minimalSwaggerConfig: AdditionalProps = { - // we're setting this to the "throw" to demonstrate that explicit additionalProperties should always be allowed - noImplicitAdditionalProperties: 'throw-on-extras', - bodyCoercion: true, - }; const result = v.validateModel({ name: '', value: { a: 's' }, @@ -188,7 +196,6 @@ describe('ValidationService', () => { fieldErrors: error, isBodyParam: true, - minimalSwaggerConfig, }); expect(Object.keys(error)).to.be.empty; expect(result).to.eql({ a: 's' }); @@ -202,15 +209,17 @@ describe('ValidationService', () => { a: { dataType: 'string' }, }, }; - const v = new ValidationService({}); + const v = new ValidationService( + {}, + { + // This test should ignore this, otherwise there's a problem the code + // when the model has additionalProperties, that should take precedence since it's explicit + noImplicitAdditionalProperties: 'throw-on-extras', + bodyCoercion: true, + }, + ); const error: FieldErrors = {}; - const minimalSwaggerConfig: AdditionalProps = { - // This test should ignore this, otherwise there's a problem the code - // when the model has additionalProperties, that should take precedence since it's explicit - noImplicitAdditionalProperties: 'throw-on-extras', - bodyCoercion: true, - }; - const result = v.validateModel({ name: '', value: {}, modelDefinition, fieldErrors: error, isBodyParam: true, minimalSwaggerConfig }); + const result = v.validateModel({ name: '', value: {}, modelDefinition, fieldErrors: error, isBodyParam: true }); expect(Object.keys(error)).to.be.empty; expect(result).to.eql({}); }); @@ -226,35 +235,40 @@ describe('ValidationService', () => { a: { dataType: 'integer' }, }, }; - const v = new ValidationService({}); + const v = new ValidationService( + {}, + { + // This test should ignore this, otherwise there's a problem the code + // when the model has additionalProperties, that should take precedence since it's explicit + noImplicitAdditionalProperties: 'throw-on-extras', + bodyCoercion: true, + }, + ); const error: FieldErrors = {}; - const minimalSwaggerConfig: AdditionalProps = { - // This test should ignore this, otherwise there's a problem the code - // when the model has additionalProperties, that should take precedence since it's explicit - noImplicitAdditionalProperties: 'throw-on-extras', - bodyCoercion: true, - }; - const result = v.validateModel({ name: '', value: { a: 9 }, modelDefinition, fieldErrors: error, isBodyParam: true, minimalSwaggerConfig }); + const result = v.validateModel({ name: '', value: { a: 9 }, modelDefinition, fieldErrors: error, isBodyParam: true }); expect(Object.keys(error)).to.be.empty; expect(result).to.eql({ a: 9 }); }); it('non provided parameters should not result in undefined', () => { - const v = new ValidationService({ - BEnum: { - dataType: 'refEnum', - enums: ['X', 'Y'], - }, - General: { - dataType: 'refObject', - properties: { - a: { dataType: 'string' }, - b: { ref: 'BEnum' }, - c: { dataType: 'string' }, + const v = new ValidationService( + { + BEnum: { + dataType: 'refEnum', + enums: ['X', 'Y'], + }, + General: { + dataType: 'refObject', + properties: { + a: { dataType: 'string' }, + b: { ref: 'BEnum' }, + c: { dataType: 'string' }, + }, + additionalProperties: false, }, - additionalProperties: false, }, - }); + { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, + ); const error: FieldErrors = {}; @@ -268,7 +282,6 @@ describe('ValidationService', () => { error, true, undefined, - { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, ); expect(result).to.deep.equal({ a: 'value', b: undefined }); @@ -279,15 +292,18 @@ describe('ValidationService', () => { }); it('required provided parameters should result in required errors', () => { - const v = new ValidationService({ - General: { - dataType: 'refObject', - properties: { - a: { dataType: 'string', required: true }, + const v = new ValidationService( + { + General: { + dataType: 'refObject', + properties: { + a: { dataType: 'string', required: true }, + }, + additionalProperties: false, }, - additionalProperties: false, }, - }); + { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, + ); const error: FieldErrors = {}; @@ -300,7 +316,6 @@ describe('ValidationService', () => { error, true, undefined, - { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, ); expect(error['body.a'].message).to.equal(`'a' is required`); @@ -311,33 +326,39 @@ describe('ValidationService', () => { it('should apply defaults for optional properties', () => { const value = undefined; const propertySchema: TsoaRoute.PropertySchema = { dataType: 'integer', default: '666', required: false, validators: {} }; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; - const result = new ValidationService({}).ValidateParam(propertySchema, value, 'defaultProp', {}, true, undefined, minimalSwaggerConfig); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).ValidateParam(propertySchema, value, 'defaultProp', {}, true, undefined); expect(result).to.equal(666); }); it('should not override values with defaults', () => { const value = 123; const propertySchema: TsoaRoute.PropertySchema = { dataType: 'integer', default: '666', required: false, validators: {} }; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; - const result = new ValidationService({}).ValidateParam(propertySchema, value, 'defaultProp', {}, true, undefined, minimalSwaggerConfig); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).ValidateParam(propertySchema, value, 'defaultProp', {}, true, undefined); expect(result).to.equal(123); }); it('should apply defaults for required properties', () => { const value = undefined; const propertySchema: TsoaRoute.PropertySchema = { dataType: 'integer', default: '666', required: true, validators: {} }; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; - const result = new ValidationService({}).ValidateParam(propertySchema, value, 'defaultProp', {}, true, undefined, minimalSwaggerConfig); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).ValidateParam(propertySchema, value, 'defaultProp', {}, true, undefined); expect(result).to.equal(666); }); }); @@ -345,11 +366,13 @@ describe('ValidationService', () => { describe('Integer validate', () => { it('should integer value', () => { const value = '10'; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; - const result = new ValidationService({}).validateInt('name', value, {}, true, minimalSwaggerConfig); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateInt('name', value, {}, true); expect(result).to.equal(Number(value)); }); @@ -357,11 +380,13 @@ describe('ValidationService', () => { const name = 'name'; const value = '10.0'; const error: FieldErrors = {}; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; - const result = new ValidationService({}).validateInt(name, value, error, true, minimalSwaggerConfig); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateInt(name, value, error, true); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`invalid integer number`); }); @@ -370,12 +395,14 @@ describe('ValidationService', () => { const name = 'name'; const value = '11'; const error: FieldErrors = {}; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; const validator = { minimum: { value: 10 }, maximum: { value: 12 } }; - const result = new ValidationService({}).validateInt(name, value, error, true, minimalSwaggerConfig, validator); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateInt(name, value, error, true, validator); expect(result).to.equal(Number(value)); }); @@ -384,11 +411,13 @@ describe('ValidationService', () => { const value = '11'; const error: FieldErrors = {}; const validator = { minimum: { value: 12 } }; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; - const result = new ValidationService({}).validateInt(name, value, error, true, minimalSwaggerConfig, validator); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateInt(name, value, error, true, validator); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`min 12`); }); @@ -398,11 +427,13 @@ describe('ValidationService', () => { const value = '11'; const error: FieldErrors = {}; const validator = { maximum: { value: 10 } }; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; - const result = new ValidationService({}).validateInt(name, value, error, true, minimalSwaggerConfig, validator); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateInt(name, value, error, true, validator); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`max 10`); }); @@ -411,7 +442,7 @@ describe('ValidationService', () => { const name = 'name'; const value: any = '10'; const error: FieldErrors = {}; - const result = new ValidationService({}).validateInt(name, value, error, true, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }); + const result = new ValidationService({}, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }).validateInt(name, value, error, true); expect(result).to.deep.equal(undefined); expect(error[name].message).to.equal('invalid integer number'); expect(error[name].value).to.equal('10'); @@ -421,11 +452,13 @@ describe('ValidationService', () => { describe('Float validate', () => { it('should float value', () => { const value = '10'; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; - const result = new ValidationService({}).validateFloat('name', value, {}, true, minimalSwaggerConfig); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateFloat('name', value, {}, true); expect(result).to.equal(Number(value)); }); @@ -433,11 +466,13 @@ describe('ValidationService', () => { const name = 'name'; const value = 'Hello'; const error: FieldErrors = {}; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; - const result = new ValidationService({}).validateFloat(name, value, error, true, minimalSwaggerConfig); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateFloat(name, value, error, true); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`invalid float number`); }); @@ -447,11 +482,13 @@ describe('ValidationService', () => { const value = '11.5'; const error: FieldErrors = {}; const validator = { minimum: { value: 10 }, maximum: { value: 12 } }; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; - const result = new ValidationService({}).validateFloat(name, value, error, true, minimalSwaggerConfig, validator); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateFloat(name, value, error, true, validator); expect(result).to.equal(Number(value)); }); @@ -460,11 +497,13 @@ describe('ValidationService', () => { const value = '12.4'; const error: FieldErrors = {}; const validator = { minimum: { value: 12.5 } }; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; - const result = new ValidationService({}).validateFloat(name, value, error, true, minimalSwaggerConfig, validator); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateFloat(name, value, error, true, validator); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`min 12.5`); }); @@ -474,11 +513,13 @@ describe('ValidationService', () => { const value = '10.6'; const error: FieldErrors = {}; const validator = { maximum: { value: 10.5 } }; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; - const result = new ValidationService({}).validateFloat(name, value, error, true, minimalSwaggerConfig, validator); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateFloat(name, value, error, true, validator); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`max 10.5`); }); @@ -487,7 +528,7 @@ describe('ValidationService', () => { const name = 'name'; const value: any = '10.1'; const error: FieldErrors = {}; - const result = new ValidationService({}).validateFloat(name, value, error, true, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }); + const result = new ValidationService({}, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }).validateFloat(name, value, error, true); expect(result).to.deep.equal(undefined); expect(error[name].message).to.equal('invalid float number'); expect(error[name].value).to.equal('10.1'); @@ -497,31 +538,37 @@ describe('ValidationService', () => { describe('Boolean validate', () => { it('should return true when submitted true', () => { const value = true; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; - const result = new ValidationService({}).validateBool('name', value, {}, true, minimalSwaggerConfig); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateBool('name', value, {}, true); expect(result).to.equal(true); }); it('should return false when submitted false', () => { const value = false; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; - const result = new ValidationService({}).validateBool('name', value, {}, true, minimalSwaggerConfig); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateBool('name', value, {}, true); expect(result).to.equal(false); }); it('should coerce strings to boolean values if body coercion is enabled', () => { const value = 'false'; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; - const result = new ValidationService({}).validateBool('name', value, {}, true, minimalSwaggerConfig); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateBool('name', value, {}, true); expect(result).to.equal(false); }); @@ -529,7 +576,7 @@ describe('ValidationService', () => { const name = 'name'; const value = 'false'; const error: FieldErrors = {}; - const result = new ValidationService({}).validateBool(name, value, error, true, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }); + const result = new ValidationService({}, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }).validateBool(name, value, error, true); expect(result).to.deep.equal(undefined); expect(error[name].message).to.equal('invalid boolean value'); expect(error[name].value).to.equal('false'); @@ -544,13 +591,25 @@ describe('ValidationService', () => { const value = 1; const error: FieldErrors = {}; const enumeration: Enumeration = [0, 1]; - const result = new ValidationService({}).validateEnum(name, value, error, enumeration); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateEnum(name, value, error, enumeration); expect(result).to.equal(value); }); it('should enum empty string value', () => { const value = ''; - const result = new ValidationService({}).validateEnum('name', value, {}, ['']); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateEnum('name', value, {}, ['']); expect(result).to.equal(value); }); @@ -558,7 +617,13 @@ describe('ValidationService', () => { const value = null; const error: FieldErrors = {}; const name = 'name'; - const result = new ValidationService({}).validateEnum(name, value, error, ['']); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateEnum(name, value, error, ['']); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`should be one of the following; ['']`); }); @@ -568,7 +633,13 @@ describe('ValidationService', () => { const value = 'HELLO'; const error: FieldErrors = {}; const enumeration: Enumeration = ['HELLO']; - const result = new ValidationService({}).validateEnum(name, value, error, enumeration); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateEnum(name, value, error, enumeration); expect(result).to.equal(value); }); @@ -577,7 +648,13 @@ describe('ValidationService', () => { const value = 'HI'; const error: FieldErrors = {}; const enumeration: Enumeration = []; - const result = new ValidationService({}).validateEnum(name, value, error, enumeration); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateEnum(name, value, error, enumeration); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`no member`); }); @@ -587,7 +664,13 @@ describe('ValidationService', () => { const value = 'SAY'; const error: FieldErrors = {}; const enumeration: Enumeration = ['HELLO', 'HI']; - const result = new ValidationService({}).validateEnum(name, value, error, enumeration); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateEnum(name, value, error, enumeration); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`should be one of the following; ['HELLO','HI']`); }); @@ -597,7 +680,13 @@ describe('ValidationService', () => { const value = '1'; const error: FieldErrors = {}; const enumeration: Enumeration = [0, 1]; - const result = new ValidationService({}).validateEnum(name, value, error, enumeration); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateEnum(name, value, error, enumeration); expect(result).to.equal(1); expect(error).to.deep.equal({}); }); @@ -607,7 +696,13 @@ describe('ValidationService', () => { const value = '2'; const error: FieldErrors = {}; const enumeration: Enumeration = [0, 1]; - const result = new ValidationService({}).validateEnum(name, value, error, enumeration); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateEnum(name, value, error, enumeration); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`should be one of the following; [0,1]`); }); @@ -617,7 +712,13 @@ describe('ValidationService', () => { const value = 1; const error: FieldErrors = {}; const enumeration: Enumeration = ['0', '1']; - const result = new ValidationService({}).validateEnum(name, value, error, enumeration); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateEnum(name, value, error, enumeration); expect(result).to.equal('1'); expect(error).to.deep.equal({}); }); @@ -627,7 +728,13 @@ describe('ValidationService', () => { const value = 2; const error: FieldErrors = {}; const enumeration: Enumeration = ['0', '1']; - const result = new ValidationService({}).validateEnum(name, value, error, enumeration); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateEnum(name, value, error, enumeration); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`should be one of the following; ['0','1']`); }); @@ -637,7 +744,13 @@ describe('ValidationService', () => { const value = 'foo'; const error: FieldErrors = {}; const enumeration: Enumeration = [1, 2]; - const result = new ValidationService({}).validateEnum(name, value, error, enumeration); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateEnum(name, value, error, enumeration); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`should be one of the following; [1,2]`); }); @@ -647,7 +760,13 @@ describe('ValidationService', () => { const value = false; const error: FieldErrors = {}; const enumeration = [false]; - const result = new ValidationService({}).validateEnum(name, value, error, enumeration); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateEnum(name, value, error, enumeration); expect(result).to.equal(false); expect(error).to.deep.equal({}); }); @@ -657,7 +776,13 @@ describe('ValidationService', () => { const value = 'true'; const error: FieldErrors = {}; const enumeration = [true]; - const result = new ValidationService({}).validateEnum(name, value, error, enumeration); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateEnum(name, value, error, enumeration); expect(result).to.equal(true); expect(error).to.deep.equal({}); }); @@ -667,7 +792,13 @@ describe('ValidationService', () => { const value = false; const error: FieldErrors = {}; const enumeration = [true]; - const result = new ValidationService({}).validateEnum(name, value, error, enumeration); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateEnum(name, value, error, enumeration); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`should be one of the following; [true]`); }); @@ -677,7 +808,13 @@ describe('ValidationService', () => { const value = 'false'; const error: FieldErrors = {}; const enumeration = [true]; - const result = new ValidationService({}).validateEnum(name, value, error, enumeration); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateEnum(name, value, error, enumeration); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`should be one of the following; [true]`); }); @@ -687,7 +824,13 @@ describe('ValidationService', () => { const value = null; const error: FieldErrors = {}; const enumeration = [null]; - const result = new ValidationService({}).validateEnum(name, value, error, enumeration); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateEnum(name, value, error, enumeration); expect(result).to.equal(null); expect(error).to.deep.equal({}); }); @@ -697,7 +840,13 @@ describe('ValidationService', () => { const value = 'null'; const error: FieldErrors = {}; const enumeration = [null]; - const result = new ValidationService({}).validateEnum(name, value, error, enumeration); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateEnum(name, value, error, enumeration); expect(result).to.equal(null); expect(error).to.deep.equal({}); }); @@ -707,7 +856,13 @@ describe('ValidationService', () => { const value = 'null'; const error: FieldErrors = {}; const enumeration = [0]; - const result = new ValidationService({}).validateEnum(name, value, error, enumeration); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateEnum(name, value, error, enumeration); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`should be one of the following; [0]`); }); @@ -717,7 +872,13 @@ describe('ValidationService', () => { const value = 0; const error: FieldErrors = {}; const enumeration = [null]; - const result = new ValidationService({}).validateEnum(name, value, error, enumeration); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateEnum(name, value, error, enumeration); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`should be one of the following; [null]`); }); @@ -727,7 +888,13 @@ describe('ValidationService', () => { const value = null; const error: FieldErrors = {}; const enumeration = [false]; - const result = new ValidationService({}).validateEnum(name, value, error, enumeration); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateEnum(name, value, error, enumeration); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`should be one of the following; [false]`); }); @@ -737,7 +904,13 @@ describe('ValidationService', () => { const value = false; const error: FieldErrors = {}; const enumeration = [null]; - const result = new ValidationService({}).validateEnum(name, value, error, enumeration); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateEnum(name, value, error, enumeration); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`should be one of the following; [null]`); }); @@ -747,7 +920,13 @@ describe('ValidationService', () => { const value = 0; const error: FieldErrors = {}; const enumeration = [false]; - const result = new ValidationService({}).validateEnum(name, value, error, enumeration); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateEnum(name, value, error, enumeration); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`should be one of the following; [false]`); }); @@ -757,7 +936,13 @@ describe('ValidationService', () => { const value = false; const error: FieldErrors = {}; const enumeration = [0]; - const result = new ValidationService({}).validateEnum(name, value, error, enumeration); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateEnum(name, value, error, enumeration); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`should be one of the following; [0]`); }); @@ -767,7 +952,13 @@ describe('ValidationService', () => { const value = null; const error: FieldErrors = {}; const enumeration = ['']; - const result = new ValidationService({}).validateEnum(name, value, error, enumeration); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateEnum(name, value, error, enumeration); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`should be one of the following; ['']`); }); @@ -777,7 +968,13 @@ describe('ValidationService', () => { const value = ''; const error: FieldErrors = {}; const enumeration = [null]; - const result = new ValidationService({}).validateEnum(name, value, error, enumeration); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateEnum(name, value, error, enumeration); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`should be one of the following; [null]`); }); @@ -787,7 +984,13 @@ describe('ValidationService', () => { const value = 1; const error: FieldErrors = {}; const enumeration = [true]; - const result = new ValidationService({}).validateEnum(name, value, error, enumeration); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateEnum(name, value, error, enumeration); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`should be one of the following; [true]`); }); @@ -797,7 +1000,13 @@ describe('ValidationService', () => { const value = true; const error: FieldErrors = {}; const enumeration = [1]; - const result = new ValidationService({}).validateEnum(name, value, error, enumeration); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateEnum(name, value, error, enumeration); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`should be one of the following; [1]`); }); @@ -807,7 +1016,13 @@ describe('ValidationService', () => { const value = true; const error: FieldErrors = {}; const enumeration = ['1']; - const result = new ValidationService({}).validateEnum(name, value, error, enumeration); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateEnum(name, value, error, enumeration); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`should be one of the following; ['1']`); }); @@ -816,7 +1031,13 @@ describe('ValidationService', () => { describe('String validate', () => { it('should string value', () => { const value = 'Hello'; - const result = new ValidationService({}).validateString('name', value, {}); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateString('name', value, {}); expect(result).to.equal(value); }); @@ -824,7 +1045,13 @@ describe('ValidationService', () => { const name = 'name'; const value = 'AB'; const error: FieldErrors = {}; - const result = new ValidationService({}).validateString(name, value, error, { minLength: { value: 5 } }); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateString(name, value, error, { minLength: { value: 5 } }); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`minLength 5`); }); @@ -833,7 +1060,13 @@ describe('ValidationService', () => { const name = 'name'; const value = 'ABCDE'; const error: FieldErrors = {}; - const result = new ValidationService({}).validateString(name, value, error, { maxLength: { value: 3 } }); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateString(name, value, error, { maxLength: { value: 3 } }); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`maxLength 3`); }); @@ -842,7 +1075,13 @@ describe('ValidationService', () => { const name = 'name'; const value = 'ABC'; const error: FieldErrors = {}; - const result = new ValidationService({}).validateString(name, value, error, { pattern: { value: 'a-z' } }); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateString(name, value, error, { pattern: { value: 'a-z' } }); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`Not match in 'a-z'`); }); @@ -851,11 +1090,13 @@ describe('ValidationService', () => { describe('Date validate', () => { it('should date value', () => { const value = '2017-01-01'; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; - const result = new ValidationService({}).validateDate('name', value, {}, true, minimalSwaggerConfig); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateDate('name', value, {}, true); expect(result).to.deep.equal(new Date(value)); }); @@ -863,11 +1104,13 @@ describe('ValidationService', () => { const name = 'name'; const value = '2017-33-11'; const error: FieldErrors = {}; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; - const result = new ValidationService({}).validateDate(name, value, error, true, minimalSwaggerConfig); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateDate(name, value, error, true); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`invalid ISO 8601 date format, i.e. YYYY-MM-DD`); }); @@ -876,11 +1119,13 @@ describe('ValidationService', () => { const name = 'name'; const value = '2017-06-01'; const error: FieldErrors = {}; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; - const result = new ValidationService({}).validateDate(name, value, error, true, minimalSwaggerConfig, { minDate: { value: '2017-07-01' } }); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateDate(name, value, error, true, { minDate: { value: '2017-07-01' } }); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`minDate '2017-07-01'`); }); @@ -889,11 +1134,13 @@ describe('ValidationService', () => { const name = 'name'; const value = '2017-06-01'; const error: FieldErrors = {}; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; - const result = new ValidationService({}).validateDate(name, value, error, true, minimalSwaggerConfig, { maxDate: { value: '2017-05-01' } }); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateDate(name, value, error, true, { maxDate: { value: '2017-05-01' } }); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`maxDate '2017-05-01'`); }); @@ -902,7 +1149,7 @@ describe('ValidationService', () => { const name = 'name'; const value: any = 1234; const error: FieldErrors = {}; - const result = new ValidationService({}).validateDate(name, value, error, true, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }); + const result = new ValidationService({}, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }).validateDate(name, value, error, true); expect(result).to.deep.equal(undefined); expect(error[name].message).to.equal('invalid ISO 8601 date format, i.e. YYYY-MM-DD'); expect(error[name].value).to.equal(1234); @@ -912,11 +1159,13 @@ describe('ValidationService', () => { describe('DateTime validate', () => { it('should datetime value', () => { const value = '2017-12-30T00:00:00'; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; - const result = new ValidationService({}).validateDateTime('name', value, {}, true, minimalSwaggerConfig); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateDateTime('name', value, {}, true); expect(result).to.deep.equal(new Date(value)); }); @@ -924,11 +1173,13 @@ describe('ValidationService', () => { const name = 'name'; const value = '2017-12-309i'; const error: FieldErrors = {}; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; - const result = new ValidationService({}).validateDateTime(name, value, error, true, minimalSwaggerConfig); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateDateTime(name, value, error, true); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`invalid ISO 8601 datetime format, i.e. YYYY-MM-DDTHH:mm:ss`); }); @@ -937,11 +1188,13 @@ describe('ValidationService', () => { const name = 'name'; const value = '2017-12-30T00:00:00'; const error: FieldErrors = {}; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; - const result = new ValidationService({}).validateDateTime(name, value, error, true, minimalSwaggerConfig, { minDate: { value: '2017-12-31T00:00:00' } }); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateDateTime(name, value, error, true, { minDate: { value: '2017-12-31T00:00:00' } }); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`minDate '2017-12-31T00:00:00'`); }); @@ -950,11 +1203,13 @@ describe('ValidationService', () => { const name = 'name'; const value = '2017-12-30T00:00:00'; const error: FieldErrors = {}; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; - const result = new ValidationService({}).validateDateTime(name, value, error, true, minimalSwaggerConfig, { maxDate: { value: '2017-12-29T00:00:00' } }); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).validateDateTime(name, value, error, true, { maxDate: { value: '2017-12-29T00:00:00' } }); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`maxDate '2017-12-29T00:00:00'`); }); @@ -963,7 +1218,7 @@ describe('ValidationService', () => { const name = 'name'; const value: any = 1234; const error: FieldErrors = {}; - const result = new ValidationService({}).validateDateTime(name, value, error, true, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }); + const result = new ValidationService({}, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }).validateDateTime(name, value, error, true); expect(result).to.deep.equal(undefined); expect(error[name].message).to.equal('invalid ISO 8601 datetime format, i.e. YYYY-MM-DDTHH:mm:ss'); expect(error[name].value).to.equal(1234); @@ -973,7 +1228,7 @@ describe('ValidationService', () => { describe('Array validate', () => { it('should array value', () => { const value = ['A', 'B', 'C']; - const result = new ValidationService({}).validateArray('name', value, {}, true, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, { dataType: 'string' }); + const result = new ValidationService({}, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }).validateArray('name', value, {}, true, { dataType: 'string' }); expect(result).to.deep.equal(value); }); @@ -981,7 +1236,7 @@ describe('ValidationService', () => { const name = 'name'; const value = ['A', 10, true]; const error: FieldErrors = {}; - const result = new ValidationService({}).validateArray(name, value, error, true, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, { dataType: 'integer' }); + const result = new ValidationService({}, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }).validateArray(name, value, error, true, { dataType: 'integer' }); expect(result).to.deep.equal(undefined); expect(error[`${name}.$0`].message).to.equal('invalid integer number'); expect(error[`${name}.$0`].value).to.equal('A'); @@ -993,14 +1248,17 @@ describe('ValidationService', () => { const name = 'name'; const value = [{ a: 123 }, { a: 'bcd' }]; const error: FieldErrors = {}; - const result = new ValidationService({ - ExampleModel: { - dataType: 'refObject', - properties: { - a: { dataType: 'string', required: true }, + const result = new ValidationService( + { + ExampleModel: { + dataType: 'refObject', + properties: { + a: { dataType: 'string', required: true }, + }, }, }, - }).validateArray(name, value, error, true, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, { ref: 'ExampleModel' }); + { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, + ).validateArray(name, value, error, true, { ref: 'ExampleModel' }); expect(result).to.deep.equal(undefined); expect(error).to.deep.equal({ [`${name}.$0.a`]: { @@ -1014,12 +1272,11 @@ describe('ValidationService', () => { const name = 'name'; const value = [80, 10, 199]; const error: FieldErrors = {}; - const result = new ValidationService({}).validateArray( + const result = new ValidationService({}, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }).validateArray( name, value, error, true, - { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, { dataType: 'integer' }, { minItems: { value: 4 } }, ); @@ -1031,12 +1288,11 @@ describe('ValidationService', () => { const name = 'name'; const value = [80, 10, 199]; const error: FieldErrors = {}; - const result = new ValidationService({}).validateArray( + const result = new ValidationService({}, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }).validateArray( name, value, error, true, - { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, { dataType: 'integer' }, { maxItems: { value: 2 } }, ); @@ -1048,7 +1304,7 @@ describe('ValidationService', () => { const name = 'name'; const value = [10, 10, 20]; const error: FieldErrors = {}; - const result = new ValidationService({}).validateArray(name, value, error, true, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }, { dataType: 'integer' }, { uniqueItems: {} }); + const result = new ValidationService({}, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true }).validateArray(name, value, error, true, { dataType: 'integer' }, { uniqueItems: {} }); expect(result).to.equal(undefined); expect(error[name].message).to.equal(`required unique array`); }); @@ -1058,13 +1314,15 @@ describe('ValidationService', () => { dataType: 'refEnum', enums: ['foo', 'bar'], }; - const v = new ValidationService({ enumModel }); - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; + const v = new ValidationService( + { enumModel }, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ); const fieldErrors = {}; - const result = v.validateArray('name', ['foo', 'bar', 'foo', 'foobar'], fieldErrors, true, minimalSwaggerConfig, { dataType: 'refEnum', ref: 'enumModel' }); + const result = v.validateArray('name', ['foo', 'bar', 'foo', 'foobar'], fieldErrors, true, { dataType: 'refEnum', ref: 'enumModel' }); expect(Object.keys(fieldErrors)).to.not.be.empty; expect(result).to.be.undefined; expect(fieldErrors).to.deep.equal({ 'name.$3': { message: "should be one of the following; ['foo','bar']", value: 'foobar' } }); @@ -1074,7 +1332,7 @@ describe('ValidationService', () => { const name = 'name'; const value: any = 'some primitive string'; const error: FieldErrors = {}; - const result = new ValidationService({}).validateArray(name, value, error, true, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }, { dataType: 'string' }); + const result = new ValidationService({}, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: false }).validateArray(name, value, error, true, { dataType: 'string' }); expect(result).to.deep.equal(undefined); expect(error[name].message).to.equal('invalid array'); expect(error[name].value).to.equal('some primitive string'); @@ -1083,45 +1341,49 @@ describe('ValidationService', () => { describe('Union validate', () => { it('should validate discriminated union with silently-remove-extras on', () => { - const v = new ValidationService({ - TypeA: { - dataType: 'refObject', - properties: { - type: { dataType: 'enum', enums: ['A'], required: true }, - a: { dataType: 'double', required: true }, + const v = new ValidationService( + { + TypeA: { + dataType: 'refObject', + properties: { + type: { dataType: 'enum', enums: ['A'], required: true }, + a: { dataType: 'double', required: true }, + }, }, - }, - TypeB: { - dataType: 'refObject', - properties: { - type: { dataType: 'enum', enums: ['B'], required: true }, - b: { dataType: 'double', required: true }, + TypeB: { + dataType: 'refObject', + properties: { + type: { dataType: 'enum', enums: ['B'], required: true }, + b: { dataType: 'double', required: true }, + }, }, }, - }); + { + noImplicitAdditionalProperties: 'silently-remove-extras', + bodyCoercion: true, + }, + ); const name = 'name'; const error: FieldErrors = {}; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'silently-remove-extras', - bodyCoercion: true, - }; const schema: TsoaRoute.PropertySchema = { subSchemas: [{ ref: 'TypeA' }, { ref: 'TypeB' }] }; - const resultA = v.validateUnion(name, { type: 'A', a: 100 }, error, true, minimalSwaggerConfig, schema); - const resultB = v.validateUnion(name, { type: 'B', b: 20 }, error, true, minimalSwaggerConfig, schema); + const resultA = v.validateUnion(name, { type: 'A', a: 100 }, error, true, schema); + const resultB = v.validateUnion(name, { type: 'B', b: 20 }, error, true, schema); expect(resultA).to.deep.equal({ type: 'A', a: 100 }); expect(resultB).to.deep.equal({ type: 'B', b: 20 }); }); it('validates parent validators', () => { - const v = new ValidationService({}); + const v = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'silently-remove-extras', + bodyCoercion: true, + }, + ); const errors = {}; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'silently-remove-extras', - bodyCoercion: true, - }; const schema: TsoaRoute.PropertySchema = { dataType: 'union', subSchemas: [{ dataType: 'integer' }, { dataType: 'string' }], required: true, validators: { minimum: { value: 5 } } }; - const result = v.validateUnion('union', 2, errors, true, minimalSwaggerConfig, schema); + const result = v.validateUnion('union', 2, errors, true, schema); expect(errors).to.deep.equal({ union: { message: 'Could not match the union against any of the items. Issues: [{"union":{"message":"min 5","value":2}},{"union":{"message":"invalid string value","value":2}}]', @@ -1134,10 +1396,6 @@ describe('ValidationService', () => { describe('Intersection Validate', () => { describe('throw on extras', () => { - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'throw-on-extras', - bodyCoercion: true, - }; it('should validate intersection with 3 or more types', () => { const refName = 'ExampleModel'; const subSchemas: TsoaRoute.PropertySchema[] = [{ ref: 'TypeAliasModel1' }, { ref: 'TypeAliasModel2' }, { ref: 'TypeAliasModelDateTime' }]; @@ -1181,7 +1439,10 @@ describe('ValidationService', () => { additionalProperties: false, }, }; - const v = new ValidationService(models); + const v = new ValidationService(models, { + noImplicitAdditionalProperties: 'throw-on-extras', + bodyCoercion: true, + }); const errorDictionary: FieldErrors = {}; const dataToValidate: TypeAliasModel1 & TypeAliasModel2 & TypeAliasDateTime = { value1: 'this is value 1', @@ -1191,7 +1452,7 @@ describe('ValidationService', () => { // Act const name = 'dataToValidate'; - const validatedData = v.validateIntersection('and', dataToValidate, errorDictionary, true, minimalSwaggerConfig, subSchemas, name + '.'); + const validatedData = v.validateIntersection('and', dataToValidate, errorDictionary, true, subSchemas, name + '.'); // Assert const expectedValues = { ...dataToValidate, dateTimeValue: new Date('2017-01-01T00:00:00') }; @@ -1205,7 +1466,7 @@ describe('ValidationService', () => { }; const subSchemas2 = subSchemas.concat([{ ref: 'TypeAliasModelDate' }]); - const validatedData2 = v.validateIntersection('and', dataToValidate2, errorDictionary2, true, minimalSwaggerConfig, subSchemas2, name + '.'); + const validatedData2 = v.validateIntersection('and', dataToValidate2, errorDictionary2, true, subSchemas2, name + '.'); const expectedValues2 = { ...expectedValues, dateValue: new Date('2017-01-01') }; expect(errorDictionary2).to.deep.equal({}); @@ -1264,14 +1525,17 @@ describe('ValidationService', () => { additionalProperties: false, }, }; - const withUnionValidationService = new ValidationService(WithUnionModels); + const withUnionValidationService = new ValidationService(WithUnionModels, { + noImplicitAdditionalProperties: 'throw-on-extras', + bodyCoercion: true, + }); const withUnionDataToValidate1 = { model: 'model1', service: '23', }; const withUnionErrorDictionary1 = {}; - withUnionValidationService.validateIntersection('union', withUnionDataToValidate1, withUnionErrorDictionary1, true, minimalSwaggerConfig, withUnionsSubSchemas, withUnionsName + '.'); + withUnionValidationService.validateIntersection('union', withUnionDataToValidate1, withUnionErrorDictionary1, true, withUnionsSubSchemas, withUnionsName + '.'); // Assert expect(withUnionErrorDictionary1).to.deep.equal({ @@ -1289,15 +1553,7 @@ describe('ValidationService', () => { }; const withUnionErrorDictionary2 = {}; - const validatedResult2 = withUnionValidationService.validateIntersection( - 'union', - withUnionDataToValidate2, - withUnionErrorDictionary2, - true, - minimalSwaggerConfig, - withUnionsSubSchemas, - withUnionsName + '.', - ); + const validatedResult2 = withUnionValidationService.validateIntersection('union', withUnionDataToValidate2, withUnionErrorDictionary2, true, withUnionsSubSchemas, withUnionsName + '.'); // Assert expect(withUnionErrorDictionary2).to.deep.equal({}); @@ -1310,15 +1566,7 @@ describe('ValidationService', () => { }; const withUnionErrorDictionary3 = {}; - const validatedResult3 = withUnionValidationService.validateIntersection( - 'union', - withUnionDataToValidate3, - withUnionErrorDictionary3, - true, - minimalSwaggerConfig, - withUnionsSubSchemas, - withUnionsName + '.', - ); + const validatedResult3 = withUnionValidationService.validateIntersection('union', withUnionDataToValidate3, withUnionErrorDictionary3, true, withUnionsSubSchemas, withUnionsName + '.'); // Assert expect(withUnionErrorDictionary3).to.deep.equal({}); @@ -1404,7 +1652,10 @@ describe('ValidationService', () => { additionalProperties: false, }, }; - const v = new ValidationService(models); + const v = new ValidationService(models, { + noImplicitAdditionalProperties: 'throw-on-extras', + bodyCoercion: true, + }); // Validate all schema combinations const validInputs = [ @@ -1447,7 +1698,7 @@ describe('ValidationService', () => { // Act const errorDictionary: FieldErrors = {}; - const validatedData = v.validateIntersection('and', input, errorDictionary, true, minimalSwaggerConfig, subSchemas, refName + '.'); + const validatedData = v.validateIntersection('and', input, errorDictionary, true, subSchemas, refName + '.'); // Assert expect(errorDictionary, `validInputs[${i}] returned errors`).to.deep.equal({}); @@ -1482,7 +1733,7 @@ describe('ValidationService', () => { // Act const errorDictionary: FieldErrors = {}; - const validatedData = v.validateIntersection('and', invalidInput, errorDictionary, true, minimalSwaggerConfig, subSchemas, refName + '.'); + const validatedData = v.validateIntersection('and', invalidInput, errorDictionary, true, subSchemas, refName + '.'); // Assert expect(errorDictionary, `${name}[${i}] did not return errors`).to.not.deep.equal({}); @@ -1503,12 +1754,14 @@ describe('ValidationService', () => { it('returns undefined when not optional', () => { const value = undefined; const propertySchema: TsoaRoute.PropertySchema = { dataType: 'undefined', required: true, validators: {} }; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; const fieldErrors: FieldErrors = {}; - const result = new ValidationService({}).ValidateParam(propertySchema, value, 'defaultProp', fieldErrors, true, undefined, minimalSwaggerConfig); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).ValidateParam(propertySchema, value, 'defaultProp', fieldErrors, true, undefined); expect(Object.keys(fieldErrors)).to.be.empty; expect(result).to.be.undefined; }); @@ -1516,12 +1769,14 @@ describe('ValidationService', () => { it('fail if value required and not undefined', () => { const value = 'undefined'; const propertySchema: TsoaRoute.PropertySchema = { dataType: 'undefined', required: true, validators: {} }; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; const fieldErrors: FieldErrors = {}; - const result = new ValidationService({}).ValidateParam(propertySchema, value, 'defaultProp', fieldErrors, true, undefined, minimalSwaggerConfig); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).ValidateParam(propertySchema, value, 'defaultProp', fieldErrors, true, undefined); expect(Object.keys(fieldErrors)).to.not.be.empty; expect(result).to.be.undefined; }); @@ -1533,13 +1788,15 @@ describe('ValidationService', () => { a: { dataType: 'undefined' }, }, }; - const v = new ValidationService({}); + const v = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ); const error = {}; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; - const result = v.validateModel({ name: '', value: {}, modelDefinition, fieldErrors: error, isBodyParam: true, minimalSwaggerConfig }); + const result = v.validateModel({ name: '', value: {}, modelDefinition, fieldErrors: error, isBodyParam: true }); expect(Object.keys(error)).to.be.empty; // use JSON strngify to allow comparison of undefined values expect(JSON.stringify(result, replacer)).to.equal(JSON.stringify(result)); @@ -1552,13 +1809,15 @@ describe('ValidationService', () => { a: { dataType: 'undefined', required: true }, }, }; - const v = new ValidationService({}); + const v = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ); const error = {}; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; - const result = v.validateModel({ name: '', value: {}, modelDefinition, fieldErrors: error, isBodyParam: true, minimalSwaggerConfig }); + const result = v.validateModel({ name: '', value: {}, modelDefinition, fieldErrors: error, isBodyParam: true }); expect(Object.keys(error)).to.be.empty; // use JSON strngify to allow comparison of undefined values expect(JSON.stringify(result, replacer)).to.equal(JSON.stringify({ a: undefined }, replacer)); @@ -1567,12 +1826,14 @@ describe('ValidationService', () => { it('fail if value optional and not undefined', () => { const value = 'undefined'; const propertySchema: TsoaRoute.PropertySchema = { dataType: 'undefined', required: false, validators: {} }; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'ignore', - bodyCoercion: true, - }; const fieldErrors: FieldErrors = {}; - const result = new ValidationService({}).ValidateParam(propertySchema, value, 'defaultProp', fieldErrors, true, undefined, minimalSwaggerConfig); + const result = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'ignore', + bodyCoercion: true, + }, + ).ValidateParam(propertySchema, value, 'defaultProp', fieldErrors, true, undefined); expect(Object.keys(fieldErrors)).to.not.be.empty; expect(result).to.be.undefined; }); diff --git a/tests/unit/templating/templateHelpers.spec.ts b/tests/unit/templating/templateHelpers.spec.ts index 2c5f1bd5b..b7cfc4796 100644 --- a/tests/unit/templating/templateHelpers.spec.ts +++ b/tests/unit/templating/templateHelpers.spec.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; import 'mocha'; -import { TsoaRoute, AdditionalProps, ValidateError, FieldErrors, ValidationService } from '@tsoa/runtime'; +import { TsoaRoute, ValidateError, FieldErrors, ValidationService } from '@tsoa/runtime'; import { TypeAliasModel1, TypeAliasModel2 } from '../../fixtures/testModel'; it('ValidateError should be an instanceof ValidateError', () => { @@ -40,11 +40,10 @@ it('should allow additionalProperties (on a union) if noImplicitAdditionalProper additionalProperties: false, }, }; - const v = new ValidationService(models); - const minimalSwaggerConfig: AdditionalProps = { + const v = new ValidationService(models, { noImplicitAdditionalProperties: 'silently-remove-extras', bodyCoercion: true, - }; + }); const errorDictionary: FieldErrors = {}; const nameOfAdditionalProperty = 'I am the bad key name'; const dataToValidate: TypeAliasModel1 = { @@ -54,7 +53,7 @@ it('should allow additionalProperties (on a union) if noImplicitAdditionalProper // Act const name = 'dataToValidate'; - const result = v.validateUnion('or', dataToValidate, errorDictionary, true, minimalSwaggerConfig, unionProperty, name + '.'); + const result = v.validateUnion('or', dataToValidate, errorDictionary, true, unionProperty, name + '.'); // Assert expect(errorDictionary).to.deep.eq({}); @@ -94,11 +93,10 @@ it('should throw if the data has additionalProperties (on a union) if noImplicit additionalProperties: false, }, }; - const v = new ValidationService(models); - const minimalSwaggerConfig: AdditionalProps = { + const v = new ValidationService(models, { noImplicitAdditionalProperties: 'throw-on-extras', bodyCoercion: true, - }; + }); const errorDictionary: FieldErrors = {}; const nameOfAdditionalProperty = 'I am the bad key name' as keyof TypeAliasModel1; const dataToValidate: TypeAliasModel1 = { @@ -108,7 +106,7 @@ it('should throw if the data has additionalProperties (on a union) if noImplicit // Act const name = 'dataToValidate'; - v.validateUnion('or', dataToValidate, errorDictionary, true, minimalSwaggerConfig, unionPropertySchema, name + '.'); + v.validateUnion('or', dataToValidate, errorDictionary, true, unionPropertySchema, name + '.'); // Assert const errorKeys = Object.keys(errorDictionary); @@ -154,11 +152,10 @@ it('should throw if the data has additionalProperties (on a intersection) if noI additionalProperties: false, }, }; - const v = new ValidationService(models); - const minimalSwaggerConfig: AdditionalProps = { + const v = new ValidationService(models, { noImplicitAdditionalProperties: 'throw-on-extras', bodyCoercion: true, - }; + }); const errorDictionary: FieldErrors = {}; const nameOfAdditionalProperty = 'extraKeyName' as keyof (TypeAliasModel1 & TypeAliasModel2); // pretend this is fine const expectedErrorMsg = `Could not match intersection against any of the possible combinations: [["value1","value2"]]`; @@ -170,7 +167,7 @@ it('should throw if the data has additionalProperties (on a intersection) if noI // Act const name = 'dataToValidate'; - v.validateIntersection('and', dataToValidate, errorDictionary, true, minimalSwaggerConfig, subSchemas, name + '.'); + v.validateIntersection('and', dataToValidate, errorDictionary, true, subSchemas, name + '.'); // Assert const errorKeys = Object.keys(errorDictionary); @@ -239,11 +236,10 @@ it('should throw if the data has additionalProperties (on a nested Object) if no additionalProperties: false, }, }; - const v = new ValidationService(models); - const minimalSwaggerConfig: AdditionalProps = { + const v = new ValidationService(models, { noImplicitAdditionalProperties: 'throw-on-extras', bodyCoercion: true, - }; + }); const errorDictionary: FieldErrors = {}; const dataToValidate = { name: '', @@ -263,7 +259,7 @@ it('should throw if the data has additionalProperties (on a nested Object) if no }; // Act - const result = v.validateNestedObjectLiteral('objLiteral', dataToValidate, errorDictionary, true, minimalSwaggerConfig, models[refName].properties.objLiteral.nestedProperties, false, refName + '.'); + const result = v.validateNestedObjectLiteral('objLiteral', dataToValidate, errorDictionary, true, models[refName].properties.objLiteral.nestedProperties, false, refName + '.'); // Assert expect(errorDictionary).to.deep.eq({ @@ -309,11 +305,10 @@ it('should not throw if the data has additionalProperties (on a intersection) if additionalProperties: false, }, }; - const v = new ValidationService(models); - const minimalSwaggerConfig: AdditionalProps = { + const v = new ValidationService(models, { noImplicitAdditionalProperties: 'silently-remove-extras', bodyCoercion: true, - }; + }); const errorDictionary: FieldErrors = {}; const nameOfAdditionalProperty = 'extraKeyName' as keyof (TypeAliasModel1 & TypeAliasModel2); // pretend this is fine const dataToValidate: TypeAliasModel1 & TypeAliasModel2 = { @@ -324,7 +319,7 @@ it('should not throw if the data has additionalProperties (on a intersection) if // Act const name = 'dataToValidate'; - const result = v.validateIntersection('and', dataToValidate, errorDictionary, true, minimalSwaggerConfig, subSchemas, name + '.'); + const result = v.validateIntersection('and', dataToValidate, errorDictionary, true, subSchemas, name + '.'); // Assert expect(errorDictionary).to.deep.eq({}); @@ -367,11 +362,10 @@ it('should not throw if the data has additionalProperties (on a intersection) if additionalProperties: false, }, }; - const v = new ValidationService(models); - const minimalSwaggerConfig: AdditionalProps = { + const v = new ValidationService(models, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true, - }; + }); const errorDictionary: FieldErrors = {}; const nameOfAdditionalProperty = 'extraKeyName' as keyof (TypeAliasModel1 & TypeAliasModel2); // pretend this is fine const dataToValidate: TypeAliasModel1 & TypeAliasModel2 = { @@ -382,7 +376,7 @@ it('should not throw if the data has additionalProperties (on a intersection) if // Act const name = 'dataToValidate'; - const result = v.validateIntersection('and', dataToValidate, errorDictionary, true, minimalSwaggerConfig, subSchemas, name + '.'); + const result = v.validateIntersection('and', dataToValidate, errorDictionary, true, subSchemas, name + '.'); // Assert expect(errorDictionary).to.deep.eq({}); @@ -441,11 +435,10 @@ it('should not throw if the data has additionalProperties (on a nested Object) i additionalProperties: false, }, }; - const v = new ValidationService(models); - const minimalSwaggerConfig: AdditionalProps = { + const v = new ValidationService(models, { noImplicitAdditionalProperties: 'silently-remove-extras', bodyCoercion: true, - }; + }); const errorDictionary: FieldErrors = {}; const dataToValidate = { name: '', @@ -465,7 +458,7 @@ it('should not throw if the data has additionalProperties (on a nested Object) i }; // Act - const result = v.validateNestedObjectLiteral('objLiteral', dataToValidate, errorDictionary, true, minimalSwaggerConfig, models[refName].properties.objLiteral.nestedProperties, false, refName + '.'); + const result = v.validateNestedObjectLiteral('objLiteral', dataToValidate, errorDictionary, true, models[refName].properties.objLiteral.nestedProperties, false, refName + '.'); // Assert expect(errorDictionary).to.deep.eq({}); @@ -537,11 +530,10 @@ it('should not throw if the data has additionalProperties (on a nested Object) i additionalProperties: false, }, }; - const v = new ValidationService(models); - const minimalSwaggerConfig: AdditionalProps = { + const v = new ValidationService(models, { noImplicitAdditionalProperties: 'ignore', bodyCoercion: true, - }; + }); const errorDictionary: FieldErrors = {}; const dataToValidate = { name: '', @@ -561,7 +553,7 @@ it('should not throw if the data has additionalProperties (on a nested Object) i }; // Act - const result = v.validateNestedObjectLiteral('objLiteral', dataToValidate, errorDictionary, true, minimalSwaggerConfig, models[refName].properties.objLiteral.nestedProperties, false, refName + '.'); + const result = v.validateNestedObjectLiteral('objLiteral', dataToValidate, errorDictionary, true, models[refName].properties.objLiteral.nestedProperties, false, refName + '.'); // Assert expect(errorDictionary).to.deep.eq({}); @@ -596,15 +588,17 @@ it('should throw if properties on nOl are missing', () => { }, }; - const v = new ValidationService({}); + const v = new ValidationService( + {}, + { + noImplicitAdditionalProperties: 'silently-remove-extras', + bodyCoercion: true, + }, + ); const errors = {}; - const minimalSwaggerConfig: AdditionalProps = { - noImplicitAdditionalProperties: 'silently-remove-extras', - bodyCoercion: true, - }; - v.validateNestedObjectLiteral('nested', {}, errors, true, minimalSwaggerConfig, schema, true, 'Model.'); + v.validateNestedObjectLiteral('nested', {}, errors, true, schema, true, 'Model.'); expect(Object.keys(errors).length).to.equal(2); @@ -615,7 +609,7 @@ it('should throw if properties on nOl are missing', () => { const nestedErrors = {}; - v.validateNestedObjectLiteral('nested', { street: {} }, nestedErrors, true, minimalSwaggerConfig, schema, true, 'Model.'); + v.validateNestedObjectLiteral('nested', { street: {} }, nestedErrors, true, schema, true, 'Model.'); expect(nestedErrors).to.deep.eq({ 'Model.nested.country': {