From 0849e0f0bcd1877a66147ac7baaa2a29ac245a3e Mon Sep 17 00:00:00 2001 From: Vinayak Sarawagi Date: Fri, 27 Sep 2024 23:14:48 +0530 Subject: [PATCH] add support for reflections and guards --- lib/index.ts | 1 + lib/reflections/index.ts | 2 + lib/reflections/reflector.ts | 117 ++++++++++++++++++++++++ lib/reflections/setMetadata.ts | 22 +++++ lib/rest/foundation/guards/baseGuard.ts | 29 ++++-- 5 files changed, 164 insertions(+), 7 deletions(-) create mode 100644 lib/reflections/index.ts create mode 100644 lib/reflections/reflector.ts create mode 100644 lib/reflections/setMetadata.ts diff --git a/lib/index.ts b/lib/index.ts index 011ddee..208af8f 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -19,3 +19,4 @@ export * from './validator'; export * from './config/service'; export * from './foundation'; export { registerAs } from '@nestjs/config'; +export * from './reflections'; diff --git a/lib/reflections/index.ts b/lib/reflections/index.ts new file mode 100644 index 0000000..25b3a4d --- /dev/null +++ b/lib/reflections/index.ts @@ -0,0 +1,2 @@ +export * from './reflector'; +export * from './setMetadata'; diff --git a/lib/reflections/reflector.ts b/lib/reflections/reflector.ts new file mode 100644 index 0000000..1073cbd --- /dev/null +++ b/lib/reflections/reflector.ts @@ -0,0 +1,117 @@ +/* eslint-disable @typescript-eslint/ban-types */ +import 'reflect-metadata'; +import { ulid } from 'ulid'; +import { Obj } from '../utils'; + +/** + * Reflector is a class to easily fetch metadata from a class and request handler method + * at the runtime. + */ +export class Reflector { + constructor( + private cls: any, + private handler?: Function, + ) {} + + /** + * Gets the metadata from the `handler` method and `handler's class`, + * if the metadata from the handler is not empty, then return the handler data. + * Otherwise, return the class' metadata. + * + * @param keyOrDecorator + * @returns + */ + allAndOverride(keyOrDecorator: string | Object): T { + const decoratorKey = + typeof keyOrDecorator === 'function' + ? keyOrDecorator['KEY'] + : keyOrDecorator; + + const dataFromHandler = Reflect.getMetadata(decoratorKey, this.handler); + const dataFromClass = Reflect.getMetadata(decoratorKey, this.cls); + return dataFromHandler || dataFromClass; + } + + /** + * Gets the metadata from the `handler` method and `handler's class` + * and merges it. It can currently merge the array and objects. + * + * @param keyOrDecorator string | Object + * @returns + */ + allAndMerge(keyOrDecorator: string | Object): T { + const decoratorKey = + typeof keyOrDecorator === 'function' + ? keyOrDecorator['KEY'] + : keyOrDecorator; + + const dataFromHandler = Reflect.getMetadata(decoratorKey, this.handler); + const dataFromClass = Reflect.getMetadata(decoratorKey, this.cls); + + if (Array.isArray(dataFromHandler) && Array.isArray(dataFromClass)) { + return dataFromHandler.concat(dataFromClass) as T; + } + + if (Obj.isObj(dataFromHandler) && Obj.isObj(dataFromClass)) { + return { ...dataFromClass, ...dataFromHandler } as T; + } + + return [dataFromClass, dataFromHandler] as T; + } + + /** + * Gets the metadata from the `handler's class` and returns it. + * @param keyOrDecorator + * @param defaultValue + * @returns + */ + getFromClass(keyOrDecorator: string | Object, defaultValue?: T): T { + const key = + typeof keyOrDecorator === 'function' + ? keyOrDecorator['KEY'] + : keyOrDecorator; + + const data = Reflect.getMetadata(key, this.cls); + return data || defaultValue; + } + + /** + * Gets the metadata from the `handler` method and returns it. + * @param keyOrDecorator string + * @param defaultValue T + * @returns + */ + getFromMethod(keyOrDecorator: string | Object, defaultValue?: T): T { + const key = + typeof keyOrDecorator === 'function' + ? keyOrDecorator['KEY'] + : keyOrDecorator; + + const data = Reflect.getMetadata(key, this.handler); + return data || defaultValue; + } + + /** + * A shorthand to create a decorator for quickly setting metadata + * on controllers and methods. + * @param decoratorKey string + * @returns + */ + public static createDecorator(decoratorKey?: string) { + const metadataKey = decoratorKey || ulid(); + const decoratorFn = + (metadataValue: TParam) => + (target: object | Function, key?: string | symbol, descriptor?: any) => { + if (descriptor) { + Reflect.defineMetadata(metadataKey, metadataValue, descriptor.value); + return descriptor; + } + + Reflect.defineMetadata(metadataKey, metadataValue, target); + return target; + }; + + decoratorFn.KEY = metadataKey; + return decoratorFn; + } +} diff --git a/lib/reflections/setMetadata.ts b/lib/reflections/setMetadata.ts new file mode 100644 index 0000000..187b999 --- /dev/null +++ b/lib/reflections/setMetadata.ts @@ -0,0 +1,22 @@ +/* eslint-disable @typescript-eslint/ban-types */ +export const SetMetadata = ( + metadataKey: K, + metadataValue: V, +) => { + const decoratorFn = ( + target: object | Function, + key?: string | symbol, + descriptor?: any, + ) => { + if (descriptor) { + Reflect.defineMetadata(metadataKey, metadataValue, descriptor.value); + return descriptor; + } + + Reflect.defineMetadata(metadataKey, metadataValue, target); + return target; + }; + + decoratorFn.KEY = metadataKey; + return decoratorFn; +}; diff --git a/lib/rest/foundation/guards/baseGuard.ts b/lib/rest/foundation/guards/baseGuard.ts index 7d9142e..692e0e2 100644 --- a/lib/rest/foundation/guards/baseGuard.ts +++ b/lib/rest/foundation/guards/baseGuard.ts @@ -1,15 +1,30 @@ -import { CanActivate, ExecutionContext, Inject } from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; +import { CanActivate, ExecutionContext } from '@nestjs/common'; import { Request, Response } from '../interface'; +import { Reflector } from '../../../reflections'; export abstract class IntentGuard implements CanActivate { - @Inject() - private reflector: Reflector; - async canActivate(context: ExecutionContext): Promise { + /** + * Get Express Request Object + */ const expressRequest = context.switchToHttp().getRequest(); - return this.boot(expressRequest, expressRequest); + + /** + * Get Express Response Object + */ + const expressResponse = context.switchToHttp().getResponse(); + + /** + * Initialise a new Reflector class. + */ + const reflector = new Reflector(context.getClass(), context.getHandler()); + + return this.guard(expressRequest, expressResponse, reflector); } - abstract boot(req: Request, res: Response): boolean | Promise; + abstract guard( + req: Request, + res: Response, + reflector: Reflector, + ): boolean | Promise; }