node.js - @UsePipes(ValidationPipe) generiklerle (soyut kontrolcü) çalışmıyor
Nest.js ve MySQL kullanarak bir API geliştiriyorum. Çeviklik ve DRY ilkeleri nedeniyle, belirli bir Varlık (TypeORM'den) için tüm temel CRUD uç noktalarını ayarlayan bir OOP yapısı oluşturuyorum. Ana hedef, farklı Varlıklar için aynı ortak yöntemleri tekrar yazmaktan kaçınmaktır.
Bunu başarmak için TypeScript Generics ile bir strateji kullanıyorum. Her Entity için tüm ortak dosyaları (.controller.ts, .service.ts, .module.ts, .entity.ts) hâlâ oluşturmam gerekiyor, ama metodlarını yazmak zorunda değilim. Bunun yerine sadece iki sınıfı genişletiyorum: RestController ve RestService. Bu sınıflar zaten ortak metodları implemente ediyor, ama TypeORM'un Service'e doğru repository'i enjekte edebilmesi için bazı T tiplerini parametre olarak geçmem gerekiyor.
Sorun: @UsePipes dekoratörü, ebeveyn sınıfta (RestController) kullandığımda çağrılmıyor, ancak alt sınıfta (SubcategoriesController) RestController oluşturma metodunu geçersiz kıldığımda normal şekilde çalışıyor.
rest.controller.ts:
import { Get, Post, Body, Param, Put, Delete, UsePipes, ValidationPipe } from '@nestjs/common';
import { RestService } from './rest.service';
import { ObjectLiteral } from 'typeorm';
export abstract class RestController<T, C = T, U = T> {
constructor(protected service: RestService<T, C, U>) {}
@Get()
async index(): Promise<T[]> {
return this.service.getAll();
}
@Post('create')
@UsePipes(ValidationPipe) //HERE!
async create(@Body() data: C): Promise<T> {
return this.service.create(data as C);
}
}
rest.service.ts:
import { Repository, UpdateResult, DeleteResult, Entity, DeepPartial } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
export interface RestClass<T, C = T, U = T> {
// Properties
repository: Repository<T>;
// Default Methods
getAll(): Promise<T[]>;
create(model: T | C | U): Promise<T>;
}
export class RestService<T, C = T, U = T> implements RestClass<T, C, U> {
constructor(
public repository: Repository<T>,
) {}
getAll = async () => {
return await this.repository.find({relations:: this.repository.metadata.ownRelations.map(r => r.propertyName)});
}
create = async (model: C) => {
return await this.repository.save(model as C);
}
}
Ve işte yukarıdaki sınıfları genişleterek gerçek bir varlık uç noktalarını nasıl ayarladığım:
altkategoriler.controller.ts:
import { Controller, Get, Post, UsePipes, ValidationPipe, Body } from '@nestjs/common';
import { SubcategoriesService } from './subcategories.service';
import { Subcategory } from './subcategory.entity';
import { RestController } from '../rest.controller';
import { CreateSubcategoryDTO } from './dto/createSubcategory.dto';
//NOTE THE TYPE PARAMS IN <>
@Controller('subcategories')
export class SubcategoriesController extends RestController<Subcategory, CreateSubcategoryDTO> {
constructor(public service: SubcategoriesService) {
super(service);
}
}
altkategoriler.servis.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Subcategory } from './subcategory.entity';
import { Repository } from 'typeorm';
import { RestService } from '../rest.service';
import { CreateSubcategoryDTO } from './dto/createSubcategory.dto';
//NOTE THE TYPE PARAMS IN <>
@Injectable()
export class SubcategoriesService extends RestService<Subcategory, CreateSubcategoryDTO> {
constructor(
@InjectRepository(Subcategory) repository: Repository<Subcategory>,
) {
super(repository);
}
}
createSubcategory.dto.ts
import { IsString, Length, IsInt } from 'class-validator';
export class CreateSubcategoryDTO {
@IsString()
@Length(5, 60)
name: string;
@IsString()
@Length(0, 140)
summary: string;
@Length(0, 140)
icon: string;
@IsInt()
category: number;
}
Ana sınıfın 3 tür parametre kabul ettiğini görebilirsiniz:
T: varlıkC: CreateDTO, isteğe bağlıU: UpdateDTO, isteğe bağlı
Yukarıdaki kod, uç noktaları mükemmel bir şekilde oluşturuyor, ancak beklendiği gibi ValidationPipe tarafından /create'deki yükü doğrulamıyor.
Eğer SubcategoriesController içinde create metodunu yeniden yazarsam ve oraya UsePipes eklersem, çalışıyor!
Bence bu, Nests yaşam döngüsünü ilgilendiren bir hata olabilir; bu, soyut sınıflarda Pipe kullanımını desteklemeyebilir.
Birinin bir fikri var mı?
P.S. Hiçbir derleme hatası, lint uyarısı veya çalıştırma zamanı istisnası yoktur.
Cevap #1
Bunun bir çözümü, denetleyicileriniz için, body parametreniz sınıfını bir argüman olarak alacak ve ardından bunu şu şekilde özel bir ValidationPipe uzantısına iletecek bir fabrika fonksiyonu oluşturmaktır:
@Injectable()
export class AbstractValidationPipe extends ValidationPipe {
constructor(
options: ValidationPipeOptions,
private readonly targetTypes: {
body?: Type<any>;
query?: Type<any>;
param?: Type<any>;
custom?: Type<any>;
},
) {
super(options);
}
async transform(value: any, metadata: ArgumentMetadata) {
const targetType = this.targetTypes[metadata.type];
if (!targetType) {
return super.transform(value, metadata);
}
return super.transform(value, { ...metadata, metatype: targetType });
}
}
export interface IController<T> {
hello(body: T);
}
export function Factory<T>(bodyDto: ClassType<T>): ClassType<IController<T>> {
@Controller()
class ControllerHost<T> implements IController<T> {
@Post()
@UsePipes(new AbstractValidationPipe({whitelist: true, transform: true}, {body: bodyDto}))
hello(@Body() body: T) {
return "hello"
}
}
return ControllerHost;
}
export class MyDto {
@Expose()
@IsDefined()
@IsString()
hello: string;
}
export class AppController extends Factory<MyDto>(MyDto) {}
Reflection ile jeneriklerle ilgili bir bilgi mevcut değil, bu yüzden standart ValidationPipe metadata.metatype'ten anlamlı bir bilgi alamıyor. Bunu, metadata.metatype içeriğini geçersiz kılmak için kullanabileceği isteğe bağlı tür parametrelerini sağlayarak çözülüyorum. Bu, normal kullanım durumları (jeneriksiz) için de çalışacak güzel bir özellik sunuyor. Eğer query veya param değerlerini de geçersiz kılmak isterseniz, sadece targetTypes parametresi aracılığıyla uygun değerleri sağlayın.
Cevap #2
muhtemelen InheritValidation dekoratörünü arıyorsunuz
https://github.com/typestack/class-validator/blob/master/test/functional/inherited-validation.spec.ts
