I want to combine multiple classes for reusability and consistency amongst backend and frontend. Something like:
import {
IsEmail,
IsString,
MaxLength,
MinLength,
validateSync
} from "class-validator";
class UserUsername {
@IsString()
@MinLength(3)
@MaxLength(10)
username!: string;
}
class UserEmail {
@IsEmail()
email!: string;
}
class UserPassword {
@IsString()
@MinLength(8)
password!: string;
}
class UserSecret {
@IsString()
secret!: string;
}
class User /* extends UserEmail, UserUsername, UserSecret */ {}
class UserDto /* extends UserEmail, UserUsername, UserPassword */ {}
const userDto = new UserDto();
userDto.username = "noerror";
userDto.email = "error";
userDto.password = "error";
console.log(validateSync(userDto).toString());
Is something similar anyway possible?
Note: I do not mean types only like TypeScript's &
. The main purpose is to reuse class validation.
export class User {
@IsUUID()
id!: string;
@IsNotEmpty()
@IsString()
@MinLength(3)
username!: string;
@IsEmail()
email!: string;
@IsNotEmpty()
@IsString()
secret!: string;
@IsNotEmpty()
@IsAlpha()
firstName!: string;
@IsNotEmpty()
@IsAlpha()
lastName!: string;
@IsBoolean()
isEmailVerified!: boolean;
}
export class UserDto {
@IsUUID()
id!: string;
@IsNotEmpty()
@IsString()
@MinLength(3)
username!: string;
@IsEmail()
email!: string;
secret?: never;
@IsNotEmpty()
@IsAlpha()
firstName!: string;
@IsNotEmpty()
@IsAlpha()
lastName!: string;
@IsBoolean()
isEmailVerified!: boolean;
}
export class SignUpUserDto {
id?: never;
@IsNotEmpty()
@IsString()
@MinLength(3)
username!: string;
@IsEmail()
email!: string;
secret?: never;
@IsNotEmpty()
@IsAlpha()
firstName!: string;
@IsNotEmpty()
@IsAlpha()
lastName!: string;
isEmailVerified?: never;
}
export class UpdateUserDto {
id?: never;
@IsOptional()
@IsNotEmpty()
@IsString()
@MinLength(3)
username?: string;
@IsOptional()
@IsEmail()
email?: string;
secret?: never;
@IsOptional()
@IsNotEmpty()
@IsAlpha()
firstName?: string;
@IsOptional()
@IsNotEmpty()
@IsAlpha()
lastName?: string;
isEmailVerified?: never;
}
export class SignInUserDto {
@IsString()
@IsNotEmpty()
username!: string;
@IsNotEmpty()
@IsString()
password!: string;
}
Thanks to Filip Kaštovský who pointed me to this doc: How Does A Mixin Work?
I got to solve it this way:
import { IsString, IsUUID, MinLength, validateSync } from 'class-validator';
type Constructor<T = {}> = new (...args: any[]) => T;
export function WithUserId<TBase extends Constructor>(Base: TBase) {
class UserId extends Base {
@IsUUID()
id!: string;
}
return UserId;
}
export function WithUserUsername<TBase extends Constructor>(Base: TBase) {
class UserUsername extends Base {
@IsString()
@MinLength(3)
username!: string;
}
return UserUsername;
}
export function WithUserSecret<TBase extends Constructor>(Base: TBase) {
class UserSecret extends Base {
@IsString()
secret!: string;
}
return UserSecret;
}
class User extends WithUserId(WithUserUsername(WithUserSecret(class {}))) {}
const user = new User();
user.id = "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d";
user.username = "a";
user.secret = "a";
console.log(validateSync(user).toString());
EDIT: I cloned @nestjs/mapped-types and modified it to work in both the browser and nodejs. I wanted to publish it but I do not know a lot about publishing and whether it is actually going to work. It works fine in my set-up with Nx because I used Nx to create the library.