UNPKG

@opra/nestjs-http

Version:
164 lines (163 loc) 7.49 kB
import { __decorate, __metadata, __param } from "tslib"; import nodePath from 'node:path'; import { isConstructor } from '@jsopen/objects'; import { Controller, Delete, Get, Head, Next, Options, Patch, Post, Put, Req, Res, Search, } from '@nestjs/common'; import { HTTP_CONTROLLER_METADATA, NotFoundError, } from '@opra/common'; import { HttpAdapter } from '@opra/http'; import { OpraNestUtils, Public } from '@opra/nestjs'; import { asMutable } from 'ts-gems'; export class OpraHttpNestjsAdapter extends HttpAdapter { constructor(options) { super(options); this.nestControllers = []; this._addRootController(options.schemaIsPublic); if (options.controllers) { for (const c of options.controllers) { this._addToNestControllers(c, this.basePath, []); } } } async close() { // } _addRootController(isPublic) { const _this = this; let RootController = class RootController { schema(_req, next) { _this.handler.sendDocumentSchema(_req.opraContext).catch(() => next()); } }; __decorate([ Get('/\\$schema'), __param(0, Req()), __param(1, Next()), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Function]), __metadata("design:returntype", void 0) ], RootController.prototype, "schema", null); RootController = __decorate([ Controller({ path: this.basePath, }) ], RootController); if (isPublic) { Public()(RootController.prototype, 'schema', Object.getOwnPropertyDescriptor(RootController.prototype, 'schema')); } this.nestControllers.push(RootController); } _addToNestControllers(sourceClass, currentPath, parentTree) { const metadata = Reflect.getMetadata(HTTP_CONTROLLER_METADATA, sourceClass); if (!metadata) return; /** Create a new controller class */ const newClass = { [sourceClass.name]: class extends sourceClass { }, }[sourceClass.name]; /** Copy metadata keys from source class to new one */ OpraNestUtils.copyDecoratorMetadata(newClass, ...parentTree); Controller()(newClass); const newPath = metadata.path ? nodePath.join(currentPath, metadata.path) : currentPath; const adapter = this; // adapter.logger = /** Disable default error handler. Errors will be handled by OpraExceptionFilter */ adapter.handler.onError = (context, error) => { throw error; }; this.nestControllers.push(newClass); let metadataKeys; if (metadata.operations) { for (const [k, v] of Object.entries(metadata.operations)) { const operationHandler = sourceClass.prototype[k]; Object.defineProperty(newClass.prototype, k, { writable: true, /** NestJS handler method */ async value(_req, _res) { _res.statusCode = 200; const api = adapter.document.api; const controller = api.findController(sourceClass); const operation = controller?.operations.get(k); const context = asMutable(_req.opraContext); if (!(context && operation && typeof operationHandler === 'function')) { throw new NotFoundError({ message: `No endpoint found for [${_req.method}]${_req.baseUrl}`, details: { path: _req.baseUrl, method: _req.method, }, }); } /** Configure the HttpContext */ context.operation = operation; context.controller = operation.owner; context.controllerInstance = this; context.operationHandler = operationHandler; /** Handle request */ await adapter.handler.handleRequest(context); }, }); /** Copy metadata keys from source function to new one */ metadataKeys = Reflect.getOwnMetadataKeys(operationHandler); const newFn = newClass.prototype[k]; for (const key of metadataKeys) { const m = Reflect.getMetadata(key, operationHandler); Reflect.defineMetadata(key, m, newFn); } Req()(newClass.prototype, k, 0); Res()(newClass.prototype, k, 1); const descriptor = Object.getOwnPropertyDescriptor(newClass.prototype, k); const operationPath = v.mergePath ? newPath + (v.path || '') : nodePath.posix.join(newPath, v.path || ''); switch (v.method || 'GET') { case 'DELETE': /** Call @Delete decorator over new property */ Delete(operationPath)(newClass.prototype, k, descriptor); break; case 'GET': /** Call @Get decorator over new property */ Get(operationPath)(newClass.prototype, k, descriptor); break; case 'HEAD': /** Call @Head decorator over new property */ Head(operationPath)(newClass.prototype, k, descriptor); break; case 'OPTIONS': /** Call @Options decorator over new property */ Options(operationPath)(newClass.prototype, k, descriptor); break; case 'PATCH': /** Call @Patch decorator over new property */ Patch(operationPath)(newClass.prototype, k, descriptor); break; case 'POST': /** Call @Post decorator over new property */ Post(operationPath)(newClass.prototype, k, descriptor); break; case 'PUT': /** Call @Put decorator over new property */ Put(operationPath)(newClass.prototype, k, descriptor); break; case 'SEARCH': /** Call @Search decorator over new property */ Search(operationPath)(newClass.prototype, k, descriptor); break; default: break; } } } if (metadata.controllers) { for (const child of metadata.controllers) { if (!isConstructor(child)) throw new TypeError('Controllers should be injectable a class'); this._addToNestControllers(child, newPath, [ ...parentTree, sourceClass, ]); } } } }