I was doing my nestjs project and i was finding the class-validation very annoying. I was repeating the same classes over and over again with the same decorators.
ex:
export class DTO1 {
@IsDefined()
@IsString()
name: string;
@IsDefined()
@Type(() => Number)
@IsInt()
client: number;
@IsDefined()
@IsIn(['value1', 'value2'])
role: 'value1' | 'value2';
}
export class DTO2 {
@IsDefined()
@IsNotEmpty()
@IsString()
name: string;
@IsDefined()
@IsNotEmpty()
@Type(() => Number)
@IsInt()
team: number;
}
export class DTO3 {
@IsDefined()
@IsNotEmpty()
@Type(() => Number)
@IsInt()
team: number;
@IsDefined()
@IsNotEmpty()
@Type(() => Number)
@IsInt()
client: number;
}
export class DTO4 {
@IsDefined()
@IsNotEmpty()
@IsIn(['value1', 'value2', 'value3', 'value4'])
mode: 'value1' | 'value2' | 'value3' | 'value4';
@IsDefined()
@IsNotEmpty()
@Type(() => Number)
@IsInt()
team: number;
}
A lot of repetition because you have alot of query that are the same from dto to dto but do not appear consistantly enough for you make a base class then extending it
export class Base {
@IsDefined()
@IsString()
elemEveruoneHas: string;
}
export class DTO1 extends Base {
@IsDefined()
@IsNotEmpty()
@Type(() => Number)
@IsInt()
id: number;
}
what i wanted was to create a few classes for reacuring elements then have a way to generate them into the specific DTO.
it would look like :
export class DTO_name {
@IsDefined()
@IsNotEmpty()
@IsString()
name: string;
}
export class DTO_team {
@IsDefined()
@IsNotEmpty()
@Type(() => Number)
@IsInt()
team: number;
}
export class DTO_client {
@IsDefined()
@IsNotEmpty()
@Type(() => Number)
@IsInt()
client: number;
}
export class DTO1 extends DTO(DTO_name, DTO_client) {
@IsDefined()
@IsIn(['value1', 'value2'])
role: 'value1' | 'value2';
}
export class DTO2 extends DTO(DTO_name, DTO_team) {}
export class DTO3 extends DTO(DTO_team, DTO_client) {}
export class DTO4 extends DTO(DTO_team) {
@IsDefined()
@IsNotEmpty()
@IsIn(['value1', 'value2', 'value3', 'value4'])
mode: 'value1' | 'value2' | 'value3' | 'value4';
}
Right now i have something like this which is better but ugly :
type Constructor<T = {}> = new (...args: any[]) => T;
export const DTO_name = <TBase extends Constructor>(Base: TBase = class {} as TBase) => {
class _ extends Base {
@IsDefined()
@IsNotEmpty()
@IsString()
name: string;
}
return _;
}
export const DTO_team = <TBase extends Constructor>(Base: TBase = class {} as TBase) => {
class _ extends Base {
@IsDefined()
@IsNotEmpty()
@Type(() => Number)
@IsInt()
team: number;
}
return _;
}
export const DTO_client = <TBase extends Constructor>(Base: TBase = class {} as TBase) => {
class _ extends Base {
@IsDefined()
@IsNotEmpty()
@Type(() => Number)
@IsInt()
client: number;
}
return _;
}
export class DTO1 extends DTO_name(DTO_client()) {
@IsDefined()
@IsIn(['value1', 'value2'])
role: 'value1' | 'value2';
}
export class DTO2 extends DTO_name(DTO_team()) {}
export class DTO3 extends DTO_team(DTO_client()) {}
export class DTO4 extends DTO_team() {
@IsDefined()
@IsNotEmpty()
@IsIn(['value1', 'value2', 'value3', 'value4'])
mode: 'value1' | 'value2' | 'value3' | 'value4';
}
Does any know how i could improve this so that i dont have to recreate the entire setup for each element class and maybe if we could have have a function that generate the inherited class that does the chaining inheritance so we could have the DTO(Test1, Test2, Test3) ?
Not in the exact form you were wanting, but you could achieve this with Nest's @nestjs/mapped-types
's IntersectionType
, doing something like export class DTO extends IntersectionType(Class1, IntersectionType(Class2, Class3)) {}
(and of course nesting as necessary)