Problems with decorators when using class validator
Closed this issue · 19 comments
Describe the bug
I'm trying to use Nest.js with Class Validator for body validation. I've created an E2E test using ts-jest
that passes, and I'm using the same test with @swc/jest
but it's returning a 400 as if the fields weren't being assigned.
Input code
In this link is the repository with the tests that reproduces the error. Run yarn test:e2e
for running the tests with ts-jest
and test:e2e:swc
for @swc/jest
.
The problem is at src/app.controller.ts:17
.
Another problem, is that the import * as request from 'supertest'
is working with ts-jest
but not with @swc/jest
. The error is TypeError: request is not a function
Config
Check the .swcrc
file at the repo.
{
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": false,
"decorators": true
},
"transform": {
"legacyDecorator": true,
"decoratorMetadata": true
},
"target": "es2018"
},
"module": {
"type": "commonjs"
}
}
Expected behavior
- I would expect that
import * as request from 'supertest';
works with@swc/jest
(here). - I would expect that Nest.js assigns the attributes to the User class instance (here).
Version
The version of @swc/core: 1.2.46
Do you use tsconfig.json
?
I'm not sure about the root cause, but I had seen something simillar.
Yes! There is a tsconfig.json
file at the repo. Is this interfering with swc in some sense? We can have a call to deeper debug if you wish.
@DanielRamosAcosta There was an option that determines interop with es modules.
I googled it and it is esModuleInterop
. Can you share your config?
Which config? Swc's one or TypeScript? You have the complete repo here with the tests: https://github.com/DanielRamosAcosta/nest-class-validator-swc
Oh, I see. Thanks!
(I meant tsconfig)
I've pushed some other tests. I thought the problem was caused with class-validator
, you can run the tests yarn test
for running with ts-node
and yarn test:swc
for running with swc
.
The error is:
→ E:\Documentos/my-nest-project [main ≡]› yarn test
yarn run v1.22.5
$ jest
PASS src/dtos/CreateUserDto.spec.ts
CreateUserDto
√ accepts a valid user (5 ms)
√ rejects a user with wrong email (3 ms)
√ rejects a user without password
√ rejects a user without password and email (1 ms)
Test Suites: 1 passed, 1 total
Tests: 4 passed, 4 total
Snapshots: 0 total
Time: 0.974 s, estimated 1 s
Ran all test suites.
Done in 1.66s.
→ E:\Documentos/my-nest-project [main ≡]› yarn test:swc
> yarn run v1.22.5
> $ jest --config ./test/jest-swc.json
FAIL src/dtos/CreateUserDto.spec.ts
● Test suite failed to run
TypeError: decorator is not a function
at E:\Documentos/my-nest-project/src/dtos/CreateUserDto.ts:19:16
at Array.reduce (<anonymous>)
at _applyDecoratedDescriptor (E:\Documentos/my-nest-project/src/dtos/CreateUserDto.ts:18:41)
at Object.<anonymous> (E:\Documentos/my-nest-project/src/dtos/CreateUserDto.ts:50:31)
at Object.<anonymous> (E:\Documentos/my-nest-project/src/dtos/CreateUserDto.spec.ts:3:22)
Test Suites: 1 failed, 1 total
Tests: 0 total
Snapshots: 0 total
Time: 0.719 s
Ran all test suites.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
@DanielRamosAcosta can you check if it works with the latest version?
I've updated the dependencies of the example repo, but it still failing, don't know exactly why, but it still seems to be a problem with decorators.
Running Unitary tests
Without SWC
yarn test
yarn run v1.22.5
$ jest
PASS src/dtos/CreateUserDto.spec.ts
CreateUserDto
√ accepts a valid user (5 ms)
√ rejects a user with wrong email (3 ms)
√ rejects a user without password
√ rejects a user without password and email (1 ms)
Test Suites: 1 passed, 1 total
Tests: 4 passed, 4 total
Snapshots: 0 total
Time: 2.686 s
Ran all test suites.
Done in 3.54s.
With SWC
yarn test:swc
yarn run v1.22.5
$ jest --config ./test/jest-swc.json
FAIL src/dtos/CreateUserDto.spec.ts
● Test suite failed to run
TypeError: decorator is not a function
at dtos/CreateUserDto.ts:19:16
at Array.reduce (<anonymous>)
at _applyDecoratedDescriptor (dtos/CreateUserDto.ts:18:41)
at Object.<anonymous> (dtos/CreateUserDto.ts:50:31)
at Object.<anonymous> (dtos/CreateUserDto.spec.ts:3:22)
Test Suites: 1 failed, 1 total
Tests: 0 total
Snapshots: 0 total
Time: 0.716 s
Ran all test suites.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
Running E2E tests
Without SWC
yarn test:e2e
yarn run v1.22.5
$ jest --config ./test/jest-e2e.json
PASS test/app.e2e-spec.ts
AppController (e2e)
√ / (GET) (327 ms)
√ / (POST) (28 ms)
console.log
I have the user!
at AppService.getHello (../src/app.service.ts:16:15)
console.log
User { name: 'Sam 1' }
at AppService.getHello (../src/app.service.ts:17:15)
console.log
createUserDto { email: 'danielramosacosta@hotmail.com', password: 'hello' }
at AppController.create (../src/app.controller.ts:18:13)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 3.564 s, estimated 7 s
Ran all test suites.
Done in 4.43s.
With SWC
yarn test:e2e:swc
yarn run v1.22.5
$ jest --config ./test/jest-e2e-swc.json
FAIL test/app.e2e-spec.ts
AppController (e2e)
√ / (GET) (344 ms)
× / (POST) (27 ms)
● AppController (e2e) › / (POST)
expected 201 "Created", got 400 "Bad Request"
26 | return request(app.getHttpServer())
27 | .post('/')
> 28 | .send({
| ^
29 | email: "danielramosacosta@hotmail.com",
30 | password: "hello"
31 | })
at Object.<anonymous> (app.e2e-spec.ts:28:12)
----
at Test.Object.<anonymous>.Test._assertStatus (../node_modules/supertest/lib/test.js:296:12)
at ../node_modules/supertest/lib/test.js:80:15
at Test.Object.<anonymous>.Test._assertFunction (../node_modules/supertest/lib/test.js:311:11)
at Test.Object.<anonymous>.Test.assert (../node_modules/supertest/lib/test.js:201:21)
at Server.localAssert (../node_modules/supertest/lib/test.js:159:12)
console.log
I have the user!
at AppService1.getHello (../src/app.service.ts:19:25)
console.log
User { name: 'Sam 1' }
at AppService1.getHello (../src/app.service.ts:20:25)
I tried fixing this, but babel also emits error for
import * as request from 'supertest';
and it's logically correct.
You need to set jsc.module.noInterop
to true lke
{
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": false,
"decorators": true
},
"transform": {
"legacyDecorator": true,
"decoratorMetadata": true
},
"target": "es2018"
},
"module": {
"type": "commonjs",
"noInterop": true
}
}
If so, swc works.
Hello @kdy1, thanks a lot for your comment and great work! The noModuleInterop option worked for me. The thing is, that the main issue, still persist. I think that decorators are still not working.
These are the outputs from the example repository I gave at the top:
Unitary tests:
› yarn test; yarn test:swc
yarn run v1.22.5
$ jest
PASS src/dtos/CreateUserDto.spec.ts
CreateUserDto
√ accepts a valid user (6 ms)
√ rejects a user with wrong email (3 ms)
√ rejects a user without password
√ rejects a user without password and email (1 ms)
Test Suites: 1 passed, 1 total
Tests: 4 passed, 4 total
Snapshots: 0 total
Time: 0.876 s, estimated 1 s
Ran all test suites.
Done in 1.49s.
yarn run v1.22.5
$ jest --config ./test/jest-swc.json
FAIL src/dtos/CreateUserDto.spec.ts
● Test suite failed to run
TypeError: decorator is not a function
at dtos/CreateUserDto.ts:19:16
at Array.reduce (<anonymous>)
at _applyDecoratedDescriptor (dtos/CreateUserDto.ts:18:41)
at Object.<anonymous> (dtos/CreateUserDto.ts:50:31)
at Object.<anonymous> (dtos/CreateUserDto.spec.ts:3:22)
Test Suites: 1 failed, 1 total
Tests: 0 total
Snapshots: 0 total
Time: 0.63 s
Ran all test suites.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
And also, the e2e tests are not working with swc:
› yarn test:e2e; yarn test:e2e:swc
yarn run v1.22.5
$ jest --config ./test/jest-e2e.json
PASS test/app.e2e-spec.ts
AppController (e2e)
√ / (GET) (459 ms)
√ / (POST) (34 ms)
console.log
I have the user!
at AppService.getHello (../src/app.service.ts:16:15)
console.log
User { name: 'Sam 1' }
at AppService.getHello (../src/app.service.ts:17:15)
console.log
createUserDto { email: 'danielramosacosta@hotmail.com', password: 'hello' }
at AppController.create (../src/app.controller.ts:18:13)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 2.119 s
Ran all test suites.
Done in 2.78s.
yarn run v1.22.5
$ jest --config ./test/jest-e2e-swc.json
FAIL test/app.e2e-spec.ts
AppController (e2e)
√ / (GET) (360 ms)
× / (POST) (26 ms)
● AppController (e2e) › / (POST)
expected 201 "Created", got 400 "Bad Request"
26 | return request(app.getHttpServer())
27 | .post('/')
> 28 | .send({
| ^
29 | email: "danielramosacosta@hotmail.com",
30 | password: "hello"
31 | })
at Object.<anonymous> (app.e2e-spec.ts:28:12)
----
at Test.Object.<anonymous>.Test._assertStatus (../node_modules/supertest/lib/test.js:296:12)
at ../node_modules/supertest/lib/test.js:80:15
at Test.Object.<anonymous>.Test._assertFunction (../node_modules/supertest/lib/test.js:311:11)
at Test.Object.<anonymous>.Test.assert (../node_modules/supertest/lib/test.js:201:21)
at Server.localAssert (../node_modules/supertest/lib/test.js:159:12)
console.log
I have the user!
at AppService1.getHello (../src/app.service.ts:19:25)
console.log
User { name: 'Sam 1' }
at AppService1.getHello (../src/app.service.ts:20:25)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 passed, 2 total
Snapshots: 0 total
Time: 1.686 s, estimated 11 s
Ran all test suites.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
I think there might be a problem with de decorators because when I remove the decorators from the CreateUserDto
it works.
I've updated all the dependencies of the example repository so you can try it out.
Thanks a lot again!
Hey, I can confirm that class-transformer
is not working well either.
I have following setup
{
"jsc": {
"target": "es2017",
"parser": {
"syntax": "typescript",
"decorators": true,
"dynamicImport": false
},
"transform": {
"legacyDecorator": true,
"decoratorMetadata": true
},
"keepClassNames": true
},
"module": {
"type": "commonjs",
"noInterop": true
}
}
But, unfortunately when I do plainToClass
on such an entity
export class GetUsersOptionsInput {
@IsString()
@IsOptional()
public query?: string
@ValidateNested()
public pagination: PaginationInput
}
export class PaginationInput {
@IsInt()
@IsOptional()
public skip = 0
@IsInt()
@IsOptional()
public limit = 39
}
Passing down { query: '', pagination: { skip:0, limit: 20 } }
it fails to convert returning empty object of GetUsersOptionsInput
input. E.g. logging this to console console.log(input)
-> GetUsersOptionsInput {}
If decorators will start to work as expected SWC will be a way to go.
Ah, also unsure what's wrong but for IntelliJ debugging with node -r @swc/register ./src/index.ts
half broke and doesn't stop in certain files. Mind that in most of the files there are lots of lots decorators, so that's maybe why.
Thanks, but seems like lots of investigation is still required to reduce the test case and spot the error.
I tried to narrow this down (from the example by @DanielRamosAcosta) to the simplest possible use case with a DTO having a single property and a single decorator, like this:
import { IsString } from 'class-validator';
export class CreateUserDto {
@IsString()
id!: string;
}
With TS it gets compiled to:
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CreateUserDto = void 0;
const class_validator_1 = require("class-validator");
class CreateUserDto {
}
__decorate([
class_validator_1.IsString(),
__metadata("design:type", String)
], CreateUserDto.prototype, "id", void 0);
exports.CreateUserDto = CreateUserDto;
With SWC the result is:
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.CreateUserDto = void 0;
var _classValidator = require("class-validator");
function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
var desc = {
};
Object.keys(descriptor).forEach(function(key) {
desc[key] = descriptor[key];
});
desc.enumerable = !!desc.enumerable;
desc.configurable = !!desc.configurable;
if ("value" in desc || desc.initializer) {
desc.writable = true;
}
desc = decorators.slice().reverse().reduce(function(desc, decorator) {
return decorator(target, property, desc) || desc;
}, desc);
if (context && desc.initializer !== void 0) {
desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
desc.initializer = undefined;
}
if (desc.initializer === void 0) {
Object.defineProperty(target, property, desc);
desc = null;
}
return desc;
}
function _initializerDefineProperty(target, property, descriptor, context) {
if (!descriptor) return;
Object.defineProperty(target, property, {
enumerable: descriptor.enumerable,
configurable: descriptor.configurable,
writable: descriptor.writable,
value: descriptor.initializer ? descriptor.initializer.call(context) : void 0
});
}
var _class, _descriptor;
var _dec = (0, _classValidator).IsString(), _dec1 = typeof Reflect !== "undefined" && typeof Reflect.metadata === "function" && Reflect.metadata("design:type", String);
let CreateUserDto = ((_class = class CreateUserDto {
constructor(){
_initializerDefineProperty(this, "id", _descriptor, this);
}
}) || _class, _descriptor = _applyDecoratedDescriptor(_class.prototype, "id", [
_dec,
_dec1
], {
configurable: true,
enumerable: true,
writable: true,
initializer: void 0
}), _class);
exports.CreateUserDto = CreateUserDto;
Trying to console.log(CreateUserDto.prototype)
just before export TS outputs {}
while with SWC it is { id: undefined }
.
When logging the objectOrSchemaName
here in the validator I get CreateUserDto { id: 'the-actual-test-value' }
with TS while only CreateUserDto {}
with SWC.
Seems like with SWC the validator doesn't receive the actual value of the id!: string;
property. Not sure where to look further.
Oh, thanks for reducing! I'll try fixing it.
Thanks! Not sure if it's related to #879 in any way or if it's a completely different issue. The main thing I see on the class-validator side is not receiving the runtime data of the CreateUserDto
instance properties.
I found that this is fixed by inserting require('reflect-metadata');
in the top.
Also improving helper worked, and I'm going to update helpers.
This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.