-
-
Notifications
You must be signed in to change notification settings - Fork 7.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(@nestjs/common) improve the ValidationPipe #383
feat(@nestjs/common) improve the ValidationPipe #383
Conversation
PLEASE MERGE 🗡 |
@kamilmysliwiec pls merge this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not even a common participant in the nest community (just a newcomer) but if you're interested, I gave some input on this PR as I found the Pipe (and its configuration) useful (I may do a following PR on validation groups ;) )
src/common/pipes/validation.pipe.ts
Outdated
private shouldReject: boolean; | ||
|
||
constructor(options?: ValidationPipeOptions) { | ||
this.shouldTransform = options && (options.transform || options.strip || options.reject); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these ||
are too opinionated. If I want stripping but not transforming, I can't. Same between strip and reject.
There is not an issue at all to let them be independent of each other.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, people might want to strip and don't get the transformed value. However, I this represents the more common usecase. I'm setting transform = true
by default.
src/common/pipes/validation.pipe.ts
Outdated
@Pipe() | ||
export class ValidationPipe implements PipeTransform<any> { | ||
|
||
private shouldTransform: boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're using a different naming scheme from class-validator
.
Why rename whitelist -> shouldStrip
and forbidNonWhitelisted -> shouldReject
. You should use same names and not change the mnemonics without a hard reason. You end up loosing the original class-validator
documentation references as a user.
Thanks for your review, I'm looking into it asap |
allow to whitelist and forbid properties without transforming the returned object
adopt the class-validator naming scheme
add support for all class-validator options
@ShadowManu My last commit adds support for all |
src/common/pipes/validation.pipe.ts
Outdated
this.shouldTransform = options && (options.transform || options.strip || options.reject); | ||
this.shouldStrip = options && (options.strip || options.reject); | ||
this.shouldReject = options && options.reject; | ||
this.returnTransformed = (options && 'transform' in options) ? options.transform : true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its not particularly good to use the delete
thing (specially if that object doesn't start from your code. A recommendation for this body:
const { transform, ...validatorOptions } = options;
this.returnTransformed = transform != null ? transform : true; // I don't agree with a default true value, as I said
this.validatorOptions = options;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't want the transform
property to get passed to the validate
function. What harm could this delete
do? If you have a more elegant solution to remove it from the options
object let me know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As for the transform
default value, I think we should wait for more reviews. You wanted to be able to get the plain object value and with transform: false
you are 😉
If I set this default value to false
we're getting back to @Query(new ValidationPipe()) pagination?: PaginationOptionsDto
not being an actual PaginationOptionsDto
which is not the expected behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first line of my snippet separates the transform
property from the validatorOptions
(it doesnt have transform
.
About the typing disparity, while I prefer pojo objects, you are right: the typescript compiler will let you call methods on the object and the default implementation should comply with that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I didn't know about this use of the spread operator and I must say it looks really nice :D
src/common/pipes/validation.pipe.ts
Outdated
if (errors.length > 0) { | ||
throw new BadRequestException(errors); | ||
} | ||
return this.shouldTransform ? entity : value; | ||
return this.returnTransformed ? entity |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe not a lot of people are keen on using nested ternary operators.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More so, why do you need the second one? The classToPlain(entity)
part. And I don't get too much requiring Object.keys
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I like nested ternaries.
- The second one is needed when you want to get the non-transformed stripped value. (basically, you get the stripped entity as a plain object)
Object.keys
is used to check if the user has provided someclass-validator
options which means the returned object is a mutated version ofvalue
(stripped properties, etc.)
I may help you with the tests tomorrow. |
initialize the properties in a more elegant way
Instead of using a constructor, why not use a decorator maybe @dto() passing options to this decorator, this way we can define just a single new ValidationPipe() using nest global pipe. Less code to write 😀 |
Hi everyone, |
@chanlito for learning purposes, how would you create that decorator: What it decorates, how it works and why it works? Haven't really authored decorators myself. |
@ShadowManu I was thinking of something like the following without using the constructor: @Dto() // or @Dto({ transform: true, strip: true, reject: false })
export class CreateCatsDto {
@IsNotEmpty()
@IsString()
readonly name: string;
// @Transform() or @Type()
readonly color: string;
} Now we can strip unwanted incoming fields, validate and tranform. With reflect metadata we can achieve the Then somewhere in our nest bootstrap, we can do the following. ...
app.useGlobalPipe(new ValidationPipe(this.reflector))
... Inside the validation pipe we could check if the class has been decorated by |
@chanlito |
@fwoelffel nice job dude. |
@fwoelffel |
@chanlito How could one activate the stripping for one single controller endpoint and not the other endpoints if the |
@fwoelffel this PR looks like a great job 🙂 thanks man, really appreciate it. I'm gonna review today. Question: would you want to update a docs as well? At the moment, we only have a mention that |
@kamilmysliwiec Yup caaan do |
cool @fwoelffel 😃 This PR is ready to merge 🎉 looking forward to it |
Great job @fwoelffel, thanks 🎉 Looking forward to those small fixes in the docs PR! |
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
TLDR; This PR adds some options to the
ValidationPipe
.Motivation
Take the following code base:
Now if you take a look at the console output, you will see that:
pagination
is not an instance ofPaginationOptionsDto
which can cause issues if you want to define some methods in this class.pagination
is an object (any
) equal to{name: 'James', offset: 20, limit: 10}
.Weird, uh? I know that "since it's a validation pipe it should only perform validation", but still... weird.
Proposal
This PR adds the support of the following options to the
ValidationPipe
:transform
: The pipe will return an instance of the expected type.strip
: The pipe will return an instance of the expected type without the extra properties (ie: thename
property on the example above). This option implicitly setstransform
to true.reject
: The pipe will throw an error if it finds any unexpected property. This option implicitly setstransform
andstrip
to true.This is what it looks like:
This makes use of the
class-validator
latest release which provides the whitelisting feature.