routing-controllers
routing-controllers copied to clipboard
Validate array in Body decorator
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
The same issue!
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
Any updates on this?
any update ?
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:
- 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.
- 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?
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.
- https://github.com/nestjs/nest/issues/2874
- https://github.com/nestjs/nest/issues/335
I had the same issue and was able to fix it by using this hack.
- I intercepted the request body and changed the array to a nested array which can be accessed using a
.data
property. So for the controller this is what the body would look like.
{
data: [{ ... }, { ... }]
}
- I created a higher order class that takes a
class
and puts it as the type of itsdata
property. We need this as routing-controllers cannot use Generics to deduce the correct type. - I reuse the HOC wherever I encounter an array body so the array elements are validated as per decorators inside them.
Let's see some code.
- Request interceptor.
import bodyParser from 'body-parser';
import { NextFunction, Request, RequestHandler, Response } from 'express';
import {
BadRequestError,
ExpressMiddlewareInterface,
Middleware
} from 'routing-controllers';
import { Service } from 'typedi';
@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>
).
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 };
- The eventual usage. Voila!
@UseBefore(TransformArrayBody)
async put(
@Body({
type: CreateRequestDto(MyClass),
required: true
})
body: RequestDto<MyClass[]>,
@Req() req: Request
): Promise<void> {
console.log(body.data); // data has the original array that the client sent which completely typed and validated! :)
}