From 92e7083fa964f0fd16f8efaeba20225418ad2655 Mon Sep 17 00:00:00 2001 From: Alexey Pelykh Date: Fri, 10 Mar 2017 12:29:51 +0200 Subject: [PATCH] Hashmap support --- src/metadataGeneration/metadataGenerator.ts | 1 + src/metadataGeneration/resolveType.ts | 37 ++++++- src/routeGeneration/routeGenerator.ts | 36 ++++++- src/routeGeneration/templateHelpers.ts | 28 ++++- src/swagger/specGenerator.ts | 16 +++ src/swagger/swagger.ts | 2 +- tests/fixtures/express/routes.ts | 90 ++++++++++------ tests/fixtures/hapi/routes.ts | 102 +++++++++++------- tests/fixtures/inversify/routes.ts | 50 ++++++--- tests/fixtures/koa/routes.ts | 90 ++++++++++------ tests/fixtures/testModel.ts | 10 ++ tests/integration/express-server.spec.ts | 7 ++ tests/integration/hapi-server.spec.ts | 7 ++ tests/integration/inversify-server.spec.ts | 7 ++ tests/integration/koa-server.spec.ts | 7 ++ .../definitionsGeneration/definitions.spec.ts | 2 +- 16 files changed, 362 insertions(+), 130 deletions(-) diff --git a/src/metadataGeneration/metadataGenerator.ts b/src/metadataGeneration/metadataGenerator.ts index c57e76e0a..95ccaaa5a 100644 --- a/src/metadataGeneration/metadataGenerator.ts +++ b/src/metadataGeneration/metadataGenerator.ts @@ -106,6 +106,7 @@ export interface ReferenceType { description: string; name: string; properties: Property[]; + additionalProperties?: Property[]; enum?: string[]; } diff --git a/src/metadataGeneration/resolveType.ts b/src/metadataGeneration/resolveType.ts index ec3334143..2d6e716d6 100644 --- a/src/metadataGeneration/resolveType.ts +++ b/src/metadataGeneration/resolveType.ts @@ -58,12 +58,16 @@ function generateReferenceType(typeName: string): ReferenceType { const modelTypeDeclaration = getModelTypeDeclaration(typeName); const properties = getModelTypeProperties(modelTypeDeclaration); + const additionalProperties = getModelTypeAdditionalProperties(modelTypeDeclaration); const referenceType: ReferenceType = { description: getModelDescription(modelTypeDeclaration), name: typeName, properties: properties }; + if (additionalProperties && additionalProperties.length) { + referenceType.additionalProperties = additionalProperties; + } if (modelTypeDeclaration.kind === ts.SyntaxKind.TypeAliasDeclaration) { const innerType = modelTypeDeclaration.type; if (innerType.kind === ts.SyntaxKind.UnionType && (innerType as any).types) { @@ -132,8 +136,8 @@ function getModelTypeProperties(node: UsableDeclaration) { const interfaceDeclaration = node as ts.InterfaceDeclaration; return interfaceDeclaration.members .filter(member => member.kind === ts.SyntaxKind.PropertySignature) - .map((property: any) => { - const propertyDeclaration = property as ts.PropertyDeclaration; + .map((member: any) => { + const propertyDeclaration = member as ts.PropertyDeclaration; const identifier = propertyDeclaration.name as ts.Identifier; if (!propertyDeclaration.type) { throw new Error('No valid type found for property declaration.'); } @@ -141,7 +145,7 @@ function getModelTypeProperties(node: UsableDeclaration) { return { description: getNodeDescription(propertyDeclaration), name: identifier.text, - required: !property.questionToken, + required: !propertyDeclaration.questionToken, type: ResolveType(propertyDeclaration.type) }; }); @@ -149,9 +153,9 @@ function getModelTypeProperties(node: UsableDeclaration) { if (node.kind === ts.SyntaxKind.TypeAliasDeclaration) { /** - * TOOD + * TODO * - * Flesh this out so that we can properly support Type Alii instead of just assuming + * Flesh this out so that we can properly support Type Alias instead of just assuming * string literal enums */ return []; @@ -186,6 +190,29 @@ function getModelTypeProperties(node: UsableDeclaration) { }); } +function getModelTypeAdditionalProperties(node: UsableDeclaration) { + if (node.kind === ts.SyntaxKind.InterfaceDeclaration) { + const interfaceDeclaration = node as ts.InterfaceDeclaration; + return interfaceDeclaration.members + .filter(member => member.kind === ts.SyntaxKind.IndexSignature) + .map((member: any) => { + const indexSignatureDeclaration = member as ts.IndexSignatureDeclaration; + + const indexType = ResolveType(indexSignatureDeclaration.parameters[0].type); + if (indexType !== 'string') { throw new Error('Only string indexers are supported'); } + + return { + description: '', + name: '', + required: true, + type: ResolveType(indexSignatureDeclaration.type) + }; + }); + } + + return undefined; + } + function hasPublicModifier(node: ts.Node) { return !node.modifiers || node.modifiers.every(modifier => { return modifier.kind !== ts.SyntaxKind.ProtectedKeyword && modifier.kind !== ts.SyntaxKind.PrivateKeyword; diff --git a/src/routeGeneration/routeGenerator.ts b/src/routeGeneration/routeGenerator.ts index dd9a6a528..42e9ab4e7 100644 --- a/src/routeGeneration/routeGenerator.ts +++ b/src/routeGeneration/routeGenerator.ts @@ -72,9 +72,20 @@ export class RouteGenerator { const models: any = { {{#each models}} '{{name}}': { - {{#each properties}} - '{{name}}': { typeName: '{{typeName}}', required: {{required}} {{#if arrayType}}, arrayType: '{{arrayType}}' {{/if}} }, - {{/each}} + {{#if properties}} + properties: { + {{#each properties}} + '{{name}}': { typeName: '{{typeName}}', required: {{required}} {{#if arrayType}}, arrayType: '{{arrayType}}' {{/if}} }, + {{/each}} + }, + {{/if}} + {{#if additionalProperties}} + additionalProperties: [ + {{#each additionalProperties}} + {typeName: ''}, + {{/each}} + ], + {{/if}} }, {{/each}} }; @@ -114,10 +125,14 @@ export class RouteGenerator { return Object.keys(this.metadata.ReferenceTypes).map(key => { const referenceType = this.metadata.ReferenceTypes[key]; - return { + let templateModel: TemplateModel = { name: key, properties: referenceType.properties.map(property => this.getTemplateProperty(property)) }; + if (referenceType.additionalProperties && referenceType.additionalProperties.length) { + templateModel.additionalProperties = referenceType.additionalProperties.map(property => this.getTemplateAdditionalProperty(property)); + } + return templateModel; }); } @@ -154,6 +169,14 @@ export class RouteGenerator { return templateProperty; } + private getTemplateAdditionalProperty(source: Property): TemplateAdditionalProperty { + const templateAdditionalProperty: TemplateAdditionalProperty = { + typeName: this.getStringRepresentationOfType(source.type) + }; + + return templateAdditionalProperty; + } + private getTemplateParameter(parameter: Parameter): TemplateParameter { const templateParameter: TemplateParameter = { argumentName: parameter.argumentName, @@ -175,6 +198,7 @@ export class RouteGenerator { interface TemplateModel { name: string; properties: TemplateProperty[]; + additionalProperties?: TemplateAdditionalProperty[]; } interface TemplateProperty { @@ -185,6 +209,10 @@ interface TemplateProperty { request?: boolean; } +interface TemplateAdditionalProperty { + typeName: string; +} + interface TemplateParameter { name: String; argumentName: string; diff --git a/src/routeGeneration/templateHelpers.ts b/src/routeGeneration/templateHelpers.ts index efe3eaeba..0d5299fdc 100644 --- a/src/routeGeneration/templateHelpers.ts +++ b/src/routeGeneration/templateHelpers.ts @@ -69,10 +69,30 @@ function validateModel(modelValue: any, typeName: string): any { const modelDefinition = models[typeName]; if (modelDefinition) { - Object.keys(modelDefinition).forEach((key: string) => { - const property = modelDefinition[key]; - modelValue[key] = ValidateParam(property, modelValue[key], models, key); - }); + if (modelDefinition.properties) { + Object.keys(modelDefinition.properties).forEach((key: string) => { + const property = modelDefinition.properties[key]; + modelValue[key] = ValidateParam(property, modelValue[key], models, key); + }); + } + if (modelDefinition.additionalProperties) { + Object.keys(modelValue).forEach((key: string) => { + let validatedValue; + for (const additionalProperty of modelDefinition.additionalProperties) { + try { + validatedValue = ValidateParam(additionalProperty, modelValue[key], models, key); + break; + } catch (err) { + continue; + } + } + if (validatedValue) { + modelValue[key] = validatedValue; + } else { + throw new Error(`No matching model found in additionalProperties`); + } + }); + } } return modelValue; diff --git a/src/swagger/specGenerator.ts b/src/swagger/specGenerator.ts index 6feb20b47..2cbb05fc4 100644 --- a/src/swagger/specGenerator.ts +++ b/src/swagger/specGenerator.ts @@ -65,6 +65,9 @@ export class SpecGenerator { required: referenceType.properties.filter(p => p.required).map(p => p.name), type: 'object' }; + if (referenceType.additionalProperties) { + definitions[referenceType.name].additionalProperties = this.buildAdditionalProperties(referenceType.additionalProperties); + } if (referenceType.enum) { definitions[referenceType.name].type = 'string'; delete definitions[referenceType.name].properties; @@ -182,6 +185,19 @@ export class SpecGenerator { return swaggerProperties; } + private buildAdditionalProperties(properties: Property[]) { + let swaggerAdditionalProperties: { [ref: string]: string } = {}; + + properties.forEach(property => { + const swaggerType = this.getSwaggerType(property.type); + if (swaggerType.$ref) { + swaggerAdditionalProperties['$ref'] = swaggerType.$ref; + } + }); + + return swaggerAdditionalProperties; + } + private buildOperation(controllerName: string, method: Method) { const responses: any = {}; diff --git a/src/swagger/swagger.ts b/src/swagger/swagger.ts index ffd46e196..f6633415d 100644 --- a/src/swagger/swagger.ts +++ b/src/swagger/swagger.ts @@ -133,7 +133,7 @@ export namespace Swagger { export interface Schema extends BaseSchema { $ref?: string; allOf?: [Schema]; - additionalProperties?: boolean; + additionalProperties?: boolean | { [ref: string]: string }; properties?: { [propertyName: string]: Schema }; discriminator?: string; readOnly?: boolean; diff --git a/tests/fixtures/express/routes.ts b/tests/fixtures/express/routes.ts index cc9f7e05d..4fb1f8631 100644 --- a/tests/fixtures/express/routes.ts +++ b/tests/fixtures/express/routes.ts @@ -12,51 +12,79 @@ import { SecurityTestController } from './../controllers/securityController'; const models: any = { 'TestModel': { - 'numberValue': { typeName: 'number', required: true }, - 'numberArray': { typeName: 'array', required: true, arrayType: 'number' }, - 'stringValue': { typeName: 'string', required: true }, - 'stringArray': { typeName: 'array', required: true, arrayType: 'string' }, - 'boolValue': { typeName: 'boolean', required: true }, - 'boolArray': { typeName: 'array', required: true, arrayType: 'boolean' }, - 'modelValue': { typeName: 'TestSubModel', required: true }, - 'modelsArray': { typeName: 'array', required: true, arrayType: 'TestSubModel' }, - 'strLiteralVal': { typeName: 'StrLiteral', required: true }, - 'strLiteralArr': { typeName: 'array', required: true, arrayType: 'StrLiteral' }, - 'dateValue': { typeName: 'datetime', required: false }, - 'optionalString': { typeName: 'string', required: false }, - 'id': { typeName: 'number', required: true }, + properties: { + 'numberValue': { typeName: 'number', required: true }, + 'numberArray': { typeName: 'array', required: true, arrayType: 'number' }, + 'stringValue': { typeName: 'string', required: true }, + 'stringArray': { typeName: 'array', required: true, arrayType: 'string' }, + 'boolValue': { typeName: 'boolean', required: true }, + 'boolArray': { typeName: 'array', required: true, arrayType: 'boolean' }, + 'modelValue': { typeName: 'TestSubModel', required: true }, + 'modelsArray': { typeName: 'array', required: true, arrayType: 'TestSubModel' }, + 'strLiteralVal': { typeName: 'StrLiteral', required: true }, + 'strLiteralArr': { typeName: 'array', required: true, arrayType: 'StrLiteral' }, + 'dateValue': { typeName: 'datetime', required: false }, + 'optionalString': { typeName: 'string', required: false }, + 'modelsObjectIndirect': { typeName: 'TestSubModelContainer', required: false }, + 'id': { typeName: 'number', required: true }, + }, }, 'TestSubModel': { - 'email': { typeName: 'string', required: true }, - 'circular': { typeName: 'TestModel', required: false }, - 'id': { typeName: 'number', required: true }, + properties: { + 'email': { typeName: 'string', required: true }, + 'circular': { typeName: 'TestModel', required: false }, + 'id': { typeName: 'number', required: true }, + }, }, 'StrLiteral': { }, + 'TestSubModel2': { + properties: { + 'testSubModel2': { typeName: 'boolean', required: true }, + 'email': { typeName: 'string', required: true }, + 'circular': { typeName: 'TestModel', required: false }, + 'id': { typeName: 'number', required: true }, + }, + }, + 'TestSubModelContainer': { + additionalProperties: [ + { typeName: '' }, + ], + }, 'TestClassModel': { - 'publicStringProperty': { typeName: 'string', required: true }, - 'optionalPublicStringProperty': { typeName: 'string', required: false }, - 'stringProperty': { typeName: 'string', required: true }, - 'publicConstructorVar': { typeName: 'string', required: true }, - 'optionalPublicConstructorVar': { typeName: 'string', required: false }, - 'id': { typeName: 'number', required: true }, + properties: { + 'publicStringProperty': { typeName: 'string', required: true }, + 'optionalPublicStringProperty': { typeName: 'string', required: false }, + 'stringProperty': { typeName: 'string', required: true }, + 'publicConstructorVar': { typeName: 'string', required: true }, + 'optionalPublicConstructorVar': { typeName: 'string', required: false }, + 'id': { typeName: 'number', required: true }, + }, }, 'Result': { - 'value': { typeName: 'object', required: true }, + properties: { + 'value': { typeName: 'object', required: true }, + }, }, 'ErrorResponseModel': { - 'status': { typeName: 'number', required: true }, - 'message': { typeName: 'string', required: true }, + properties: { + 'status': { typeName: 'number', required: true }, + 'message': { typeName: 'string', required: true }, + }, }, 'ParameterTestModel': { - 'firstname': { typeName: 'string', required: true }, - 'lastname': { typeName: 'string', required: true }, - 'age': { typeName: 'number', required: true }, - 'human': { typeName: 'boolean', required: true }, + properties: { + 'firstname': { typeName: 'string', required: true }, + 'lastname': { typeName: 'string', required: true }, + 'age': { typeName: 'number', required: true }, + 'human': { typeName: 'boolean', required: true }, + }, }, 'UserResponseModel': { - 'id': { typeName: 'number', required: true }, - 'name': { typeName: 'string', required: true }, + properties: { + 'id': { typeName: 'number', required: true }, + 'name': { typeName: 'string', required: true }, + }, }, }; diff --git a/tests/fixtures/hapi/routes.ts b/tests/fixtures/hapi/routes.ts index b0bb070a1..67e6bcca2 100644 --- a/tests/fixtures/hapi/routes.ts +++ b/tests/fixtures/hapi/routes.ts @@ -12,51 +12,79 @@ import { SecurityTestController } from './../controllers/securityController'; const models: any = { 'TestModel': { - 'numberValue': { typeName: 'number', required: true }, - 'numberArray': { typeName: 'array', required: true, arrayType: 'number' }, - 'stringValue': { typeName: 'string', required: true }, - 'stringArray': { typeName: 'array', required: true, arrayType: 'string' }, - 'boolValue': { typeName: 'boolean', required: true }, - 'boolArray': { typeName: 'array', required: true, arrayType: 'boolean' }, - 'modelValue': { typeName: 'TestSubModel', required: true }, - 'modelsArray': { typeName: 'array', required: true, arrayType: 'TestSubModel' }, - 'strLiteralVal': { typeName: 'StrLiteral', required: true }, - 'strLiteralArr': { typeName: 'array', required: true, arrayType: 'StrLiteral' }, - 'dateValue': { typeName: 'datetime', required: false }, - 'optionalString': { typeName: 'string', required: false }, - 'id': { typeName: 'number', required: true }, + properties: { + 'numberValue': { typeName: 'number', required: true }, + 'numberArray': { typeName: 'array', required: true, arrayType: 'number' }, + 'stringValue': { typeName: 'string', required: true }, + 'stringArray': { typeName: 'array', required: true, arrayType: 'string' }, + 'boolValue': { typeName: 'boolean', required: true }, + 'boolArray': { typeName: 'array', required: true, arrayType: 'boolean' }, + 'modelValue': { typeName: 'TestSubModel', required: true }, + 'modelsArray': { typeName: 'array', required: true, arrayType: 'TestSubModel' }, + 'strLiteralVal': { typeName: 'StrLiteral', required: true }, + 'strLiteralArr': { typeName: 'array', required: true, arrayType: 'StrLiteral' }, + 'dateValue': { typeName: 'datetime', required: false }, + 'optionalString': { typeName: 'string', required: false }, + 'modelsObjectIndirect': { typeName: 'TestSubModelContainer', required: false }, + 'id': { typeName: 'number', required: true }, + }, }, 'TestSubModel': { - 'email': { typeName: 'string', required: true }, - 'circular': { typeName: 'TestModel', required: false }, - 'id': { typeName: 'number', required: true }, + properties: { + 'email': { typeName: 'string', required: true }, + 'circular': { typeName: 'TestModel', required: false }, + 'id': { typeName: 'number', required: true }, + }, }, 'StrLiteral': { }, + 'TestSubModel2': { + properties: { + 'testSubModel2': { typeName: 'boolean', required: true }, + 'email': { typeName: 'string', required: true }, + 'circular': { typeName: 'TestModel', required: false }, + 'id': { typeName: 'number', required: true }, + }, + }, + 'TestSubModelContainer': { + additionalProperties: [ + { typeName: '' }, + ], + }, 'TestClassModel': { - 'publicStringProperty': { typeName: 'string', required: true }, - 'optionalPublicStringProperty': { typeName: 'string', required: false }, - 'stringProperty': { typeName: 'string', required: true }, - 'publicConstructorVar': { typeName: 'string', required: true }, - 'optionalPublicConstructorVar': { typeName: 'string', required: false }, - 'id': { typeName: 'number', required: true }, + properties: { + 'publicStringProperty': { typeName: 'string', required: true }, + 'optionalPublicStringProperty': { typeName: 'string', required: false }, + 'stringProperty': { typeName: 'string', required: true }, + 'publicConstructorVar': { typeName: 'string', required: true }, + 'optionalPublicConstructorVar': { typeName: 'string', required: false }, + 'id': { typeName: 'number', required: true }, + }, }, 'Result': { - 'value': { typeName: 'object', required: true }, + properties: { + 'value': { typeName: 'object', required: true }, + }, }, 'ErrorResponseModel': { - 'status': { typeName: 'number', required: true }, - 'message': { typeName: 'string', required: true }, + properties: { + 'status': { typeName: 'number', required: true }, + 'message': { typeName: 'string', required: true }, + }, }, 'ParameterTestModel': { - 'firstname': { typeName: 'string', required: true }, - 'lastname': { typeName: 'string', required: true }, - 'age': { typeName: 'number', required: true }, - 'human': { typeName: 'boolean', required: true }, + properties: { + 'firstname': { typeName: 'string', required: true }, + 'lastname': { typeName: 'string', required: true }, + 'age': { typeName: 'number', required: true }, + 'human': { typeName: 'boolean', required: true }, + }, }, 'UserResponseModel': { - 'id': { typeName: 'number', required: true }, - 'name': { typeName: 'string', required: true }, + properties: { + 'id': { typeName: 'number', required: true }, + 'name': { typeName: 'string', required: true }, + }, }, }; @@ -1113,8 +1141,8 @@ export function RegisterRoutes(server: hapi.Server) { pre: [ { method: authenticateMiddleware('api_key' - ) -} + ) + } ], handler: (request: any, reply) => { const args = { @@ -1149,8 +1177,8 @@ export function RegisterRoutes(server: hapi.Server) { 'write:pets', 'read:pets' ] - ) - } + ) +} ], handler: (request: any, reply) => { const args = { @@ -1414,8 +1442,8 @@ export function RegisterRoutes(server: hapi.Server) { pre: [ { method: authenticateMiddleware('api_key' - ) -} + ) + } ], handler: (request: any, reply) => { const args = { diff --git a/tests/fixtures/inversify/routes.ts b/tests/fixtures/inversify/routes.ts index 3849886dd..cffb26a4a 100644 --- a/tests/fixtures/inversify/routes.ts +++ b/tests/fixtures/inversify/routes.ts @@ -6,27 +6,45 @@ import { ManagedController } from './managedController'; const models: any = { 'TestModel': { - 'numberValue': { typeName: 'number', required: true }, - 'numberArray': { typeName: 'array', required: true, arrayType: 'number' }, - 'stringValue': { typeName: 'string', required: true }, - 'stringArray': { typeName: 'array', required: true, arrayType: 'string' }, - 'boolValue': { typeName: 'boolean', required: true }, - 'boolArray': { typeName: 'array', required: true, arrayType: 'boolean' }, - 'modelValue': { typeName: 'TestSubModel', required: true }, - 'modelsArray': { typeName: 'array', required: true, arrayType: 'TestSubModel' }, - 'strLiteralVal': { typeName: 'StrLiteral', required: true }, - 'strLiteralArr': { typeName: 'array', required: true, arrayType: 'StrLiteral' }, - 'dateValue': { typeName: 'datetime', required: false }, - 'optionalString': { typeName: 'string', required: false }, - 'id': { typeName: 'number', required: true }, + properties: { + 'numberValue': { typeName: 'number', required: true }, + 'numberArray': { typeName: 'array', required: true, arrayType: 'number' }, + 'stringValue': { typeName: 'string', required: true }, + 'stringArray': { typeName: 'array', required: true, arrayType: 'string' }, + 'boolValue': { typeName: 'boolean', required: true }, + 'boolArray': { typeName: 'array', required: true, arrayType: 'boolean' }, + 'modelValue': { typeName: 'TestSubModel', required: true }, + 'modelsArray': { typeName: 'array', required: true, arrayType: 'TestSubModel' }, + 'strLiteralVal': { typeName: 'StrLiteral', required: true }, + 'strLiteralArr': { typeName: 'array', required: true, arrayType: 'StrLiteral' }, + 'dateValue': { typeName: 'datetime', required: false }, + 'optionalString': { typeName: 'string', required: false }, + 'modelsObjectIndirect': { typeName: 'TestSubModelContainer', required: false }, + 'id': { typeName: 'number', required: true }, + }, }, 'TestSubModel': { - 'email': { typeName: 'string', required: true }, - 'circular': { typeName: 'TestModel', required: false }, - 'id': { typeName: 'number', required: true }, + properties: { + 'email': { typeName: 'string', required: true }, + 'circular': { typeName: 'TestModel', required: false }, + 'id': { typeName: 'number', required: true }, + }, }, 'StrLiteral': { }, + 'TestSubModel2': { + properties: { + 'testSubModel2': { typeName: 'boolean', required: true }, + 'email': { typeName: 'string', required: true }, + 'circular': { typeName: 'TestModel', required: false }, + 'id': { typeName: 'number', required: true }, + }, + }, + 'TestSubModelContainer': { + additionalProperties: [ + { typeName: '' }, + ], + }, }; diff --git a/tests/fixtures/koa/routes.ts b/tests/fixtures/koa/routes.ts index ef10b35b1..fbf923c94 100644 --- a/tests/fixtures/koa/routes.ts +++ b/tests/fixtures/koa/routes.ts @@ -12,51 +12,79 @@ import { SecurityTestController } from './../controllers/securityController'; const models: any = { 'TestModel': { - 'numberValue': { typeName: 'number', required: true }, - 'numberArray': { typeName: 'array', required: true, arrayType: 'number' }, - 'stringValue': { typeName: 'string', required: true }, - 'stringArray': { typeName: 'array', required: true, arrayType: 'string' }, - 'boolValue': { typeName: 'boolean', required: true }, - 'boolArray': { typeName: 'array', required: true, arrayType: 'boolean' }, - 'modelValue': { typeName: 'TestSubModel', required: true }, - 'modelsArray': { typeName: 'array', required: true, arrayType: 'TestSubModel' }, - 'strLiteralVal': { typeName: 'StrLiteral', required: true }, - 'strLiteralArr': { typeName: 'array', required: true, arrayType: 'StrLiteral' }, - 'dateValue': { typeName: 'datetime', required: false }, - 'optionalString': { typeName: 'string', required: false }, - 'id': { typeName: 'number', required: true }, + properties: { + 'numberValue': { typeName: 'number', required: true }, + 'numberArray': { typeName: 'array', required: true, arrayType: 'number' }, + 'stringValue': { typeName: 'string', required: true }, + 'stringArray': { typeName: 'array', required: true, arrayType: 'string' }, + 'boolValue': { typeName: 'boolean', required: true }, + 'boolArray': { typeName: 'array', required: true, arrayType: 'boolean' }, + 'modelValue': { typeName: 'TestSubModel', required: true }, + 'modelsArray': { typeName: 'array', required: true, arrayType: 'TestSubModel' }, + 'strLiteralVal': { typeName: 'StrLiteral', required: true }, + 'strLiteralArr': { typeName: 'array', required: true, arrayType: 'StrLiteral' }, + 'dateValue': { typeName: 'datetime', required: false }, + 'optionalString': { typeName: 'string', required: false }, + 'modelsObjectIndirect': { typeName: 'TestSubModelContainer', required: false }, + 'id': { typeName: 'number', required: true }, + }, }, 'TestSubModel': { - 'email': { typeName: 'string', required: true }, - 'circular': { typeName: 'TestModel', required: false }, - 'id': { typeName: 'number', required: true }, + properties: { + 'email': { typeName: 'string', required: true }, + 'circular': { typeName: 'TestModel', required: false }, + 'id': { typeName: 'number', required: true }, + }, }, 'StrLiteral': { }, + 'TestSubModel2': { + properties: { + 'testSubModel2': { typeName: 'boolean', required: true }, + 'email': { typeName: 'string', required: true }, + 'circular': { typeName: 'TestModel', required: false }, + 'id': { typeName: 'number', required: true }, + }, + }, + 'TestSubModelContainer': { + additionalProperties: [ + { typeName: '' }, + ], + }, 'TestClassModel': { - 'publicStringProperty': { typeName: 'string', required: true }, - 'optionalPublicStringProperty': { typeName: 'string', required: false }, - 'stringProperty': { typeName: 'string', required: true }, - 'publicConstructorVar': { typeName: 'string', required: true }, - 'optionalPublicConstructorVar': { typeName: 'string', required: false }, - 'id': { typeName: 'number', required: true }, + properties: { + 'publicStringProperty': { typeName: 'string', required: true }, + 'optionalPublicStringProperty': { typeName: 'string', required: false }, + 'stringProperty': { typeName: 'string', required: true }, + 'publicConstructorVar': { typeName: 'string', required: true }, + 'optionalPublicConstructorVar': { typeName: 'string', required: false }, + 'id': { typeName: 'number', required: true }, + }, }, 'Result': { - 'value': { typeName: 'object', required: true }, + properties: { + 'value': { typeName: 'object', required: true }, + }, }, 'ErrorResponseModel': { - 'status': { typeName: 'number', required: true }, - 'message': { typeName: 'string', required: true }, + properties: { + 'status': { typeName: 'number', required: true }, + 'message': { typeName: 'string', required: true }, + }, }, 'ParameterTestModel': { - 'firstname': { typeName: 'string', required: true }, - 'lastname': { typeName: 'string', required: true }, - 'age': { typeName: 'number', required: true }, - 'human': { typeName: 'boolean', required: true }, + properties: { + 'firstname': { typeName: 'string', required: true }, + 'lastname': { typeName: 'string', required: true }, + 'age': { typeName: 'number', required: true }, + 'human': { typeName: 'boolean', required: true }, + }, }, 'UserResponseModel': { - 'id': { typeName: 'number', required: true }, - 'name': { typeName: 'string', required: true }, + properties: { + 'id': { typeName: 'number', required: true }, + 'name': { typeName: 'string', required: true }, + }, }, }; diff --git a/tests/fixtures/testModel.ts b/tests/fixtures/testModel.ts index 54e496b97..d24fc1efb 100644 --- a/tests/fixtures/testModel.ts +++ b/tests/fixtures/testModel.ts @@ -19,17 +19,27 @@ export interface TestModel extends Model { strLiteralArr: StrLiteral[]; dateValue?: Date; optionalString?: string; + // modelsObjectDirect?: {[key: string]: TestSubModel2}; + modelsObjectIndirect?: TestSubModelContainer; } // shortened from StringLiteral to make the tslint enforced // alphabetical sorting cleaner export type StrLiteral = 'Foo' | 'Bar'; +export interface TestSubModelContainer { + [key: string]: TestSubModel2; +} + export interface TestSubModel extends Model { email: string; circular?: TestModel; } +export interface TestSubModel2 extends TestSubModel { + testSubModel2: boolean; +} + export interface BooleanResponseModel { success: boolean; } diff --git a/tests/integration/express-server.spec.ts b/tests/integration/express-server.spec.ts index fc8b0d691..8568e6d71 100644 --- a/tests/integration/express-server.spec.ts +++ b/tests/integration/express-server.spec.ts @@ -312,6 +312,13 @@ describe('Express Server', () => { id: 1, modelValue: { email: 'test@test.com', id: 2 }, modelsArray: [{ email: 'test@test.com', id: 1 }], + modelsObjectIndirect: { + key: { + email: 'test@test.com', + id: 1, + testSubModel2: false + } + }, numberArray: [1, 2], numberValue: 5, optionalString: 'test1234', diff --git a/tests/integration/hapi-server.spec.ts b/tests/integration/hapi-server.spec.ts index ba2b7e4b6..5bb1e91b5 100644 --- a/tests/integration/hapi-server.spec.ts +++ b/tests/integration/hapi-server.spec.ts @@ -299,6 +299,13 @@ describe('Hapi Server', () => { id: 1, modelValue: { email: 'test@test.com', id: 2 }, modelsArray: [{ email: 'test@test.com', id: 1 }], + modelsObjectIndirect: { + key: { + email: 'test@test.com', + id: 1, + testSubModel2: false + } + }, numberArray: [1, 2], numberValue: 5, optionalString: 'test1234', diff --git a/tests/integration/inversify-server.spec.ts b/tests/integration/inversify-server.spec.ts index a0f79d022..f0f42cae4 100644 --- a/tests/integration/inversify-server.spec.ts +++ b/tests/integration/inversify-server.spec.ts @@ -32,6 +32,13 @@ describe('Inversify Express Server', () => { id: 100, }, modelsArray: new Array(), + modelsObjectIndirect: { + key: { + email: 'test@test.com', + id: 1, + testSubModel2: false + } + }, numberArray: [1, 2, 3], numberValue: 1, optionalString: 'optional string', diff --git a/tests/integration/koa-server.spec.ts b/tests/integration/koa-server.spec.ts index dafc12cea..0956807d3 100644 --- a/tests/integration/koa-server.spec.ts +++ b/tests/integration/koa-server.spec.ts @@ -280,6 +280,13 @@ describe('Koa Server', () => { id: 1, modelValue: { email: 'test@test.com', id: 2 }, modelsArray: [{ email: 'test@test.com', id: 1 }], + modelsObjectIndirect: { + key: { + email: 'test@test.com', + id: 1, + testSubModel2: false + } + }, numberArray: [1, 2], numberValue: 5, optionalString: 'test1234', diff --git a/tests/unit/swagger/definitionsGeneration/definitions.spec.ts b/tests/unit/swagger/definitionsGeneration/definitions.spec.ts index a5e0bef33..8f70db054 100644 --- a/tests/unit/swagger/definitionsGeneration/definitions.spec.ts +++ b/tests/unit/swagger/definitionsGeneration/definitions.spec.ts @@ -25,7 +25,7 @@ describe('Definition generation', () => { describe('Interface-based generation', () => { it('should generate a definition for referenced models', () => { - const expectedModels = ['TestModel', 'TestSubModel', 'Result']; + const expectedModels = ['TestModel', 'TestSubModel', 'TestSubModel2', 'Result']; expectedModels.forEach(modelName => { getValidatedDefinition(modelName); });