Skip to content

Commit

Permalink
Hashmap support
Browse files Browse the repository at this point in the history
  • Loading branch information
alexey-pelykh committed Mar 11, 2017
1 parent 36358f6 commit 48edeb7
Show file tree
Hide file tree
Showing 15 changed files with 523 additions and 227 deletions.
1 change: 1 addition & 0 deletions src/metadataGeneration/metadataGenerator.ts
Expand Up @@ -110,6 +110,7 @@ export interface EnumerateType extends Type {
export interface ReferenceType extends Type {
description: string;
properties: Property[];
additionalProperties?: Property[];
}

export interface ArrayType extends Type {
Expand Down
33 changes: 30 additions & 3 deletions src/metadataGeneration/resolveType.ts
Expand Up @@ -183,12 +183,16 @@ function getReferenceType(type: ts.EntityName, genericTypes?: ts.TypeNode[]): Re
const modelTypeDeclaration = getModelTypeDeclaration(type);

const properties = getModelTypeProperties(modelTypeDeclaration, genericTypes);
const additionalProperties = getModelTypeAdditionalProperties(modelTypeDeclaration);

const referenceType: ReferenceType = {
description: getModelDescription(modelTypeDeclaration),
properties: properties,
typeName: typeNameWithGenerics,
};
if (additionalProperties && additionalProperties.length) {
referenceType.additionalProperties = additionalProperties;
}

const extendedProperties = getInheritedProperties(modelTypeDeclaration);
referenceType.properties = referenceType.properties.concat(extendedProperties);
Expand Down Expand Up @@ -340,9 +344,9 @@ function getModelTypeProperties(node: UsableDeclaration, genericTypes?: ts.TypeN
const interfaceDeclaration = node as ts.InterfaceDeclaration;
return interfaceDeclaration.members
.filter(member => member.kind === ts.SyntaxKind.PropertySignature)
.map((property: any) => {
.map((member: any) => {

const propertyDeclaration = property as ts.PropertyDeclaration;
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.'); }
Expand Down Expand Up @@ -379,7 +383,7 @@ function getModelTypeProperties(node: UsableDeclaration, genericTypes?: ts.TypeN
return {
description: getNodeDescription(propertyDeclaration),
name: identifier.text,
required: !property.questionToken,
required: !propertyDeclaration.questionToken,
type: ResolveType(aType)
};
});
Expand Down Expand Up @@ -424,6 +428,29 @@ function getModelTypeProperties(node: UsableDeclaration, genericTypes?: ts.TypeN
});
}

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(<ts.TypeNode>indexSignatureDeclaration.parameters[0].type);
if (indexType.typeName !== 'string') { throw new Error('Only string indexers are supported'); }

return {
description: '',
name: '',
required: true,
type: ResolveType(<ts.TypeNode>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;
Expand Down
36 changes: 32 additions & 4 deletions src/routeGeneration/routeGenerator.ts
Expand Up @@ -75,9 +75,20 @@ export class RouteGenerator {
const models: any = {
{{#each models}}
"{{name}}": {
{{#each properties}}
"{{@key}}": {{{json this}}},
{{/each}}
{{#if properties}}
properties: {
{{#each properties}}
"{{@key}}": {{{json this}}},
{{/each}}
},
{{/if}}
{{#if additionalProperties}}
additionalProperties: [
{{#each additionalProperties}}
{typeName: '{{typeName}}'},
{{/each}}
],
{{/if}}
},
{{/each}}
};
Expand Down Expand Up @@ -127,10 +138,14 @@ export class RouteGenerator {
properties[property.name] = this.getPropertySchema(property);
});

return {
const templateModel: TemplateModel = {
name: key,
properties
};
if (referenceType.additionalProperties && referenceType.additionalProperties.length) {
templateModel.additionalProperties = referenceType.additionalProperties.map(property => this.getTemplateAdditionalProperty(property));
}
return templateModel;
});
}

Expand Down Expand Up @@ -165,6 +180,14 @@ export class RouteGenerator {
return templateProperty;
}

private getTemplateAdditionalProperty(source: Property): TemplateAdditionalProperty {
const templateAdditionalProperty: TemplateAdditionalProperty = {
typeName: source.type.typeName
};

return templateAdditionalProperty;
}

private getParameterSchema(parameter: Parameter): ParameterSchema {
const parameterSchema: ParameterSchema = {
in: parameter.in,
Expand Down Expand Up @@ -197,6 +220,7 @@ export class RouteGenerator {
interface TemplateModel {
name: string;
properties: { [name: string]: PropertySchema };
additionalProperties?: TemplateAdditionalProperty[];
}

interface PropertySchema {
Expand All @@ -207,6 +231,10 @@ interface PropertySchema {
enumMembers?: string[];
}

interface TemplateAdditionalProperty {
typeName: string;
}

export interface ArraySchema {
typeName: string;
enumMembers?: string[];
Expand Down
28 changes: 24 additions & 4 deletions src/routeGeneration/templateHelpers.ts
Expand Up @@ -103,10 +103,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 = null;
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 to validate ${key}`);
}
});
}
}

return modelValue;
Expand Down
16 changes: 16 additions & 0 deletions src/swagger/specGenerator.ts
Expand Up @@ -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.typeName].additionalProperties = this.buildAdditionalProperties(referenceType.additionalProperties);
}
});

return definitions;
Expand Down Expand Up @@ -176,6 +179,19 @@ export class SpecGenerator {
return swaggerProperties;
}

private buildAdditionalProperties(properties: Property[]) {
const 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 = {};

Expand Down
2 changes: 1 addition & 1 deletion src/swagger/swagger.ts
Expand Up @@ -136,7 +136,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;
Expand Down

0 comments on commit 48edeb7

Please sign in to comment.