Skip to content

Commit

Permalink
Fix and update
Browse files Browse the repository at this point in the history
- update route schema and update type
- change swagger enum inline type to enum definitions
- add new rule for tslint
- fix set status not work lukeautry#94
- support custom response header lukeautry#89
  • Loading branch information
isman-usoh committed Jul 21, 2017
1 parent bdcaa06 commit 79f0dfc
Show file tree
Hide file tree
Showing 31 changed files with 1,982 additions and 2,987 deletions.
12 changes: 2 additions & 10 deletions src/decorators/methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,10 @@ export function Put(value?: string): any {
return () => { return; };
}

export function Delete(value?: string): any {
return () => { return; };
}

export function Options(value?: string): any {
return () => { return; };
}

export function Head(value?: string): any {
export function Patch(value?: string): any {
return () => { return; };
}

export function Patch(value?: string): any {
export function Delete(value?: string): any {
return () => { return; };
}
19 changes: 16 additions & 3 deletions src/interfaces/controller.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@

export class Controller {
public statusCode?: number = undefined;
private statusCode?: number = undefined;
private headers = {} as { [name: string]: string | undefined };

public setStatus(statusCode: number) {
this.statusCode = statusCode;
}

public getStatus() {
return this.statusCode;
}

public setStatus(statusCode: number) {
this.statusCode = statusCode;
public setHeader(name: string, value?: string) {
this.headers[name] = value;
}

public getHeader(name: string) {
return this.headers[name];
}

public getHeaders() {
return this.headers;
}
}
10 changes: 7 additions & 3 deletions src/metadataGeneration/controllerGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@ export class ControllerGenerator {
}

public Generate(): Tsoa.Controller {
if (!this.node.parent) { throw new GenerateMetadataError(this.node, 'Controller node doesn\'t have a valid parent source file.'); }
if (!this.node.name) { throw new GenerateMetadataError(this.node, 'Controller node doesn\'t have a valid name.'); }
if (!this.node.parent) {
throw new GenerateMetadataError('Controller node doesn\'t have a valid parent source file.');
}
if (!this.node.name) {
throw new GenerateMetadataError('Controller node doesn\'t have a valid name.');
}

const sourceFile = this.node.parent.getSourceFile();

Expand Down Expand Up @@ -52,7 +56,7 @@ export class ControllerGenerator {

if (!matchedAttributes.length) { return undefined; }
if (matchedAttributes.length > 1) {
throw new GenerateMetadataError(this.node, `A controller can only have a single 'decoratorName' decorator in \`${(this.node.name as any).text}\` class.`);
throw new GenerateMetadataError(`A controller can only have a single 'decoratorName' decorator in \`${(this.node.name as any).text}\` class.`);
}

const value = matchedAttributes[0].arguments[0] as ts.StringLiteral;
Expand Down
8 changes: 5 additions & 3 deletions src/metadataGeneration/exceptions.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import * as ts from 'typescript';

export class GenerateMetadataError extends Error {
constructor(node: ts.Node, message: string) {
super();
this.message = `${message}\n in: ${getSourceFile(node)}`;
constructor(message?: string, node?: ts.Node) {
super(message);
if (node) {
this.message = `${message}\n in: ${getSourceFile(node)}`;
}
}
}

function getSourceFile(node: ts.Node): string {
Expand Down
21 changes: 12 additions & 9 deletions src/metadataGeneration/metadataGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ export class MetadataGenerator {
public readonly nodes = new Array<ts.Node>();
public readonly typeChecker: ts.TypeChecker;
private readonly program: ts.Program;
private referenceTypes: { [typeName: string]: Tsoa.ReferenceType } = {};
private circularDependencyResolvers = new Array<(referenceTypes: { [typeName: string]: Tsoa.ReferenceType }) => void>();
private referenceTypeMap: Tsoa.ReferenceTypeMap = {};
private circularDependencyResolvers = new Array<(referenceTypes: Tsoa.ReferenceTypeMap) => void>();

public IsExportedNode(node: ts.Node) { return true; }

Expand All @@ -27,11 +27,11 @@ export class MetadataGenerator {

const controllers = this.buildControllers();

this.circularDependencyResolvers.forEach(c => c(this.referenceTypes));
this.circularDependencyResolvers.forEach(c => c(this.referenceTypeMap));

return {
Controllers: controllers,
ReferenceTypes: this.referenceTypes,
controllers,
referenceTypeMap: this.referenceTypeMap,
};
}

Expand All @@ -40,14 +40,17 @@ export class MetadataGenerator {
}

public AddReferenceType(referenceType: Tsoa.ReferenceType) {
this.referenceTypes[referenceType.typeName] = referenceType;
if (!referenceType.refName) {
return;
}
this.referenceTypeMap[referenceType.refName] = referenceType;
}

public GetReferenceType(typeName: string) {
return this.referenceTypes[typeName];
public GetReferenceType(refName: string) {
return this.referenceTypeMap[refName];
}

public OnFinish(callback: (referenceTypes: { [typeName: string]: Tsoa.ReferenceType }) => void) {
public OnFinish(callback: (referenceTypes: Tsoa.ReferenceTypeMap) => void) {
this.circularDependencyResolvers.push(callback);
}

Expand Down
48 changes: 30 additions & 18 deletions src/metadataGeneration/methodGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { getDecorators, getInitializerValue } from './../utils/decoratorUtils';
import { GenerateMetadataError } from './exceptions';

export class MethodGenerator {
private method: 'get' | 'post' | 'put' | 'delete' | 'options' | 'head' | 'patch';
private method: 'get' | 'post' | 'put' | 'patch' | 'delete';
private path: string;

constructor(private readonly node: ts.MethodDeclaration) {
Expand All @@ -19,8 +19,12 @@ export class MethodGenerator {
}

public Generate(): Tsoa.Method {
if (!this.IsValid()) { throw new GenerateMetadataError(this.node, 'This isn\'t a valid a controller method.'); }
if (!this.node.type) { throw new GenerateMetadataError(this.node, 'Controller methods must have a return type.'); }
if (!this.IsValid()) {
throw new GenerateMetadataError('This isn\'t a valid a controller method.');
}
if (!this.node.type) {
throw new GenerateMetadataError('Controller methods must have a return type.');
}

const identifier = this.node.name as ts.Identifier;
const type = ResolveType(this.node.type);
Expand Down Expand Up @@ -50,18 +54,18 @@ export class MethodGenerator {
const methodId = this.node.name as ts.Identifier;
const controllerId = (this.node.parent as ts.ClassDeclaration).name as ts.Identifier;
const parameterId = p.name as ts.Identifier;
throw new GenerateMetadataError(this.node, `Error generate parameter method: '${controllerId.text}.${methodId.text}' argument: ${parameterId.text} ${e}`);
throw new GenerateMetadataError(`Error generate parameter method: '${controllerId.text}.${methodId.text}' argument: ${parameterId.text} ${e}`);
}
});

const bodyParameters = parameters.filter(p => p.in === 'body');
const bodyProps = parameters.filter(p => p.in === 'body-prop');

if (bodyParameters.length > 1) {
throw new GenerateMetadataError(this.node, `Only one body parameter allowed in '${this.getCurrentLocation()}' method.`);
throw new GenerateMetadataError(`Only one body parameter allowed in '${this.getCurrentLocation()}' method.`);
}
if (bodyParameters.length > 0 && bodyProps.length > 0) {
throw new GenerateMetadataError(this.node, `Choose either during @Body or @BodyProp in '${this.getCurrentLocation()}' method.`);
throw new GenerateMetadataError(`Choose either during @Body or @BodyProp in '${this.getCurrentLocation()}' method.`);
}
return parameters;
}
Expand All @@ -77,7 +81,7 @@ export class MethodGenerator {

if (!pathDecorators || !pathDecorators.length) { return; }
if (pathDecorators.length > 1) {
throw new GenerateMetadataError(this.node, `Only one path decorator in '${this.getCurrentLocation}' method, Found: ${pathDecorators.map(d => d.text).join(', ')}`);
throw new GenerateMetadataError(`Only one path decorator in '${this.getCurrentLocation}' method, Found: ${pathDecorators.map(d => d.text).join(', ')}`);
}

const decorator = pathDecorators[0];
Expand All @@ -93,7 +97,9 @@ export class MethodGenerator {

private getMethodResponses(): Tsoa.Response[] {
const decorators = getDecorators(this.node, identifier => identifier.text === 'Response');
if (!decorators || !decorators.length) { return []; }
if (!decorators || !decorators.length) {
return [];
}

return decorators.map(decorator => {
const expression = decorator.parent as ts.CallExpression;
Expand Down Expand Up @@ -127,14 +133,14 @@ export class MethodGenerator {
const decorators = getDecorators(this.node, identifier => identifier.text === 'SuccessResponse');
if (!decorators || !decorators.length) {
return {
description: type.typeName === 'void' ? 'No content' : 'Ok',
description: type.dataType === 'void' ? 'No content' : 'Ok',
examples: this.getMethodSuccessExamples(),
name: type.typeName === 'void' ? '204' : '200',
name: type.dataType === 'void' ? '204' : '200',
schema: type,
};
}
if (decorators.length > 1) {
throw new GenerateMetadataError(this.node, `Only one SuccessResponse decorator allowed in '${this.getCurrentLocation}' method.`);
throw new GenerateMetadataError(`Only one SuccessResponse decorator allowed in '${this.getCurrentLocation}' method.`);
}

const decorator = decorators[0];
Expand All @@ -161,9 +167,11 @@ export class MethodGenerator {

private getMethodSuccessExamples() {
const exampleDecorators = getDecorators(this.node, identifier => identifier.text === 'Example');
if (!exampleDecorators || !exampleDecorators.length) { return undefined; }
if (!exampleDecorators || !exampleDecorators.length) {
return undefined;
}
if (exampleDecorators.length > 1) {
throw new GenerateMetadataError(this.node, `Only one Example decorator allowed in '${this.getCurrentLocation}' method.`);
throw new GenerateMetadataError(`Only one Example decorator allowed in '${this.getCurrentLocation}' method.`);
}

const decorator = exampleDecorators[0];
Expand All @@ -174,7 +182,7 @@ export class MethodGenerator {
}

private supportsPathMethod(method: string) {
return ['get', 'post', 'patch', 'delete', 'put'].some(m => m === method.toLowerCase());
return ['get', 'post', 'put', 'patch', 'delete'].some(m => m === method.toLowerCase());
}

private getExamplesValue(argument: any) {
Expand All @@ -187,9 +195,11 @@ export class MethodGenerator {

private getMethodTags() {
const tagsDecorators = getDecorators(this.node, identifier => identifier.text === 'Tags');
if (!tagsDecorators || !tagsDecorators.length) { return []; }
if (!tagsDecorators || !tagsDecorators.length) {
return [];
}
if (tagsDecorators.length > 1) {
throw new GenerateMetadataError(this.node, `Only one Tags decorator allowed in '${this.getCurrentLocation}' method.`);
throw new GenerateMetadataError(`Only one Tags decorator allowed in '${this.getCurrentLocation}' method.`);
}

const decorator = tagsDecorators[0];
Expand All @@ -200,9 +210,11 @@ export class MethodGenerator {

private getMethodSecurity() {
const securityDecorators = getDecorators(this.node, identifier => identifier.text === 'Security');
if (!securityDecorators || !securityDecorators.length) { return undefined; }
if (!securityDecorators || !securityDecorators.length) {
return undefined;
}
if (securityDecorators.length > 1) {
throw new GenerateMetadataError(this.node, `Only one Security decorator allowed in '${this.getCurrentLocation}' method.`);
throw new GenerateMetadataError(`Only one Security decorator allowed in '${this.getCurrentLocation}' method.`);
}

const decorator = securityDecorators[0];
Expand Down
32 changes: 16 additions & 16 deletions src/metadataGeneration/parameterGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export class ParameterGenerator {
in: 'request',
name: parameterName,
required: !parameter.questionToken,
type: { typeName: 'object' },
type: { dataType: 'object' },
parameterName,
validators: getParameterValidators(this.parameter, parameterName),
};
Expand All @@ -58,7 +58,7 @@ export class ParameterGenerator {
const type = this.getValidatedType(parameter);

if (!this.supportBodyMethod(this.method)) {
throw new GenerateMetadataError(parameter, `Body can't support '${this.getCurrentLocation()}' method.`);
throw new GenerateMetadataError(`Body can't support '${this.getCurrentLocation()}' method.`);
}

return {
Expand All @@ -77,7 +77,7 @@ export class ParameterGenerator {
const type = this.getValidatedType(parameter);

if (!this.supportBodyMethod(this.method)) {
throw new GenerateMetadataError(parameter, `Body can't support ${this.method} method`);
throw new GenerateMetadataError(`Body can't support ${this.method} method`);
}

return {
Expand All @@ -93,10 +93,10 @@ export class ParameterGenerator {

private getHeaderParameter(parameter: ts.ParameterDeclaration): Tsoa.Parameter {
const parameterName = (parameter.name as ts.Identifier).text;
const type = this.getValidatedType(parameter);
const type = this.getValidatedType(parameter, false);

if (!this.supportPathDataType(type)) {
throw new GenerateMetadataError(parameter, `Parameter '${parameterName}' can't be passed as a header parameter in '${this.getCurrentLocation()}'.`);
throw new GenerateMetadataError(`Parameter '${parameterName}' can't be passed as a header parameter in '${this.getCurrentLocation()}'.`);
}

return {
Expand All @@ -112,17 +112,17 @@ export class ParameterGenerator {

private getQueryParameter(parameter: ts.ParameterDeclaration): Tsoa.Parameter {
const parameterName = (parameter.name as ts.Identifier).text;
const type = this.getValidatedType(parameter);
const type = this.getValidatedType(parameter, false);

if (type.typeName === 'array') {
if (type.dataType === 'array') {
const arrayType = type as Tsoa.ArrayType;

if (!this.supportPathDataType(arrayType.elementType)) {
throw new GenerateMetadataError(parameter, `Parameter '${parameterName}' can't be passed array as a query parameter in '${this.getCurrentLocation()}'.`);
throw new GenerateMetadataError(`Parameter '${parameterName}' can't be passed array as a query parameter in '${this.getCurrentLocation()}'.`);
}
} else {
if (!this.supportPathDataType(type)) {
throw new GenerateMetadataError(parameter, `Parameter '${parameterName}' can't be passed as a query parameter in '${this.getCurrentLocation()}'.`);
throw new GenerateMetadataError(`Parameter '${parameterName}' can't be passed as a query parameter in '${this.getCurrentLocation()}'.`);
}
}

Expand All @@ -139,14 +139,14 @@ export class ParameterGenerator {

private getPathParameter(parameter: ts.ParameterDeclaration): Tsoa.Parameter {
const parameterName = (parameter.name as ts.Identifier).text;
const type = this.getValidatedType(parameter);
const type = this.getValidatedType(parameter, false);
const pathName = getDecoratorTextValue(this.parameter, ident => ident.text === 'Path') || parameterName;

if (!this.supportPathDataType(type)) {
throw new GenerateMetadataError(parameter, `Parameter '${parameterName}:${type}' can't be passed as a path parameter in '${this.getCurrentLocation()}'.`);
throw new GenerateMetadataError(`Parameter '${parameterName}:${type}' can't be passed as a path parameter in '${this.getCurrentLocation()}'.`);
}
if (!this.path.includes(`{${pathName}}`)) {
throw new GenerateMetadataError(parameter, `Parameter '${parameterName}' can't match in path: '${this.path}'`);
throw new GenerateMetadataError(`Parameter '${parameterName}' can't match in path: '${this.path}'`);
}

return {
Expand Down Expand Up @@ -179,13 +179,13 @@ export class ParameterGenerator {
}

private supportPathDataType(parameterType: Tsoa.Type) {
return ['string', 'integer', 'long', 'float', 'double', 'date', 'datetime', 'buffer', 'boolean', 'enum'].find(t => t === parameterType.typeName);
return ['string', 'integer', 'long', 'float', 'double', 'date', 'datetime', 'buffer', 'boolean', 'enum'].find(t => t === parameterType.dataType);
}

private getValidatedType(parameter: ts.ParameterDeclaration) {
private getValidatedType(parameter: ts.ParameterDeclaration, extractEnum = true) {
if (!parameter.type) {
throw new GenerateMetadataError(parameter, `Parameter ${parameter.name} doesn't have a valid type assigned in '${this.getCurrentLocation()}'.`);
throw new GenerateMetadataError(`Parameter ${parameter.name} doesn't have a valid type assigned in '${this.getCurrentLocation()}'.`);
}
return ResolveType(parameter.type);
return ResolveType(parameter.type, extractEnum);
}
}

0 comments on commit 79f0dfc

Please sign in to comment.