samchon / nestia

NestJS Helper Libraries + TypeScript OpenAPI generator
https://nestia.io/
MIT License
1.71k stars 89 forks source link

@TypedFormData.Body not working with Fastify #862

Closed ninthsun91 closed 2 months ago

ninthsun91 commented 3 months ago

Hi, thanks for updating fastify supports with @TypedFormData.

However, I don't think this is working well. Nestia is throwing error while validating request body.

{
    "path": "$input.name",
    "reason": "Error on typia.http.assertFormData(): invalid type on $input.name, expect to be string",
    "expected": "string",
    "message": "Request multipart data is not following the promised type."
}
image

Here's the minimal reproduction. You can also clone the code from here.

// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
import multipart from '@fastify/multipart';

async function bootstrap() {
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter(),
  );
  app.register(multipart);

  await app.listen(3000);
}
bootstrap();
// app.controller.ts
import { Controller } from '@nestjs/common';
import { TypedFormData, TypedRoute } from '@nestia/core';

@Controller()
export class AppController {
  @TypedRoute.Post('/test')
  async test(
    @TypedFormData.Body() form: FormDataDto,
  ): Promise<void> {
    console.log('form: ', form);
  }
}

export interface FormDataDto {
  file: File;
  name: string;
}

I confirmed that the same request works with nestjs express platform or vanilla fastify with @fastify/multipart plugin registered. So, I hardly doubt the request is not valid for fastify.

thoriqadillah commented 2 months ago

Confirmed this. Using fastify with multipart is not working for me either. Here is my error

[Nest] 36944  - 04/28/2024, 12:44:08 AM   ERROR [ExceptionsHandler] File is not defined
ReferenceError: File is not defined
    at string (project/node_modules/.pnpm/typia@6.0.3_typescript@5.4.2/node_modules/typia/src/functional/$FormDataReader/$FormDataReader.ts:43:20)
    at decode (project/dist/modules/example/example.controller.js:124:26)
    at assert (project/dist/modules/example/example.controller.js:150:31)
    at project/node_modules/.pnpm/@nestia+core@3.0.5_@nestia+fetcher@3.0.5_@nestjs+common@10.3.8_@nestjs+core@10.3.8_reflect-me_33ze35cl3fyddlgbtw33ly2oz4/node_modules/@nestia/core/src/decorators/internal/validate_request_form_data.ts:32:14
    at project/node_modules/.pnpm/@nestia+core@3.0.5_@nestia+fetcher@3.0.5_@nestjs+common@10.3.8_@nestjs+core@10.3.8_reflect-me_33ze35cl3fyddlgbtw33ly2oz4/node_modules/@nestia/core/src/decorators/TypedFormData.ts:101:33
    at step (project/node_modules/.pnpm/@nestia+core@3.0.5_@nestia+fetcher@3.0.5_@nestjs+common@10.3.8_@nestjs+core@10.3.8_reflect-me_33ze35cl3fyddlgbtw33ly2oz4/node_modules/@nestia/core/lib/decorators/TypedFormData.js:44:23)
    at Object.next (project/node_modules/.pnpm/@nestia+core@3.0.5_@nestia+fetcher@3.0.5_@nestjs+common@10.3.8_@nestjs+core@10.3.8_reflect-me_33ze35cl3fyddlgbtw33ly2oz4/node_modules/@nestia/core/lib/decorators/TypedFormData.js:25:53)
    at fulfilled (project/node_modules/.pnpm/@nestia+core@3.0.5_@nestia+fetcher@3.0.5_@nestjs+common@10.3.8_@nestjs+core@10.3.8_reflect-me_33ze35cl3fyddlgbtw33ly2oz4/node_modules/@nestia/core/lib/decorators/TypedFormData.js:16:58)
    at processTicksAndRejections (node:internal/process/task_queues:95:5)

Currently my workaround is just creating a param decorator to assert the request body

export function FormData(assert: (data: unknown) => void): ParameterDecorator {
  return createParamDecorator((_: any, ctx: ExecutionContext) => {
    const req = ctx.switchToHttp().getRequest() as FastifyRequest
    try {
      assert(req.body)
      return req.body

    } catch (error) {
      if (error instanceof TypeGuardError) throw new BadRequestException({
        path: error.path,
        reason: `Error on ${error.method}(): invalid type on ${error.path}, expect to be ${error.expected}`,
        expected: error.expected,
        message: "Request body data is not following the promised type."
      })
    }
  })()
}

Usage

export interface FormDataExample {
  foo: string
}

@Post('form-data')
@Upload('image') // wrapper decorator using fastify multer
formData(
  @FormData(data => typia.assert<FormDataExample>(data)) 
  payload: FormDataExample,
  @UploadedFile() file: File,
) {
  return {
    ...file,
    ...payload
  }
}
samchon commented 2 months ago

@ninthsun91 @thoriqadillah Thanks for bug reporting.

Now, upgrade to v3.1.2, then this bug be fixed.

thoriqadillah commented 2 months ago

After updating , I still got the same error @samchon

[Nest] 27665  - 05/05/2024, 7:53:25 AM   ERROR [ExceptionsHandler] File is not defined
ReferenceError: File is not defined
    at string (project/node_modules/.pnpm/typia@6.0.3_typescript@5.4.2/node_modules/typia/src/functional/$FormDataReader/$FormDataReader.ts:43:20)
    at decode (project/dist/modules/example/example.controller.js:435:26)
    at assert (project/dist/modules/example/example.controller.js:461:31)
    at project/node_modules/.pnpm/@nestia+core@3.1.2_@nestia+fetcher@3.1.2_@nestjs+common@10.3.8_@nestjs+core@10.3.8_reflect-me_z7bpgokcv72kwtpynnfygo76ru/node_modules/@nestia/core/src/decorators/internal/validate_request_form_data.ts:32:14
    at project/node_modules/.pnpm/@nestia+core@3.1.2_@nestia+fetcher@3.1.2_@nestjs+common@10.3.8_@nestjs+core@10.3.8_reflect-me_z7bpgokcv72kwtpynnfygo76ru/node_modules/@nestia/core/src/decorators/TypedFormData.ts:101:33
    at Generator.next (<anonymous>)
    at fulfilled (project/node_modules/.pnpm/@nestia+core@3.1.2_@nestia+fetcher@3.1.2_@nestjs+common@10.3.8_@nestjs+core@10.3.8_reflect-me_z7bpgokcv72kwtpynnfygo76ru/node_modules/@nestia/core/lib/decorators/TypedFormData.js:5:58)
    at processTicksAndRejections (node:internal/process/task_queues:95:5)

What i did:

export interface FormDataExample {
  foo: string
}
  @TypedRoute.Post('form-data')
  @Upload('image')
  formData(
    @TypedFormData.Body() payload: FormDataExample,
    @UploadedFile() file: File,
  ) {
    return {
      ...file,
      ...payload
    }
  }

image

samchon commented 2 months ago

Check your node version. File is not defined means that your NodeJS does not have the File class.

thoriqadillah commented 2 months ago

Upgrading to version 20.12.2 (LTS) solves the problem. Thanks