typescript-swagger
Version:
Generate Swagger files from a decorator library like typescript-rest or a @decorators/express.
203 lines (157 loc) • 8.05 kB
text/typescript
'use strict';
import {castArray} from 'lodash';
import {ArrayLiteralExpression, isArrayLiteralExpression, Node, SyntaxKind} from 'typescript';
import {useDebugger} from "../debug";
import {Decorator} from "../decorator/type";
import { getDecorators } from '../decorator/utils';
import {normalizePath} from "../utils/pathUtils";
import {MetadataGenerator, ResponseType} from './metadataGenerator';
import {TypeNodeResolver} from './resolver';
export abstract class EndpointGenerator<T extends Node> {
protected path: string | undefined;
protected node: T;
protected debugger = useDebugger();
// -------------------------------------------
protected constructor(node: T, protected current: MetadataGenerator) {
this.node = node;
}
// -------------------------------------------
// --------------------------------------------------------------------
protected generatePath(key: Decorator.ID) {
const values : Array<string> = [];
const handler = Decorator.getRepresentationHandler(key, this.current.decoratorMap);
const config = handler.buildRepresentationConfigFromNode(this.node);
const property = handler.getPropertyByType(config.name);
if(typeof property !== 'undefined') {
const argument = handler.getDecoratorPropertyValueAsItem(config.decorator, property);
if (typeof argument !== 'undefined') {
values.push(argument);
}
}
this.path = normalizePath(values.join('/'));
}
// --------------------------------------------------------------------
protected getDecoratorValues(decoratorName: string, acceptMultiple: boolean = false) : Array<any> {
const decorators = getDecorators(this.node, decorator => decorator.text === decoratorName);
if (!decorators || !decorators.length) { return []; }
if (!acceptMultiple && decorators.length > 1) {
throw new Error(`Only one ${decoratorName} decorator allowed in ${this.getCurrentLocation()}.`);
}
let result: Array<any>;
if (acceptMultiple) {
result = decorators.map(d => d.arguments);
} else {
const d = decorators[0];
result = d.arguments;
}
this.debugger('Arguments of decorator %s: %j', decoratorName, result);
return result;
}
// -------------------------------------------
protected getSecurity() {
const securities = this.getDecoratorValues('Security', true);
if (!securities || !securities.length) { return undefined; }
return securities.map(security => ({
name: security[1] ? security[1] : 'default',
scopes: security[0] ? castArray(this.handleRolesArray(security[0])) : []
}));
}
protected handleRolesArray(argument: ArrayLiteralExpression): Array<string> {
if (isArrayLiteralExpression(argument)) {
return argument.elements.map(value => value.getText())
.map(val => (val && val.startsWith('\'') && val.endsWith('\'')) ? val.slice(1, -1) : val);
} else {
return argument;
}
}
// -------------------------------------------
protected getExamplesValue(argument: any) {
let example: any = {};
if(typeof argument === 'undefined') {
return example;
}
this.debugger(argument);
if (argument.properties) {
argument.properties.forEach((p: any) => {
example[p.name.text] = this.getInitializerValue(p.initializer);
});
} else {
example = this.getInitializerValue(argument);
}
this.debugger('Example extracted for %s: %j', this.getCurrentLocation(), example);
return example;
}
protected getInitializerValue(initializer: any) {
switch (initializer.kind as SyntaxKind) {
case SyntaxKind.ArrayLiteralExpression:
return initializer.elements.map((e: any) => this.getInitializerValue(e));
case SyntaxKind.StringLiteral:
return initializer.text;
case SyntaxKind.TrueKeyword:
return true;
case SyntaxKind.FalseKeyword:
return false;
case SyntaxKind.NumberKeyword:
case SyntaxKind.FirstLiteralToken:
return parseInt(initializer.text, 10);
case SyntaxKind.ObjectLiteralExpression:
const nestedObject: any = {};
initializer.properties.forEach((p: any) => {
nestedObject[p.name.text] = this.getInitializerValue(p.initializer);
});
return nestedObject;
default:
return undefined;
}
}
// -------------------------------------------
protected getResponses(): Array<ResponseType> {
const handler = Decorator.getRepresentationHandler('RESPONSE_DESCRIPTION', this.current.decoratorMap);
const config = handler.buildRepresentationConfigFromNode(this.node);
const args = handler.getPropertiesByTypes(config.name, ['STATUS_CODE', 'DESCRIPTION', 'PAYLOAD', 'TYPE']);
if (!config.decorators || !config.decorators.length) { return []; }
this.debugger('Generating Responses for %s', this.getCurrentLocation());
return config.decorators.map(decorator => {
const description = handler.getDecoratorPropertyValueAsItem(decorator, args['DESCRIPTION']) || 'Ok';
const status = handler.getDecoratorPropertyValueAsItem(decorator, args['STATUS_CODE']) || '200';
let examples = handler.getDecoratorPropertyValueAsItem(decorator, args['PAYLOAD']);
if(typeof examples !== 'undefined') {
examples = this.getExamplesValue(examples);
}
const type = handler.getDecoratorPropertyValueAsItem(decorator, args['TYPE']);
const responses = {
description: description,
examples: examples,
schema: type ? new TypeNodeResolver(type, this.current).resolve() : undefined,
status: status
};
this.debugger('Generated Responses for %s: %j', this.getCurrentLocation(), responses);
return responses;
});
}
// -------------------------------------------
public getProduces() {
const handler = Decorator.getRepresentationHandler('RESPONSE_PRODUCES', this.current.decoratorMap);
const config = handler.buildRepresentationConfigFromNode(this.node);
const produces = handler.getDecoratorPropertyValueAsArray(config.decorator, handler.getPropertyByType(config.name));
if(typeof produces === 'undefined' || produces.length === 0) {
const acceptHandler = Decorator.getRepresentationHandler('REQUEST_ACCEPT', this.current.decoratorMap);
const acceptConfig = acceptHandler.buildRepresentationConfigFromNode(this.node);
return acceptHandler.getDecoratorPropertyValueAsArray(acceptConfig.decorator, acceptHandler.getPropertyByType(acceptConfig.name));
}
return produces;
}
public getConsumes() {
const handler = Decorator.getRepresentationHandler('REQUEST_CONSUMES', this.current.decoratorMap);
const config = handler.buildRepresentationConfigFromNode(this.node);
return handler.getDecoratorPropertyValueAsArray(config.decorator, handler.getPropertyByType(config.name));
}
public getTags() {
const handler = Decorator.getRepresentationHandler('SWAGGER_TAGS', this.current.decoratorMap);
const config = handler.buildRepresentationConfigFromNode(this.node);
return handler.getDecoratorPropertyValueAsArray(config.decorator, handler.getPropertyByType(config.name));
}
// -------------------------------------------
protected abstract getCurrentLocation(): string;
// -------------------------------------------
}