Wbudowane dekoratory class-validator pokrywają większość przypadków. Ale co jeśli potrzebujesz specjalistycznego strażnika, który sprawdza unikalne reguły twojego imperium? Czas nauczyć się tworzyć własnych strażników bramy!
Najbardziej elastyczny sposób to stworzenie własnej klasy walidatora:
1import {
2 ValidatorConstraint,
3 ValidatorConstraintInterface,
4 ValidationArguments,
5} from 'class-validator';
6
7@ValidatorConstraint({ name: 'isRomanName', async: false })
8export class IsRomanNameConstraint implements ValidatorConstraintInterface {
9 validate(value: string, args: ValidationArguments): boolean {
10 // Sprawdź czy imię zaczyna się od wielkiej litery
11 // i zawiera tylko litery łacińskie
12 return /^[A-Z][a-z]+$/.test(value);
13 }
14
15 defaultMessage(args: ValidationArguments): string {
16 return 'Imię ($value) nie jest poprawnym imieniem rzymskim!';
17 }
18}Klasa musi implementować interfejs
ValidatorConstraintInterface z dwiema metodami:validate() — zwraca true jeśli wartość jest poprawnadefaultMessage() — zwraca komunikat błędu1import { Validate } from 'class-validator';
2
3export class CreateLegionaryDto {
4 @Validate(IsRomanNameConstraint)
5 name: string;
6}Zamiast używać
@Validate(), możesz stworzyć własny dekorator:1import {
2 registerDecorator,
3 ValidationOptions,
4 ValidatorConstraint,
5 ValidatorConstraintInterface,
6} from 'class-validator';
7
8@ValidatorConstraint({ async: false })
9class IsLegionCodeConstraint implements ValidatorConstraintInterface {
10 validate(value: string): boolean {
11 return /^LEG-[IVXLCDM]+-\d{3}$/.test(value);
12 }
13
14 defaultMessage(): string {
15 return 'Kod legionu musi mieć format LEG-[cyfry rzymskie]-[3 cyfry]';
16 }
17}
18
19export function IsLegionCode(validationOptions?: ValidationOptions) {
20 return function (object: object, propertyName: string) {
21 registerDecorator({
22 target: object.constructor,
23 propertyName: propertyName,
24 options: validationOptions,
25 constraints: [],
26 validator: IsLegionCodeConstraint,
27 });
28 };
29}Teraz możesz używać go jak każdego innego dekoratora:
1export class CreateLegionaryDto {
2 @IsLegionCode({ message: 'Niepoprawny kod legionu!' })
3 legionCode: string;
4}Czasem walidacja wymaga dostępu do bazy danych (np. sprawdzenie czy nazwa jest unikalna). Wtedy tworzysz asynchroniczny walidator:
1@ValidatorConstraint({ name: 'isUniqueRank', async: true })
2export class IsUniqueRankConstraint implements ValidatorConstraintInterface {
3 constructor(private readonly legionService: LegionService) {}
4
5 async validate(rank: string): Promise<boolean> {
6 // Sprawdź w bazie czy ranga jest dostępna
7 const existing = await this.legionService.findByRank(rank);
8 return !existing;
9 }
10
11 defaultMessage(): string {
12 return 'Ta ranga jest już zajęta w legionie!';
13 }
14}Uwaga: Asynchroniczne walidatory wymagają integracji z systemem Dependency Injection NestJS poprzez
useContainer.Możesz tworzyć walidatory, które sprawdzają relacje między polami:
1@ValidatorConstraint({ async: false })
2class IsAgeValidForRankConstraint implements ValidatorConstraintInterface {
3 validate(value: any, args: ValidationArguments): boolean {
4 const object = args.object as CreateLegionaryDto;
5 // Legatus musi mieć co najmniej 30 lat
6 if (object.rank === 'legatus' && object.age < 30) {
7 return false;
8 }
9 // Centurio musi mieć co najmniej 25 lat
10 if (object.rank === 'centurio' && object.age < 25) {
11 return false;
12 }
13 return true;
14 }
15
16 defaultMessage(args: ValidationArguments): string {
17 return 'Wiek nie odpowiada wymaganej randze!';
18 }
19}Dzięki custom validatorom masz pełną kontrolę nad logiką walidacji. Twoi strażnicy bramy mogą teraz sprawdzać dowolne, nawet najbardziej złożone reguły!