UNPKG

@nestia/core

Version:

Super-fast validation decorators of NestJS

181 lines (173 loc) 5.77 kB
/** * Attach example values to Swagger documents. * * `SwaggerExample` is a namespace of decorators that attach example values * to controller methods (request/response bodies, parameters), so that * `@nestia/sdk`'s Swagger generator can populate the `example` / `examples` * fields of the generated OpenAPI document. * * The decorators only affect Swagger document generation. They do not change * runtime behavior, validation, or SDK function signatures. * * @example * * ```typescript * import core from "@nestia/core"; * import { Controller } from "@nestjs/common"; * import typia from "typia"; * * @Controller("bbs/articles") * export class BbsArticlesController { * // Single response example. * @core.SwaggerExample.Response(typia.random<IBbsArticle>()) * @core.TypedRoute.Post() * public async create( * // Multiple named parameter examples plus a default one. * @core.SwaggerExample.Parameter(typia.random<IBbsArticle.ICreate>()) * @core.SwaggerExample.Parameter("x", typia.random<IBbsArticle.ICreate>()) * @core.SwaggerExample.Parameter("y", typia.random<IBbsArticle.ICreate>()) * @core.TypedBody() * input: IBbsArticle.ICreate, * ): Promise<IBbsArticle> { * // ... * } * } * ``` * * @author Jeongho Nam - https://github.com/samchon */ export namespace SwaggerExample { /** * Attach an example value to the response body of a controller method. * * Two forms are supported: * * - {@link Response | `Response(value)`}: registers `value` as the single * default `example`. * - {@link Response | `Response(key, value)`}: registers `value` under * `examples[key]`. May be applied multiple times to attach several * named examples. * * Both forms can coexist on the same method — the default `example` and * any number of named `examples` will all surface in the generated * Swagger document. * * @template T Type of the example value (typically the route's response DTO). */ export function Response<T>(value: T): MethodDecorator; export function Response<T>(key: string, value: T): MethodDecorator; export function Response(...args: any[]): MethodDecorator { return function SwaggerExampleResponse( _target: Object, _propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<any>, ): TypedPropertyDescriptor<any> { emplaceValue(emplaceOfResponse(descriptor))(args); return descriptor; }; } /** * Attach an example value to a request parameter (body, path, query, etc.) * of a controller method. * * Two forms are supported: * * - {@link Parameter | `Parameter(value)`}: registers `value` as the single * default `example` for that parameter. * - {@link Parameter | `Parameter(key, value)`}: registers `value` under * `examples[key]`. May be applied multiple times to attach several * named examples to the same parameter. * * Apply alongside the actual parameter decorator * (`@TypedBody()`, `@TypedParam()`, `@TypedQuery()`, etc.); decorator * order does not matter. * * @template T Type of the example value (typically the parameter's DTO). */ export function Parameter<T>(value: T): ParameterDecorator; export function Parameter<T>(key: string, value: T): ParameterDecorator; export function Parameter(...args: any[]): ParameterDecorator { return function SwaggerExampleParameter( target: Object, propertyKey: string | symbol | undefined, index: number, ): void { emplaceValue(emplaceOfParameter(target, propertyKey ?? "", index))(args); }; } /** * Internal storage shape for `SwaggerExample` metadata. * * Reflects the OpenAPI spec's two example fields: a single default * `example`, and/or a named map `examples`. `index` is used internally * to associate parameter examples with the right parameter position. */ export interface IData<T> { examples?: Record<string, T>; example?: T; index?: number; } } const emplaceValue = <T>(data: SwaggerExample.IData<T>) => (args: any[]) => { if (args.length === 1) data.example = args[0]; else { const key: string = args[0]; const value: T = args[1]; data.examples ??= {}; data.examples[key] = value; } }; const emplaceOfResponse = <T>( descriptor: TypedPropertyDescriptor<any>, ): SwaggerExample.IData<T> => { const oldbie: SwaggerExample.IData<T> | undefined = Reflect.getMetadata( "nestia/SwaggerExample/Response", descriptor.value, ); if (oldbie !== undefined) return oldbie; const newbie: SwaggerExample.IData<T> = {}; Reflect.defineMetadata( "nestia/SwaggerExample/Response", newbie, descriptor.value, ); return newbie; }; const emplaceOfParameter = ( target: Object, propertyKey: string | symbol, index: number, ): SwaggerExample.IData<any> => { const array: SwaggerExample.IData<any>[] = emplaceArrayOfParameters( target, propertyKey, ); const oldibe: SwaggerExample.IData<any> | undefined = array.find( (e) => e.index === index, ); if (oldibe !== undefined) return oldibe; const data: SwaggerExample.IData<any> = { index }; array.push(data); return data; }; const emplaceArrayOfParameters = ( target: Object, propertyKey: string | symbol, ): SwaggerExample.IData<any>[] => { const array: SwaggerExample.IData<any>[] | undefined = Reflect.getMetadata( "nestia/SwaggerExample/Parameters", target, propertyKey, ); if (array !== undefined) return array; const newbie: SwaggerExample.IData<any>[] = []; Reflect.defineMetadata( "nestia/SwaggerExample/Parameters", newbie, target, propertyKey, ); return newbie; };