@UsePipes(ValidationTube)不使用泛型(抽象控制器) [英] @UsePipes(ValidationPipe) not working with generics (abstract controller)
问题描述
我正在使用Nest.js和MySQL构建API。由于敏捷性和干性原则,我正在创建一个OOP结构,它为给定的实体(来自TypeORM)设置所有基本的CRUD端点。主要目标是避免为不同实体编写相同的通用方法。
为了实现这一点,我使用了类型脚本泛型的策略。我仍然需要为每个实体创建所有公共文件(.controller.ts
、.service.ts
、.module.ts
、.entity.ts
),但我不必编写其方法。相反,我只扩展了两个类:RestController
和RestService
。这些类已经实现了公共方法,但我必须将一些T类型作为参数传递,以便TypeORM可以将正确的存储库注入服务。
问题:当我在父类(RestController
)中使用@UsePipes
修饰器时,它没有被调用,但当我在子类(SubcategoriesController
)中覆盖deRestControllercreate方法时,它可以正常工作。
REST.Controler.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);
}
}
下面是我如何设置真实实体终结点,扩展上面的类:
subcategories.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);
}
}
subcategories.service.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;
}
您可以看到父类接受3种类型的参数:
T
:实体C
:CreatedTo,可选U
:更新DTO,可选
上面的代码完美地创建了终结点,但是它没有验证/create
中的负载,正如验证管道所预期的那样。
如果我重写子类别控制器中的Create方法,并将UsePipes添加到那里,它会起作用!
我认为这可能是引用嵌套生命周期的错误,它可能不支持在抽象类中使用管道。
有人有主意吗?
P.S。没有转换错误、LINT警告或运行时异常。
推荐答案
解决方案之一是为控制器创建一个工厂函数,该函数将接受Body Param类作为参数,然后将其传递给如下所示的自定义ValidationTube扩展:
@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) {}
没有关于反射的泛型的信息,因此标准ValidationTube没有从metadata.metatype
获得任何有意义的信息。我通过向它提供可选的类型参数来解决这个问题,它可以使用这些参数来覆盖metadata.metatype
的内容。它有一个很好的功能,它也可以用于正常的用例(没有泛型)。如果要覆盖query
或param
,只需通过targetTypes
参数提供适当的值。
这篇关于@UsePipes(ValidationTube)不使用泛型(抽象控制器)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!