typestack / routing-controllers

Create structured, declarative and beautifully organized class-based controllers with heavy decorators usage in Express / Koa using TypeScript and Routing Controllers Framework.
MIT License
4.41k stars 394 forks source link

Validate array in Body decorator #612

Open hgranlund opened 3 years ago

hgranlund commented 3 years ago

Description

Body of type array is not validated.

Minimal code-snippet showcasing the problem

 @Post('/save')
    save( @Body({ type: Hall, validate: true }) halls: Hall[]) { }

Expected behavior

The body should have been validated

Actual behavior

The body is not validated

andy90rus commented 3 years ago

The same issue!

manofteal commented 3 years ago

I am having a similar issue. However, I am trying to setup @body to accept a json object from the request, but reject if an array is passed. So far it will run validation on the request body if a json object is passed, but not run validation if an array is passed.

example:

  public async exampleFunction(
    @Body({ required: true  }) requestBody: RequestValidator
  )

@NoNameProvided I believe this is similar to Issue 371

7affer commented 3 years ago

Any updates on this?

abheypathogenie commented 2 years ago

any update ?

attilaorosz commented 2 years ago

This is I think a typescript limitation at the moment. Reflector is not capable of returning the subtypes, it only emits that your type is an array, which is not enough for class-transformer to instantiate your desired target class. There could a solution to add the type parameter yes, but that will not solve primitive values. Since we cannot define types in the type option, you won't be able to indicate your subtype for primitve arrays.

This is a bit tricky question since class-validator won't run for primitive types without an encapsulating class anyway.

I think we have two ways to tackle this:

  1. Edit the BodyOptions to better align with ParamMetadataArgs and change transform to classTransform as other places and use transform as ParamMetadataArgs's transform to provide an interface on actually changing the value. With this approach we also have to change the order of execution because at the moment bodyParser runs after the transformation which results in an InvalidJsonError. This involves breaking changes.
  2. We have to provide additional properties to somehow cater to these scenarios. This would probably involve a lot of explicit checks for very specific things which would scale poorly in the future.

Personally I would go with scenario 1.

@NoNameProvided Any thoughts on this?

driescroons commented 2 years ago

Running into the same issue as @manofteal mentions. When passing an array (which I don't want), it gets passed to my controller method, unvalidated. Any update on this?

Edit: @attilaorosz I checked your two potential solutions, but shouldn't we just disable passing arrays as a body? Nest does it that way, because like you mentioned, it's a TS issue.

  1. https://github.com/nestjs/nest/issues/2874
  2. https://github.com/nestjs/nest/issues/335
MustansirZia commented 1 year ago

I had the same issue and was able to fix it by using this hack.

Let's see some code.

@Middleware({ type: 'before' }) @Service() class TransformArrayBody implements ExpressMiddlewareInterface { use(request: Request, response: Response, next: NextFunction): void { const json: RequestHandler = bodyParser.json(); json(request, response, () => { if (!request.body || !(request.body instanceof Array)) { return next( new BadRequestError( 'Body is invalid. Please send a valid array body.' ) ); } request.body = { data: request.body }; if (next instanceof Function) { return next(); } }); } }

export default TransformArrayBody;

* The HOC and the class blueprint it returns (That's `RequestDto<T>`).
```ts
import { Type } from 'class-transformer';
import { ValidateNested } from 'class-validator';

// Class to annotate our request body.
class RequestDto<T> {
  data: T;
}

// HOC to create a dynamic class based on a class that is passed to it. This class has a "data" property which matches structure of one array item.
function CreateRequestDto<Impl, Arg>(
  Class: new (arg?: Arg) => Impl
): new (arg?: Arg) => RequestDto<Impl> {
  class Dto {
    @Type(() => Class)
    @ValidateNested()
    data: Impl;
  }
  return Dto;
}

export { CreateRequestDto, RequestDto };