Skip to content

Commit

Permalink
feat: add error response decorator
Browse files Browse the repository at this point in the history
  • Loading branch information
lit26 committed Feb 13, 2024
1 parent feca721 commit d9e0454
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 5 deletions.
29 changes: 29 additions & 0 deletions lib/plugin/utils/ast-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,35 @@ export function getTsDocTagsOfNode(node: Node, typeChecker: TypeChecker) {
return tagResults;
}

export function getTsDocReturnsOrErrorOfNode(node: Node) {
const tsdocParser: TSDocParser = new TSDocParser();
const parserContext: ParserContext = tsdocParser.parseString(
node.getFullText()
);
const docComment: DocComment = parserContext.docComment;

const tagResults = [];
const introspectTsDocTags = (docComment: DocComment) => {
const blocks = docComment.customBlocks.filter((block) =>
['@throws', '@returns'].includes(block.blockTag.tagName)
);

blocks.forEach((block) => {
try {
const docValue = renderDocNode(block.content).split('\n')[0].trim();
const regex = /{(\d+)} (.*)/;
const match = docValue.match(regex);
tagResults.push({
status: match[1],
description: `"${match[2]}"`
});
} catch (err) {}
});
};
introspectTsDocTags(docComment);
return tagResults;
}

export function getDecoratorArguments(decorator: Decorator) {
const callExpression = decorator.expression;
return (callExpression && (callExpression as CallExpression).arguments) || [];
Expand Down
100 changes: 95 additions & 5 deletions lib/plugin/visitors/controller-class.visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
getDecoratorArguments,
getDecoratorName,
getMainCommentOfNode,
getTsDocReturnsOrErrorOfNode,
getTsDocTagsOfNode
} from '../utils/ast-utils';
import {
Expand Down Expand Up @@ -139,14 +140,34 @@ export class ControllerClassVisitor extends AbstractFileVisitor {
typeChecker,
metadata
);

const apiResponseDecoratorsArray = this.createApiResponseDecorator(
factory,
compilerNode,
decorators,
options,
sourceFile,
typeChecker,
metadata
);

const removeExistingApiOperationDecorator =
apiOperationDecoratorsArray.length > 0;

const existingDecorators = removeExistingApiOperationDecorator
? decorators.filter(
(item) => getDecoratorName(item) !== ApiOperation.name
)
: decorators;
const removeExistingApiResponseDecorator =
apiResponseDecoratorsArray.length > 0;

let existingDecorators = decorators;
if (
removeExistingApiOperationDecorator ||
removeExistingApiResponseDecorator
) {
existingDecorators = decorators.filter(
(item) =>
getDecoratorName(item) !== ApiOperation.name &&
getDecoratorName(item) !== ApiResponse.name
);
}

const modifiers = ts.getModifiers(compilerNode) ?? [];
const objectLiteralExpr = this.createDecoratorObjectLiteralExpr(
Expand All @@ -160,6 +181,7 @@ export class ControllerClassVisitor extends AbstractFileVisitor {
);
const updatedDecorators = [
...apiOperationDecoratorsArray,
...apiResponseDecoratorsArray,
...existingDecorators,
factory.createDecorator(
factory.createCallExpression(
Expand Down Expand Up @@ -302,6 +324,74 @@ export class ControllerClassVisitor extends AbstractFileVisitor {
}
}

createApiResponseDecorator(
factory: ts.NodeFactory,
node: ts.MethodDeclaration,
decorators: readonly ts.Decorator[],
options: PluginOptions,
sourceFile: ts.SourceFile,
typeChecker: ts.TypeChecker,
metadata: ClassMetadata
) {
if (!options.introspectComments) {
return [];
}
const apiResponseDecorator = getDecoratorOrUndefinedByNames(
[ApiResponse.name],
decorators,
factory
);
let apiResponseExistingProps:
| ts.NodeArray<ts.PropertyAssignment>
| undefined = undefined;

if (apiResponseDecorator && !options.readonly) {
const apiResponseExpr = head(getDecoratorArguments(apiResponseDecorator));
if (apiResponseExpr) {
apiResponseExistingProps =
apiResponseExpr.properties as ts.NodeArray<ts.PropertyAssignment>;
}
}

const tags = getTsDocReturnsOrErrorOfNode(node);
if (!tags.length) {
return [];
}

return tags.map((tag) => {
const properties = [
...(apiResponseExistingProps ?? factory.createNodeArray())
];
properties.push(
factory.createPropertyAssignment(
'status',
factory.createNumericLiteral(tag.status)
)
);
properties.push(
factory.createPropertyAssignment(
'description',
factory.createNumericLiteral(tag.description)
)
);
const objectLiteralExpr = factory.createObjectLiteralExpression(
compact(properties)
);
const methodKey = node.name.getText();
metadata[methodKey] = objectLiteralExpr;

const apiResponseDecoratorArguments: ts.NodeArray<ts.Expression> =
factory.createNodeArray([objectLiteralExpr]);
return factory.createDecorator(
factory.createCallExpression(
factory.createIdentifier(`${OPENAPI_NAMESPACE}.${ApiResponse.name}`),
undefined,
apiResponseDecoratorArguments
)
);
});
}

createDecoratorObjectLiteralExpr(
factory: ts.NodeFactory,
node: ts.MethodDeclaration,
Expand Down

0 comments on commit d9e0454

Please sign in to comment.