Global validation pipe overriden in whole Resolver if overriden in single method
See original GitHub issueBug Report
Current behavior
I have enabled validation and transformation globally via
app.useGlobalPipes(new ValidationPipe({ transform: true }));
This works just fine.
Now, I wanted to validate a simple string property without having to create a class, so I created a custom validation pipe IsNotEmptyStringPipe
and used it like this inside a GraphQL mutation:
@Mutation()
async mergeGroups(
@Args('name', IsNotEmptyStringPipe) name: string,
@Args('groupIds') groupIds: string[]
): Promise<Group> {
return this.graphqlService.mergeGroups({ name, groupIds });
}
This also works just fine.
BUT: After some debugging, I noticed that this causes the globally enabled validation pipe to disappear on the other methods of the same resolver.
This means in this mutation:
@Mutation()
async updateGroup(
@Args('id') id: string,
@Args('group') group: UpdateGroupInput
): Promise<Group> {
return this.graphqlService.updateGroup({ id, group });
}
group
is no longer transformed to a UpdateGroupInput
instance as long as IsNotEmptyStringPipe
exists in the other method.
Input Code
main.ts
// App setup
// ...
app.useGlobalPipes(new ValidationPipe({ transform: true }));
// ...
group.resolver.ts
@Resolver('Group')
export class GroupResolver {
constructor(private graphqlService: GraphqlService) {}
@Mutation()
async updateGroup(
@Args('id') id: string,
@Args('group') group: UpdateGroupInput
): Promise<Group> {
return this.graphqlService.updateGroup({ id, group });
}
@Mutation()
async mergeGroups(
@Args('name', IsNotEmptyStringPipe) name: string,
@Args('groupIds') groupIds: string[]
): Promise<Group> {
return this.graphqlService.mergeGroups({ name, groupIds });
}
}
UpdateGroupInput.dto.ts
export class UpdateGroupInput {
@IsNotEmptyString()
@IsOptional()
name?: string;
@Transform(({ value }) => (value.trim() === '' ? null : value.trim()))
@IsOptional()
country?: string;
@Transform(({ value }) => (value.trim() === '' ? null : value.trim()))
@IsOptional()
contract?: string;
@Transform(({ value }) => (value.trim() === '' ? null : value.trim()))
@IsOptional()
website?: string;
}
validation.ts
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';
import {
ValidationOptions,
registerDecorator,
isNotEmpty,
ValidationArguments,
isString,
} from 'class-validator';
export function IsNotEmptyString(validationOptions?: ValidationOptions) {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
return (object: unknown, propertyName: string) => {
registerDecorator({
name: 'isNotEmptyString',
target: object.constructor,
propertyName,
options: validationOptions,
validator: {
validate: (value: any): boolean =>
isString(value) && isNotEmpty(value.trim()),
defaultMessage: (validationArguments?: ValidationArguments): string =>
`${validationArguments.property} should be a non-empty string`,
},
});
};
}
@Injectable()
export class IsNotEmptyStringPipe implements PipeTransform {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
transform(value: any, metadata: ArgumentMetadata): string {
if (!(isString(value) && isNotEmpty(value.trim()))) {
throw Error(`${metadata.data} should be a non-empty string`);
}
return value;
}
}
Expected behavior
Overriding a globally enabled validation pipe in a single method should not cause the globally enabled validation pipe to disappear in other methods. This is very confusing behaviour and took me a whole day to debug why my input is no longer transformed.
Possible Solution
Environment
Nest version: 7.6.0
Node version: v16.2.0
Issue Analytics
- State:
- Created 2 years ago
- Comments:9 (4 by maintainers)
Okay, my minimum reproduction repo has concluded that this indeed works. So I can use
and a custom pipe on a method without the validation disappear on the other method …
Mhh, have to check what is different in my code …
Okay, that’s one of the common culprits. Like Kamil said, if you want to discuss this further, I’ll be happy to on our Discord. Usually quicker and easier responses there in my opinion as well