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?
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)
}
})
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With