UNPKG

@nestia/sdk

Version:

Nestia SDK and Swagger generator

128 lines (118 loc) 4.45 kB
import { SwaggerExample } from "@nestia/core"; import { HEADERS_METADATA, HTTP_CODE_METADATA, INTERCEPTORS_METADATA, } from "@nestjs/common/constants"; import typia from "typia"; import { JsonMetadataFactory } from "typia/lib/factories/JsonMetadataFactory"; import { HttpQueryProgrammer } from "typia/lib/programmers/http/HttpQueryProgrammer"; import { IReflectController } from "../structures/IReflectController"; import { IReflectHttpOperationSuccess } from "../structures/IReflectHttpOperationSuccess"; import { IReflectOperationError } from "../structures/IReflectOperationError"; import { IOperationMetadata } from "../transformers/IOperationMetadata"; import { TextPlainValidator } from "../transformers/TextPlainValidator"; export namespace ReflectHttpOperationResponseAnalyzer { export interface IContext { controller: IReflectController; function: Function; functionName: string; httpMethod: string; metadata: IOperationMetadata; errors: IReflectOperationError[]; } export const analyze = ( ctx: IContext, ): IReflectHttpOperationSuccess | null => { const errors: Array<string | IOperationMetadata.IError> = []; const report = () => { ctx.errors.push({ file: ctx.controller.file, class: ctx.controller.class.name, function: ctx.functionName, from: "return", contents: errors, }); return null; }; const encrypted: boolean = hasInterceptor({ name: "EncryptedRouteInterceptor", function: ctx.function, }); const contentType: string | null = encrypted ? "text/plain" : hasInterceptor({ name: "TypedQueryRouteInterceptor", function: ctx.function, }) ? "application/x-www-form-urlencoded" : (Reflect.getMetadata(HEADERS_METADATA, ctx.function)?.find( (h: Record<string, string>) => typeof h?.name === "string" && typeof h?.value === "string" && h.name.toLowerCase() === "content-type", )?.value ?? Reflect.getMetadata("swagger/apiProduces", ctx.function)?.[0] ?? (ctx.httpMethod === "HEAD" ? null : "application/json")); const schema = contentType === "application/json" ? ctx.metadata.success.primitive : ctx.metadata.success.resolved; if (schema.success === false) errors.push(...schema.errors); if (ctx.httpMethod === "HEAD" && contentType !== null) errors.push(`HEAD method must not have a content type.`); if ( typia.is<IReflectHttpOperationSuccess["contentType"]>(contentType) === false ) errors.push( `@nestia/sdk does not support ${JSON.stringify(contentType)} content type.`, ); if (errors.length) return report(); else if ( ctx.metadata.success.type === null || schema.success === false || !typia.is<IReflectHttpOperationSuccess["contentType"]>(contentType) ) return null; const example: SwaggerExample.IData<any> | undefined = Reflect.getMetadata( "nestia/SwaggerExample/Response", ctx.function, ); return { contentType: contentType, encrypted, status: getStatus(ctx.function) ?? (ctx.httpMethod === "POST" ? 201 : 200), type: ctx.metadata.success.type, ...schema.data, validate: contentType === "application/json" || encrypted === true ? JsonMetadataFactory.validate : contentType === "application/x-www-form-urlencoded" ? HttpQueryProgrammer.validate : contentType === "text/plain" ? TextPlainValidator.validate : (meta) => meta.size() ? ["HEAD method must not have any return value."] : [], example: example?.example, examples: example?.examples, }; }; const getStatus = (func: Function): number | null => { const text = Reflect.getMetadata(HTTP_CODE_METADATA, func); if (text === undefined) return null; const value: number = Number(text); return isNaN(value) ? null : value; }; const hasInterceptor = (props: { name: string; function: Function; }): boolean => { const meta = Reflect.getMetadata(INTERCEPTORS_METADATA, props.function); if (Array.isArray(meta) === false) return false; return meta.some((elem) => elem?.constructor?.name === props.name); }; }