From a283002a341b10796e1b7121b3182f96d44ad303 Mon Sep 17 00:00:00 2001 From: Philip Molares Date: Mon, 7 Feb 2022 00:43:10 +0100 Subject: [PATCH] feat: create openapi decorator This decorator gets a list of http codes and possible descriptions and adds all necessary decorator internally to the method or the class. This will prevent long OpenApi annotations and keep the controllers shorter. Signed-off-by: Philip Molares --- src/api/utils/descriptions.ts | 7 +- src/api/utils/openapi.decorator.ts | 150 +++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 src/api/utils/openapi.decorator.ts diff --git a/src/api/utils/descriptions.ts b/src/api/utils/descriptions.ts index dd8d23d4e..e00b24c23 100644 --- a/src/api/utils/descriptions.ts +++ b/src/api/utils/descriptions.ts @@ -1,9 +1,14 @@ /* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ +export const okDescription = 'This request was successful'; +export const createdDescription = + 'The requested resource was successfully created'; +export const noContentDescription = + 'The requested resource was successfully deleted'; export const badRequestDescription = "The request is malformed and can't be processed"; export const unauthorizedDescription = diff --git a/src/api/utils/openapi.decorator.ts b/src/api/utils/openapi.decorator.ts new file mode 100644 index 000000000..47258da63 --- /dev/null +++ b/src/api/utils/openapi.decorator.ts @@ -0,0 +1,150 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { applyDecorators, Header, HttpCode } from '@nestjs/common'; +import { + ApiBadRequestResponse, + ApiConflictResponse, + ApiCreatedResponse, + ApiInternalServerErrorResponse, + ApiNoContentResponse, + ApiNotFoundResponse, + ApiOkResponse, + ApiProduces, + ApiUnauthorizedResponse, +} from '@nestjs/swagger'; + +import { BaseDto } from '../../utils/base.dto.'; +import { + badRequestDescription, + conflictDescription, + createdDescription, + internalServerErrorDescription, + noContentDescription, + notFoundDescription, + okDescription, + unauthorizedDescription, +} from './descriptions'; + +export type HttpStatusCodes = + | 200 + | 201 + | 204 + | 400 + | 401 + | 403 + | 404 + | 409 + | 500; + +export interface HttpStatusCodeWithExtraInformation { + code: HttpStatusCodes; + description?: string; + isArray?: boolean; + dto?: BaseDto; + mimeType?: string; +} + +// eslint-disable-next-line @typescript-eslint/naming-convention,func-style +export const OpenApi = ( + ...httpStatusCodesMaybeWithExtraInformation: ( + | HttpStatusCodes + | HttpStatusCodeWithExtraInformation + )[] +): // eslint-disable-next-line @typescript-eslint/ban-types +(( + target: object | TFunction, + propertyKey?: string | symbol, + descriptor?: TypedPropertyDescriptor, +) => void) => { + const decoratorsToApply: (MethodDecorator | ClassDecorator)[] = []; + for (const entry of httpStatusCodesMaybeWithExtraInformation) { + let code: HttpStatusCodes = 200; + let description: string | undefined = undefined; + let isArray: boolean | undefined = undefined; + let dto: BaseDto | undefined = undefined; + if (typeof entry == 'number') { + code = entry; + } else { + // We've got a HttpStatusCodeWithExtraInformation + code = entry.code; + description = entry.description; + isArray = entry.isArray; + dto = entry.dto; + if (entry.mimeType) { + decoratorsToApply.push( + ApiProduces(entry.mimeType), + Header('Content-Type', entry.mimeType), + ); + } + } + switch (code) { + case 200: + decoratorsToApply.push( + ApiOkResponse({ + description: description ?? okDescription, + isArray: isArray, + type: () => dto, + }), + ); + break; + case 201: + decoratorsToApply.push( + ApiCreatedResponse({ + description: description ?? createdDescription, + isArray: isArray, + type: () => dto, + }), + HttpCode(201), + ); + break; + case 204: + decoratorsToApply.push( + ApiNoContentResponse({ + description: description ?? noContentDescription, + }), + HttpCode(204), + ); + break; + case 400: + decoratorsToApply.push( + ApiBadRequestResponse({ + description: description ?? badRequestDescription, + }), + ); + break; + case 401: + decoratorsToApply.push( + ApiUnauthorizedResponse({ + description: description ?? unauthorizedDescription, + }), + ); + break; + case 404: + decoratorsToApply.push( + ApiNotFoundResponse({ + description: description ?? notFoundDescription, + }), + ); + break; + case 409: + decoratorsToApply.push( + ApiConflictResponse({ + description: description ?? conflictDescription, + }), + ); + break; + case 500: + decoratorsToApply.push( + ApiInternalServerErrorResponse({ + description: internalServerErrorDescription, + }), + ); + break; + } + } + + return applyDecorators(...decoratorsToApply); +};