-
Notifications
You must be signed in to change notification settings - Fork 816
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: add decorator to check if two properties match #486
Comments
@vlapo how can we achieve something like this?? |
We do not have validator for this. Feel free open PR. |
Hi guys, any update @vlapo? |
Is there a way to make custom validator get access to other properties |
Hi! I am posting there the solution I found to implement this control. It refers to my question/answer on StackOverflow. I am going to provide a suitable solution for this particular issue. user-create.dto.tsexport class UserCreateDto {
@IsString()
@IsNotEmpty()
firstName: string;
@IsEmail()
emailAddress: string;
@Match('emailAddress')
@IsEmail()
emailAddressConfirm: string;
} match.decorator.tsimport {registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface} from 'class-validator';
export function Match(property: string, validationOptions?: ValidationOptions) {
return (object: any, propertyName: string) => {
registerDecorator({
target: object.constructor,
propertyName,
options: validationOptions,
constraints: [property],
validator: MatchConstraint,
});
};
}
@ValidatorConstraint({name: 'Match'})
export class MatchConstraint implements ValidatorConstraintInterface {
validate(value: any, args: ValidationArguments) {
const [relatedPropertyName] = args.constraints;
const relatedValue = (args.object as any)[relatedPropertyName];
return value === relatedValue;
}
} I managed to solve a similar problem on my personal project. Hope it helps! |
Added a custom message to your solution like this:
|
force the property to exist on the class: export function Match<K extends string, T extends { [$K in K]: any }>(
property: K,
validationOptions?: ValidationOptions,
) {
return (object: T, propertyName: string) => {
registerDecorator({
target: object.constructor,
propertyName,
options: validationOptions,
constraints: [property],
validator: MatchConstraint,
});
};
} |
@mrpharderwijk and @PieroMacaluso, it's works for me. Thanks!! Full code: import {registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface} from 'class-validator';
export function Match(property: string, validationOptions?: ValidationOptions) {
return (object: any, propertyName: string) => {
registerDecorator({
target: object.constructor,
propertyName,
options: validationOptions,
constraints: [property],
validator: MatchConstraint,
});
};
}
@ValidatorConstraint({name: 'Match'})
export class MatchConstraint implements ValidatorConstraintInterface {
validate(value: any, args: ValidationArguments) {
const [relatedPropertyName] = args.constraints;
const relatedValue = (args.object as any)[relatedPropertyName];
return value === relatedValue;
}
defaultMessage(args: ValidationArguments) {
const [relatedPropertyName] = args.constraints;
return `${relatedPropertyName} and ${args.property} don't match`;
}
} |
Thanks everyone for a good solution, but there's a problem with type linting. We could make a spelling mistake like: @Match('passwordd')
// 👆 So I would like to make it more strict import { ClassConstructor } from "class-transformer";
export const Match = <T>(
type: ClassConstructor<T>,
property: (o: T) => any,
validationOptions?: ValidationOptions,
) => {
return (object: any, propertyName: string) => {
registerDecorator({
target: object.constructor,
propertyName,
options: validationOptions,
constraints: [property],
validator: MatchConstraint,
});
};
};
@ValidatorConstraint({ name: "Match" })
export class MatchConstraint implements ValidatorConstraintInterface {
validate(value: any, args: ValidationArguments) {
const [fn] = args.constraints;
return fn(args.object) === value;
}
defaultMessage(args: ValidationArguments) {
const [constraintProperty]: (() => any)[] = args.constraints;
return `${constraintProperty} and ${args.property} does not match`;
}
} Usage: @Match(SignUpDto, (s) => s.password)
passwordConfirm: string; |
Really good! |
@hnbnh Cam you show |
@bato3 I forgot to mention it, you have to import it from "class-transformer" like so: import { ClassConstructor } from "class-transformer"; |
Just a bit improvements, if you like it:
|
This should be in-house I guess. The solution of @PieroMacaluso is very nice. |
We can receive the name of the property like string, to avoid typo we can use keyof: import {
ValidationArguments,
ValidatorConstraint,
ValidatorConstraintInterface,
equals,
} from 'class-validator';
export const Match =
<T>(property: keyof T, options?: ValidationOptions) =>
(object: unknown, propertyName: string) =>
registerDecorator({
target: object.constructor,
propertyName,
options,
constraints: [property],
validator: MatchConstraint,
});
@ValidatorConstraint({ name: 'Match' })
export class MatchConstraint implements ValidatorConstraintInterface {
validate(value: any, validationArguments?: ValidationArguments): boolean {
return equals(validationArguments.constraints[0], value);
}
defaultMessage(validationArguments?: ValidationArguments): string {
return `${validationArguments.constraints[0]} and ${validationArguments.property} does not match`;
}
}
defaultMessage(validationArguments?: ValidationArguments): string {
const [propertyNameToCompare] = validationArguments.constraints;
return `${validationArguments.property} and ${propertyNameToCompare} does not match`;
}
} |
Some slight corrections to @peixotoleonardo's really nice approach above, the complete code looks like this: Validator: import {
equals,
ValidationArguments,
ValidationOptions,
ValidatorConstraint,
ValidatorConstraintInterface,
registerDecorator,
} from 'class-validator';
export const Match =
<T>(property: keyof T, options?: ValidationOptions) =>
(object: any, propertyName: string) =>
registerDecorator({
target: object.constructor,
propertyName,
options,
constraints: [property],
validator: MatchConstraint,
});
@ValidatorConstraint({ name: 'Match' })
export class MatchConstraint implements ValidatorConstraintInterface {
validate(value: any, args?: ValidationArguments): boolean {
const [propertyNameToCompare] = args?.constraints || [];
const propertyValue = (args?.object as any)[propertyNameToCompare];
return equals(value, propertyValue);
}
defaultMessage(args?: ValidationArguments): string {
const [propertyNameToCompare] = args?.constraints || [];
return `${args?.property} does not match the ${propertyNameToCompare}`;
}
} Usage: @Match<SignupDto>('password')
confirmPassword: string; Thanks @hnbnh and other for the solutions, this works like a charm! |
It looks like a "My code is better" challenge. so here is my "more inclusive" solution.
|
First of all, thanks for the awesome validation solution! I use class-validator in my NestJS setup. Now I want to know if and how it is possible to check if two values match with eachother. Let's say I have a
dto
setup like this:The text was updated successfully, but these errors were encountered: