所以这个问题与IntersectionType doesn't work with class-transformer有关。在那里,解决方案是从 nestjs 7 升级到 8(有效地将 @nestjs/swagger 从 4.5.12-next.1 升级到 5.1.2)。唉,这不是故事的结局。我所看到的是,当与 esbuild 捆绑时(即使使用推荐的插件),来自 class-transformer 的装饰器不会被 IntersectionType 继承。但是 class-validate 装饰器是按预期继承的。
要重现,请克隆https://github.com/adrian-grajdeanu/nestjs-issue0 接下来我将包含并讨论来自上述 repo 的代码。
这是一个演示代码:
import "reflect-metadata"
import { ValidationError, BadRequestException, ValidationPipe } from '@nestjs/common';
import { Transform, Type } from "class-transformer";
import { IsBoolean, IsInt, IsString, Max, MaxLength, Min } from "class-validator";
import { IntersectionType } from '@nestjs/mapped-types';
const optionalBooleanMapper = new Map([
['1', true],
['0', false],
]);
function parseOptionalBoolean({ value }: any) {
console.log(`In parseOptionalBoolean: parsing ${value} (${typeof value})`);
const bValue = optionalBooleanMapper.get(value.toLowerCase());
console.log(`In parseOptionalBoolean: parsed ${bValue} (${typeof bValue})`);
return bValue === undefined ? value : bValue;
}
export class Props {
@IsInt()
@Type(() => {
console.log('In @Type');
return Number;
})
@Min(1)
@Max(1024)
count?: number;
@IsBoolean()
@Transform(parseOptionalBoolean)
flag?: boolean;
}
export class Category {
@IsString()
@MaxLength(1024)
category!: String;
}
class Frankenstein extends IntersectionType(Category, Props) {}
const pipe = new ValidationPipe({
transform: true,
whitelist: true,
forbidNonWhitelisted: true,
exceptionFactory: (errors: ValidationError[]) => new BadRequestException({
message: 'Validation failed',
payload: errors.map(error => Object.values(error.constraints || {})).reduce((prev, curr) => [...prev, ...curr], []),
}),
});
async function validate(arg: any) {
try {
const obj = await pipe.transform(arg, {
type: 'query',
metatype: Frankenstein,
});
console.log(obj);
} catch (e) {
console.log(e);
}
}
validate({
count: '10',
category: 'foo',
flag: '0',
});
通过 ts-node 运行上述程序时,一切正常:
$ pnpx ts-node cc.ts
In @Type
In parseOptionalBoolean: parsing 0 (string)
In parseOptionalBoolean: parsed false (boolean)
Frankenstein { count: 10, category: 'foo', flag: false }
$
请注意,@Type 和 @Transform 内的代码已运行并且验证/转换成功。
但是将此 cc.ts 与 esbuild 捆绑在一起。这是捆绑脚本:
import { build } from 'esbuild';
import { esbuildDecorators } from '@anatine/esbuild-decorators';
build({
"plugins": [
esbuildDecorators({
tsconfig: './tsconfig.json',
cwd: process.cwd(),
})
],
"entryPoints": [
"cc.ts"
],
"external": [
"@nestjs/microservices",
"@nestjs/websockets",
"class-transformer/storage"
],
"minify": true,
"bundle": true,
"target": "node14",
"platform": "node",
"mainFields": [
"module",
"main"
],
"outfile": "c.js"
})
.catch((e) => {
console.error('Failed to bundle');
console.error(e);
process.exit(1);
});
运行这个
$ pnpx ts-node bundle.ts
$
它会生成 c.js 捆绑文件。如果我运行这个
$ pnpx node c.js
uA: Validation failed
at d2.exceptionFactory (.../c.js:17:199649)
at d2.transform (.../c.js:17:177741)
at async M1e (.../c.js:17:199822) {
response: {
message: 'Validation failed',
payload: [
'count must not be greater than 1024',
'count must not be less than 1',
'count must be an integer number',
'flag must be a boolean value'
]
},
status: 400
}
$
请注意,@Type 和 @Transform 装饰器中的代码未运行并且验证失败。该类根本没有转换(我在类转换中的 plainToClass 函数前面放置了一个垫片,并检查了之前和之后)。但是验证失败,因为类验证装饰器是由 IntersectionType 继承的。
这是 IntersectionType 的问题!我试过没有它,一切都按预期工作。我知道 esbuild 和装饰器存在问题,我专门使用了来自 @anatine/esbuild-decorators 的 esbuildDecorators 插件。
这是我的 tsconfig.json
{
"compilerOptions": {
"target": "ES2018",
"module": "commonjs",
"lib": [
"es2018"
],
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedParameters": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"typeRoots": [
"./node_modules/@types"
],
"moduleResolution": "node",
"esModuleInterop": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"exclude": [
"node_modules"
],
"include": ["bin/**/*.ts", "lib/**/*.ts"]
}
和 package.json
{
"name": "main",
"version": "0.1.0",
"bin": {
"main": "bin/main.js"
},
"devDependencies": {
"@anatine/esbuild-decorators": "^0.2.18",
"@types/node": "10.17.27",
"esbuild": "^0.14.21",
"ts-node": "^9.1.1",
"typescript": "^4.5.5"
},
"dependencies": {
"@nestjs/common": "^8.2.6",
"@nestjs/core": "^8.2.6",
"@nestjs/mapped-types": "^1.0.1",
"@nestjs/platform-express": "^8.2.6",
"@nestjs/swagger": "^5.2.0",
"ajv": "^8.8.2",
"cache-manager": "^3.4.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.2",
"express": "^4.17.2",
"reflect-metadata": "^0.1.13",
"regenerator-runtime": "^0.13.9",
"rxjs": "^7.5.2",
"source-map-support": "^0.5.21",
"swagger-ui-express": "^4.3.0"
}
}
我是否遗漏了什么,或者这最终是我在项目中采用的这种方法的死胡同?