class-validator
class-validator copied to clipboard
feat: add option to define class level custom decorators
Is there support for validation on a class level and not just property level? I want to do DB lookups but issue a single query vs one per field.
Can you elaborate please, give me some theoretical example code?
Here's how Symfony does it in the PHP world:
https://symfony.com/doc/current/reference/constraints/UniqueEntity.html
In the class-validator
world:
@IsUnique(WidgetRepository, ["name", "tag"])
export class Widget {
@IsString()
name: string;
@IsString()
@IsLowercase()
tag: string;
}
@IsUnique
would use something like typedi
to get the repository from the container, and the array would be the fields that are to be unique.
I could think of a few more examples that would be nice to compare all fields at once. The main example is to do a single query utilizing the object after all field validations passed.
Have you seen TypeORM? It's exactly what you are looking for.
I'm not using TypeORM. I'm using MongoDB at the moment and they are pretty far behind in driver support. And not a lot of movement in MongoDB within TypeORM (issues grow, nothing getting done).
I'd like to push it to a validation library. The Symfony library I referenced above is a class validation library such as this one with support for validating DoctrineORM objects.
Edit: I'm also using https://github.com/19majkel94/type-graphql which has built-in support for this library.
Then you can create such functionality here via
@NoNameProvided I created a decorator already, but it's bound to the field and not the class, so I lack access to multiple fields.
Edit: I might be able to access multiple fields, but if so, it wouldn't be clear what the validation is trying to do.
I created a decorator already, but it's bound to the field and not the class, so I lack access to multiple fields.
You can create multi-field validations. Here is an example: https://github.com/typestack/class-validator/issues/145#issuecomment-373949845
I might be able to access multiple fields, but if so, it wouldn't be clear what the validation is trying to do.
In your example, it totally makes sense to put it on the unique property instead of the class.
Yes, in my fake example use-case, perhaps it can be solved "cleanishly" (although the two fields are not related at all, so IMO it's not clean and is actually more confusing).
Another example where it makes more sense on a class level:
@IsValidLocation(['address1', 'address2', 'city', 'state', 'zip'])
export class User {
address1: string;
address2: string;
city: string;
state: string;
zip: string;
}
or
@IsValidLogin()
export class Auth {
username: string;
password: string;
}
// This may do a true lookup on the image, verify image size, etc, etc... I don't see this being clean on a field level.
@IsImageValid(({ baseUrl, name, extension }) => `${baseUrl)${name}${extension}`)
export class Image {
@IsURL()
baseURL: string;
@IsString()
name: string;
@IsValidExtension()
extension: string;
}
Others do it:
- https://docs.jboss.org/hibernate/validator/4.1/reference/en-US/html/validator-usingvalidator.html#example-class-level
- https://symfony.com/doc/current/reference/constraints/UniqueEntity.html
I see where if you're comparing a field to another field, then field level validation works and makes sense, but if the fields aren't being compared and are being used as a final validation constraint, then it'd make more sense on the class level. It can also be an area where you may do heavier validations. You can run your simple field level validations first, then do things like DB lookups, external requests, etc, on a class level so that simple validations can fail first.
As of now, I'd never do any of these examples with class-validator and am forced to do these types of validations hardcoded in my app. It'd be awesome to have it all in one place.
This makes a lot of sense to me - I was thinking of exactly the same thing (specifically wanting to do Joi's with
and or validators.
Anything on this issue? Class level validators are very useful.
Up, for example I have this case:
// Having at least one of the two set.
@ClassLevelValidator(post => post.articleId != null || post.externalLink != null)
export class PostDto {
@IsNotEmpty()
@IsString()
title: string;
@IsDefined()
@IsString()
shortDescription: string;
@IsOptional()
@IsInt()
articleId: number; // if set I know this post relate to an article entity, that I can retrieve using the id
@IsOptional()
@IsUrl()
externalLink: string; // if set I know this post relate to an external link.
}
// if both are set or none are set then this is a wrong entry.
It do exist in Java https://stackoverflow.com/questions/2781771/how-can-i-validate-two-or-more-fields-in-combination
Generic Unique Constrain For any Column I hope we can Use below code like
File Name:- isUnique.ts
@ValidatorConstraint({ async: true }) export class IsUniqueConstraint implements ValidatorConstraintInterface { validate(columnNameValue: any, args: ValidationArguments) { let columnNameKey = args.property; let tableName = args.targetName; return getManager().query("SELECT * FROM " + tableName + " WHERE " + columnNameKey + " = ?", [columnNameValue]).then(user => { if (user[0]) return false; return true; }); } } export function IsUnique(validationOptions?: ValidationOptions) { return function (object: Object, propertyName: string) { registerDecorator({ target: object.constructor, propertyName: propertyName, options: validationOptions, constraints: [], validator: IsUniqueConstraint }); }; }
Can be use in Typeorm Model Users as
@Column() @IsEmail({}, { message: 'Invalid email Address.' }) @IsUnique({ message: "Email $value already exists. Choose another name." }) email: string;
@Column() @Length(4, 20) @IsUnique({ message: "User Name $value already exists. Choose another name." }) username: string;
So it will be common for any model table.
I was also hoping for a class-level validator, any news about this?
Voting up for class level decorators. Would be a useful feature.
@NoNameProvided this is pretty wanted.
Sure. We are open to proposals how this may works. But for now this is future request.
Hey, I made a tiny package that solves this exact problem, I tried to remain as close as possible to class-validator API https://github.com/astahmer/entity-validator/
my need for "class level" is to validate across multiple properties at the same time versus just one... i was concerned when i saw this issue still open... yet issue #759 referencing the docs Custom validation decorators - @IsLongerThan() example shows how to reference the any of the current objects properties inside the validator so i'm hopeful i can adapt that to my needs... just wanted to post in case it helps others
Any updates on this issue?
I have created a workaround for this issue, but I don't know if it is a good approach.
It works by creating a property named _classValidationProperty
on target prototype and registering a property decorator to it.
Inside this property decorator, you have access to object been validated, so you can do any validations using multiple properties of the object.
I also created a helper function to create the decorators.
import { registerDecorator, ValidationArguments } from 'class-validator';
@ValidateFoo()
export class Foo {
a: number;
b: number;
c: number;
}
interface Class {
new(...args: any[]): {};
}
export function ValidateFoo() {
return createClassValidationDecorator('Foo', (value: any, args: ValidationArguments) => {
// Do whatever validation you need
const foo = args.object as Foo;
return (foo.a === foo.b) && (foo.a === foo.c);
});
}
// Another Example
export function ValidateBar() {
// validateBarFunction is defined somewhere else
return createClassValidationDecorator('bar', validateBarFunction);
}
export function createClassValidationDecorator(validatorName: string, validateFunc: (value: any, args: ValidationArguments) => any) {
return function decorateClass<T extends Class>(target: T) {
console.log('>>> Decorating class:', target.name);
let _classValidatorRegistered = false;
return class extends target {
private readonly _classValidationProperty;
constructor(...args: any[]) {
super(...args);
console.log(`Executing ${target.name}.constructor`);
if (!_classValidatorRegistered) {
_classValidatorRegistered = true;
console.log(`Registering property validator for ${target.name}._classValidationProperty`);
registerDecorator({
name: validatorName,
target: this.constructor,
propertyName: '_classValidationProperty',
// constraints: ['...'],
// options: validationOptions,
validator: {
validate: validateFunc
},
});
}
}
};
}
}
up!
That was very useful @ModestinoAndre, thanks.
I ended up with a similar solution quite close to yours if anyone needs it some day, which works with Nestjs InputType / ObjectType decorators as well:
import {
registerDecorator,
ValidatorConstraintInterface,
} from 'class-validator';
import { MarkRequired } from 'ts-essentials';
type AnyClass = { new (...args: any[]): any };
type PublicConstructor = new (...args: any[]) => any;
/** The response type for registerClassValidator() */
export type ClassValidationDecorator = <T extends AnyClass>(
target: T,
) => PublicConstructor;
/** A helper method to create a new Class-level validation decorator. */
export function registerClassValidator(options: {
name: string;
validator: new () => MarkRequired<
ValidatorConstraintInterface,
'defaultMessage'
>;
constraints: any[];
}): ClassValidationDecorator {
return function decorateClass<T extends AnyClass>(
target: T,
): PublicConstructor {
const { name, validator, constraints } = options;
registerDecorator({
name,
target,
propertyName: target.name,
constraints,
validator,
options: {
always: true,
},
});
return target;
};
}
And then I can use it like this:
/** The class-validator constraint for the RequiredTogether() class decorator */
@ValidatorConstraint()
class RequiredTogetherConstraint implements ValidatorConstraintInterface {
validate(value: undefined, args: ValidationArguments): boolean {
const [requiredFields] = args.constraints;
return (
requiredFields.every((field: string) => field in args.object) ||
requiredFields.every((field: string) => !(field in args.object))
);
}
defaultMessage(args: ValidationArguments): string {
const [fields] = args.constraints;
return 'All fields must exist together or not at all: ' + fields.join(', ');
}
}
/**
* A class level decorator to check that all or none of the fields exist.
* This is useful when using optional fields but must all exist together.
*/
export function RequiredTogether(fields: string[]): ClassValidationDecorator {
return registerClassValidator({
name: 'RequiredTogether',
validator: RequiredTogetherConstraint,
constraints: [fields],
});
}
Need this feature too!!
Marshmallow (a schema validator for Python) also has a feature like this: @validates_schema.
Some use-cases where this is useful:
- you want some fields to be mutually exclusive (only 1 field of 3 can be defined)
- you want to ensure that a
startDate
field comes before anendDate
field - you want to ensure various combinations of fields come together to form something semantically meaningful (various combinations of address fields actually create an address)
- you want to do some validation in your API layer because you're storing raw JSON in a database and don't want to push that validation into a trigger
any updates about this?
Hello any news on this please?