UNPKG

@nestia/sdk

Version:

Nestia SDK and Swagger generator

164 lines (153 loc) 5.28 kB
import { SwaggerExample } from "@nestia/core"; import { HEADERS_METADATA, HTTP_CODE_METADATA, INTERCEPTORS_METADATA, } from "@nestjs/common/constants"; import { HttpQueryProgrammer, JsonMetadataFactory, sizeOf, } from "../internal/legacy"; import { IOperationMetadata } from "../structures/IOperationMetadata"; import { IReflectController } from "../structures/IReflectController"; import { IReflectHttpOperationSuccess } from "../structures/IReflectHttpOperationSuccess"; import { IReflectOperationError } from "../structures/IReflectOperationError"; import { HttpResponseContentTypeUtil } from "../utils/HttpResponseContentTypeUtil"; import { TextPlainValidator } from "../validators/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 binary: boolean = HttpResponseContentTypeUtil.isBinary(contentType); const schema = binary ? { success: true as const, data: EMPTY_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 (HttpResponseContentTypeUtil.isSupported(contentType) === false) errors.push( `@nestia/sdk does not support ${JSON.stringify(contentType)} content type.`, ); if (errors.length) return report(); else if ( (binary === false && ctx.metadata.success.type === null) || schema.success === false || !HttpResponseContentTypeUtil.isSupported(contentType) ) return null; const example: SwaggerExample.IData<any> | undefined = Reflect.getMetadata( "nestia/SwaggerExample/Response", ctx.function, ); return { contentType, binary, encrypted, status: getStatus(ctx.function) ?? (ctx.httpMethod === "POST" ? 201 : 200), type: ctx.metadata.success.type ?? { name: "ReadableStream" }, ...schema.data, validate: binary === true ? () => [] : contentType === "application/json" || encrypted === true ? JsonMetadataFactory.validate : contentType === "application/x-www-form-urlencoded" ? HttpQueryProgrammer.validate : contentType === "text/plain" ? TextPlainValidator.validate : (next) => sizeOf(next.metadata) !== 0 ? ["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); }; const EMPTY_SCHEMA: IOperationMetadata.ISchema = { components: { aliases: [], arrays: [], objects: [], tuples: [], }, metadata: { aliases: [], any: false, arrays: [], atomics: [], constants: [], escaped: null, functions: [], maps: [], natives: [], nullable: false, objects: [], optional: false, required: true, rest: null, sets: [], templates: [], tuples: [], size: 0, name: "void", empty: true, } as IOperationMetadata.ISchema["metadata"], }; }