From e2c15af6b948e7cd87fa19ec3ea24b94158bd6c4 Mon Sep 17 00:00:00 2001 From: Mathew Trivett Date: Thu, 25 May 2023 17:28:38 +0100 Subject: [PATCH] feat(express): add support for multer.none Adds an interceptor for multer's none option https://github.com/expressjs/multer#none. This allows for Nest.js users to process `multipart/form-data` that does not include any files. --- .../multer/interceptors/index.ts | 1 + .../interceptors/no-files.interceptor.ts | 56 +++++++++++++++++++ .../interceptors/no-files.inteceptor.spec.ts | 44 +++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 packages/platform-express/multer/interceptors/no-files.interceptor.ts create mode 100644 packages/platform-express/test/multer/interceptors/no-files.inteceptor.spec.ts diff --git a/packages/platform-express/multer/interceptors/index.ts b/packages/platform-express/multer/interceptors/index.ts index 5f76d7d477c..855620d9762 100644 --- a/packages/platform-express/multer/interceptors/index.ts +++ b/packages/platform-express/multer/interceptors/index.ts @@ -2,3 +2,4 @@ export * from './any-files.interceptor'; export * from './file-fields.interceptor'; export * from './file.interceptor'; export * from './files.interceptor'; +export * from './no-files.interceptor'; diff --git a/packages/platform-express/multer/interceptors/no-files.interceptor.ts b/packages/platform-express/multer/interceptors/no-files.interceptor.ts new file mode 100644 index 00000000000..1607917d7dd --- /dev/null +++ b/packages/platform-express/multer/interceptors/no-files.interceptor.ts @@ -0,0 +1,56 @@ +import { + CallHandler, + ExecutionContext, + Inject, + mixin, + NestInterceptor, + Optional, + Type, +} from '@nestjs/common'; +import * as multer from 'multer'; +import { Observable } from 'rxjs'; +import { MULTER_MODULE_OPTIONS } from '../files.constants'; +import { MulterModuleOptions } from '../interfaces'; +import { MulterOptions } from '../interfaces/multer-options.interface'; +import { transformException } from '../multer/multer.utils'; + +type MulterInstance = any; + +export function NoFilesInterceptor( + localOptions?: MulterOptions, +): Type { + class MixinInterceptor implements NestInterceptor { + protected multer: MulterInstance; + + constructor( + @Optional() + @Inject(MULTER_MODULE_OPTIONS) + options: MulterModuleOptions = {}, + ) { + this.multer = (multer as any)({ + ...options, + ...localOptions, + }); + } + + async intercept( + context: ExecutionContext, + next: CallHandler, + ): Promise> { + const ctx = context.switchToHttp(); + + await new Promise((resolve, reject) => + this.multer.none()(ctx.getRequest(), ctx.getResponse(), (err: any) => { + if (err) { + const error = transformException(err); + return reject(error); + } + resolve(); + }), + ); + return next.handle(); + } + } + const Interceptor = mixin(MixinInterceptor); + return Interceptor; +} diff --git a/packages/platform-express/test/multer/interceptors/no-files.inteceptor.spec.ts b/packages/platform-express/test/multer/interceptors/no-files.inteceptor.spec.ts new file mode 100644 index 00000000000..5b5289941fa --- /dev/null +++ b/packages/platform-express/test/multer/interceptors/no-files.inteceptor.spec.ts @@ -0,0 +1,44 @@ +import { CallHandler } from '@nestjs/common'; +import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context-host'; +import { expect } from 'chai'; +import { of } from 'rxjs'; +import * as sinon from 'sinon'; +import { NoFilesInterceptor } from '../../../multer/interceptors/no-files.interceptor'; + +describe('NoFilesInterceptor', () => { + it('should return metatype with expected structure', async () => { + const targetClass = NoFilesInterceptor(); + expect(targetClass.prototype.intercept).to.not.be.undefined; + }); + describe('intercept', () => { + let handler: CallHandler; + beforeEach(() => { + handler = { + handle: () => of('test'), + }; + }); + it('should call none() with expected params', async () => { + const target = new (NoFilesInterceptor())(); + + const callback = (req, res, next) => next(); + const noneSpy = sinon + .stub((target as any).multer, 'none') + .returns(callback); + + await target.intercept(new ExecutionContextHost([]), handler); + + expect(noneSpy.called).to.be.true; + }); + it('should transform exception', async () => { + const target = new (NoFilesInterceptor())(); + const err = {}; + const callback = (req, res, next) => next(err); + (target as any).multer = { + none: () => callback, + }; + (target.intercept(new ExecutionContextHost([]), handler) as any).catch( + error => expect(error).to.not.be.undefined, + ); + }); + }); +});