Reputation: 125
In my project, I have a large object with many properties. Each property has it's own distinct validations that are performed on it using class validator decorators. Each property for the class is described as a mixin. However, I noticed that when applying mixins to the base class, only the mixin that was passed last has its decorators run for validation.
For example, we have:
export class Property {
public async validate (): Promise<string[]> {
const result = await validate(this)
return result
}
}
export class Name extends Property {
@IsDefined()
@IsString()
@Length(5, 255)
name: string
}
export class Description extends Property {
@IsDefined()
@IsString()
@Length(16, 1000)
description: string
}
Each property, when unit tested, validates itself correctly.
When creating the class that inherits from the mixins, I am doing the following:
/**
* Applies the mixins to a class. Taken directly from the Typescript documentation.
*/
function applyMixins (derivedCtor: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
Object.defineProperty(derivedCtor.prototype, name, Object.getOwnPropertyDescriptor(baseCtor.prototype, name))
})
})
}
export class Foo {
public async validate (): Promise<any> {
const result = await validate(this)
return result
}
}
export interface Foo extends Name, Description { }
applyMixins(Foo, [Name, Description])
But, when creating an instance of Foo
and calling .validate
on the instance, we only get errors for Description.
Is there some different way to apply the mixins in order to get validations on all of the mixed in properties?
Upvotes: 3
Views: 1925
Reputation: 13539
That happens because сlass-validator uses prototype to detect validation rules, and we need to copy the rules to the prototype of derived ctor.
We can do that like this:
/**
* Applies the mixins to a class, but with class validator constraints.
*/
function applyMixinsWithValidators (derivedCtor: any, baseCtors: any[]) {
const metadata = getMetadataStorage() // from class-validator
// Base typescript mixin implementation
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
Object.defineProperty(derivedCtor.prototype, name, Object.getOwnPropertyDescriptor(baseCtor.prototype, name))
})
})
baseCtors.forEach(baseCtor => {
// Get validation constratints from the mixin
const constraints = metadata.getTargetValidationMetadatas(baseCtor.prototype.constructor, '')
for (const constraint of constraints) {
// For each constraint on the mixin
// Clone the constraint, replacing the target with the the derived constructor
let clone = {
...constraint,
target: derivedCtor.prototype.constructor
}
// Set the prototype of the clone to be a validation metadata object
clone = Object.setPrototypeOf(clone, Object.getPrototypeOf(constraint))
// Add the cloned constraint to class-validators metadata storage object
metadata.addValidationMetadata(clone)
}
})
}
Upvotes: 2