Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/interfaces/metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export interface PropertyMetadata {
format?: string;
title?: string;
description?: string;
default?: any;
multipleOf?: number;
maximum?: number;
exclusiveMaximum?: boolean;
minimum?: number;
exclusiveMinimum?: boolean;
maxLength?: number;
minLength?: number;
pattern?: string;
maxItems?: number;
minItems?: number;
uniqueItems?: boolean;
maxProperties?: number;
minProperties?: number;
type?: string;
}
2 changes: 2 additions & 0 deletions src/metadataGeneration/metadataGenerator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as ts from 'typescript';
import { ControllerGenerator } from './controllerGenerator';
import {PropertyMetadata} from '../interfaces/metadata';

export class MetadataGenerator {
public static current: MetadataGenerator;
Expand Down Expand Up @@ -121,6 +122,7 @@ export interface Property {
name: string;
type: Type;
required: boolean;
metadata?: PropertyMetadata;
}

export interface ArrayType {
Expand Down
156 changes: 142 additions & 14 deletions src/metadataGeneration/resolveType.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import * as ts from 'typescript';
import { MetadataGenerator, Type, ReferenceType, Property } from './metadataGenerator';
import { MetadataGenerator, Type, ReferenceType, Property, ArrayType } from './metadataGenerator';
import { ValidationGenerator } from './validationGenerator';
import * as _ from 'lodash';

const syntaxKindMap: { [kind: number]: string } = {};
syntaxKindMap[ts.SyntaxKind.NumberKeyword] = 'number';
Expand All @@ -9,6 +11,7 @@ syntaxKindMap[ts.SyntaxKind.VoidKeyword] = 'void';

const localReferenceTypeCache: { [typeName: string]: ReferenceType } = {};
const inProgressTypes: { [typeName: string]: boolean } = {};
const validationGenerator = new ValidationGenerator();

type UsableDeclaration = ts.InterfaceDeclaration | ts.ClassDeclaration | ts.TypeAliasDeclaration;
export function ResolveType(typeNode: ts.TypeNode): Type {
Expand Down Expand Up @@ -40,28 +43,40 @@ export function ResolveType(typeNode: ts.TypeNode): Type {
return ResolveType(typeReference);
}

const referenceType = generateReferenceType(typeReference.typeName.text);
let referenceType: ReferenceType;

if (typeReference.typeArguments && typeReference.typeArguments.length === 1) {
let typeT: ts.TypeNode[] = typeReference.typeArguments as ts.TypeNode[];
referenceType = generateReferenceType(typeReference.typeName.text, typeT);
} else {
referenceType = generateReferenceType(typeReference.typeName.text);
}

MetadataGenerator.current.AddReferenceType(referenceType);
return referenceType;
}

function generateReferenceType(typeName: string): ReferenceType {
function generateReferenceType(typeName: string, genericTypes?: ts.TypeNode[]): ReferenceType {
try {
const existingType = localReferenceTypeCache[typeName];

const typeNameWithGenerics = getTypeName(typeName, genericTypes);
const existingType = localReferenceTypeCache[typeNameWithGenerics];
if (existingType) { return existingType; }

if (inProgressTypes[typeName]) {
return createCircularDependencyResolver(typeName);
if (inProgressTypes[typeNameWithGenerics]) {
return createCircularDependencyResolver(typeNameWithGenerics);
}

inProgressTypes[typeName] = true;
inProgressTypes[typeNameWithGenerics] = true;

const modelTypeDeclaration = getModelTypeDeclaration(typeName);
const properties = getModelTypeProperties(modelTypeDeclaration);


const properties = getModelTypeProperties(modelTypeDeclaration, genericTypes);

const referenceType: ReferenceType = {
description: getModelDescription(modelTypeDeclaration),
name: typeName,
name: typeNameWithGenerics,
properties: properties
};
if (modelTypeDeclaration.kind === ts.SyntaxKind.TypeAliasDeclaration) {
Expand All @@ -75,14 +90,56 @@ function generateReferenceType(typeName: string): ReferenceType {
const extendedProperties = getInheritedProperties(modelTypeDeclaration);
referenceType.properties = referenceType.properties.concat(extendedProperties);

localReferenceTypeCache[typeName] = referenceType;
localReferenceTypeCache[typeNameWithGenerics] = referenceType;

return referenceType;
} catch (err) {
console.error(`There was a problem resolving type of '${typeName}'.`);
console.error(`There was a problem resolving type of '${getTypeName(typeName, genericTypes)}'.`);
throw err;
}
}

function getTypeName(typeName: string, genericTypes?: ts.TypeNode[]): string {
if (!genericTypes || !genericTypes.length) {
return typeName;
}
let names = genericTypes.map((t) => {
return getAnyTypeName(t);
});

return typeName + '<' + names.join(', ') + '>';
}

function getAnyTypeName(typeNode: ts.TypeNode): string {
const primitiveType = syntaxKindMap[typeNode.kind];
if (primitiveType) {
return primitiveType;
}

if (typeNode.kind === ts.SyntaxKind.ArrayType) {
const arrayType = typeNode as ts.ArrayTypeNode;
return getAnyTypeName(arrayType.elementType) + '[]';
}

if (typeNode.kind === ts.SyntaxKind.UnionType) {
return 'object';
}

if (typeNode.kind !== ts.SyntaxKind.TypeReference) {
throw new Error(`Unknown type: ${ts.SyntaxKind[typeNode.kind]}`);
}

const typeReference = typeNode as ts.TypeReferenceNode;
try {
return (typeReference.typeName as ts.Identifier).text;
} catch (e) {
// idk what would hit this? probably needs more testing
console.error(e);
return typeNode.toString();
}

}

function createCircularDependencyResolver(typeName: string) {
const referenceType = {
description: '',
Expand Down Expand Up @@ -127,22 +184,52 @@ function getModelTypeDeclaration(typeName: string) {
return modelTypes[0];
}

function getModelTypeProperties(node: UsableDeclaration) {
function getModelTypeProperties(node: UsableDeclaration, genericTypes?: ts.TypeNode[]) {
if (node.kind === ts.SyntaxKind.InterfaceDeclaration) {
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;
const identifier = propertyDeclaration.name as ts.Identifier;

if (!propertyDeclaration.type) { throw new Error('No valid type found for property declaration.'); }

// Declare a variable that can be overridden if needed
let aType = propertyDeclaration.type;

// aType.kind will always be a TypeReference when the property of Interface<T> is of type T
if (aType.kind === ts.SyntaxKind.TypeReference && genericTypes && genericTypes.length && node.typeParameters) {

// The type definitions are conviently located on the object which allow us to map -> to the genericTypes
let typeParams = _.map(node.typeParameters, (typeParam: ts.TypeParameterDeclaration) => {
return typeParam.name.text;
});

// I am not sure in what cases
const typeIdentifier = (aType as ts.TypeReferenceNode).typeName;
let typeIdentifierName: string;

// typeIdentifier can either be a Identifier or a QualifiedName
if ((typeIdentifier as ts.Identifier).text) {
typeIdentifierName = (typeIdentifier as ts.Identifier).text;
} else {
typeIdentifierName = (typeIdentifier as ts.QualifiedName).right.text;
}

// I could not produce a situation where this did not find it so its possible this check is irrelevant
const indexOfType = _.indexOf<string>(typeParams, typeIdentifierName);
if (indexOfType >= 0) {
aType = genericTypes[indexOfType] as ts.TypeNode;
}
}

return {
description: getNodeDescription(propertyDeclaration),
name: identifier.text,
required: !property.questionToken,
type: ResolveType(propertyDeclaration.type)
type: ResolveType(aType)
};
});
}
Expand Down Expand Up @@ -176,16 +263,57 @@ function getModelTypeProperties(node: UsableDeclaration) {
const identifier = declaration.name as ts.Identifier;

if (!declaration.type) { throw new Error('No valid type found for property declaration.'); }
const decorators = getPropertyInfo(declaration);

const type = ResolveType(declaration.type);
let propType = '';

if (typeof type === 'string') {
propType = type;
} else if ((type as ArrayType).elementType) {
propType = 'array';
}

let metadata;
if (decorators && propType) {
metadata = validationGenerator.getProperties(decorators, propType);
}
return {
description: getNodeDescription(declaration),
metadata: metadata,
name: identifier.text,
required: !declaration.questionToken,
type: ResolveType(declaration.type)
type: type
};
});
}

function getPropertyInfo(prop: ts.PropertyDeclaration | ts.ParameterDeclaration) {
if (prop.decorators) {
console.error('I found a property with decorators!');
return prop.decorators
.map(d => d.expression as ts.CallExpression)
.map(e => {
let decoratorName = (e.expression as ts.Identifier).text;
let args = e.arguments.map((arg) => {
if (arg) {
return (arg as ts.Identifier).text;
}
return undefined;
})
.filter((val) => val !== undefined);
console.error('found decorator: ', decoratorName);
console.error('args: ', args);
return {
args: args,
name: decoratorName
};
});
} else {
return null;
}
}

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
109 changes: 109 additions & 0 deletions src/metadataGeneration/validationGenerator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import {PropertyMetadata} from '../interfaces/metadata';
import * as _ from 'lodash';

export interface DecorationConfig {
name: string;
args: any[];
}

interface ValidationConfig {
[type: string]: ValidationConfigDeclaration;
}

interface ValidationFunction {
(...args: any[]): PropertyMetadata;
}

interface ValidationConfigDeclaration {
[type: string]: PropertyMetadata | ValidationFunction;
}

const internalMapping: ValidationConfig = {
array: {
arraymaxsize: (i) => {
return {
maxItems: parseInt(i, 10)
};
},
arrayminsize: (i) => {
return {
minItems: parseInt(i, 10)
};
}
},
number: {
isint: {
format: 'int64',
type: 'integer'
},
isnegative: {
exclusiveMaximum: true,
maximum: 0,
},
ispositive: {
exclusiveMinimum: true,
minimum: 0,
},
max: (i) => {
return {
maximum: parseFloat(i)
};
},
min: (i) => {
return {
maximum: parseFloat(i)
};
}
},
string: {
fake: {
minimum: 0
},
fakeme: (i) => {
return {
maximum: parseInt(i, 10) + 99
};
},
maxlength: (i) => {
return {
maxLength: parseInt(i, 10)
};
},
minlength: (i) => {
return {
minLength: parseInt(i, 10)
};
}
}
};

export class ValidationGenerator {
private config: ValidationConfig;
constructor() {
this.config = internalMapping;
}

public getProperties(decorators: DecorationConfig[], type: string) {
let configType = this.config[type];

if (!configType) { return {}; }

let ret = {};

decorators.map((dec) => {
let decName = dec.name.toLowerCase();
if (!configType[decName]) {
return;
}
let combine: Object;
if (typeof configType[decName] === 'function') {
combine = (configType[decName] as ValidationFunction).apply(null, dec.args);
} else {
combine = configType[decName] as PropertyMetadata;
}
_.assign(ret, combine);
});

return ret;
}
}
2 changes: 1 addition & 1 deletion src/routeGeneration/routeGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export class RouteGenerator {
},
{{/each}}
};
`.concat(middlewareTemplate));
`.concat(middlewareTemplate), { noEscape: true });

const authenticationModule = this.options.authenticationModule ? this.getRelativeImportPath(this.options.authenticationModule) : undefined;
const iocModule = this.options.iocModule ? this.getRelativeImportPath(this.options.iocModule) : undefined;
Expand Down
Loading