Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Routing-Controllers + class-validator bug? #384

Closed
NoNameProvided opened this issue Mar 17, 2018 · 39 comments · Fixed by #497
Closed

Routing-Controllers + class-validator bug? #384

NoNameProvided opened this issue Mar 17, 2018 · 39 comments · Fixed by #497
Labels
type: fix Issues describing a broken feature.
Milestone

Comments

@NoNameProvided
Copy link
Member

From 11000100111000 in typestack/class-validator#128

When using the automatic validation from routing-controllers for some reason the email "patch@patch" is valid when we are using these validators and the "patch" validator group

@IsEmail({
        allow_display_name: false,
        allow_utf8_local_part: true,
        require_tld: true
    }, {
        groups: ["post", "patch", "login"]
    })
    @IsOptional({
        groups: ["patch"]
    })
    public email;

Doing it the manual way like so works however

let user = new UserValidation();
user.email = "patch@patch";
validate(user, {groups: ["patch"]}).then(console.log);
@dYale
Copy link
Contributor

dYale commented Mar 30, 2018

Having same issue. Validation isn't running implicitly in the controller.

@Benj0s
Copy link

Benj0s commented May 4, 2018

I think I might be seeing similar issue too. Tried running a version of the auto validation based on the example in the README:

@JsonController()
class UserController {
    @Post('/login')
    login(@Body({validate: true}) user: User) {
        console.log('this is a valid body');
        return 'success!!!';
    }
}

class User {
    @IsEmail()
    email: string;

    @MinLength(6)
    password: string;
}

createExpressServer({
    controllers: [UserController]
}).listen(3000);

No matter what I send in the POST request I get a success back. Not sure if I've missed something in the above code but for the life of me I can't get it to validate and throw an error.

Validating manually through the class-validator does work though.

@MichalLytek
Copy link
Contributor

The problem might not be in routing-controllers directly but with transformation of plain object to class instance using class-transformer.

@fredgate
Copy link

fredgate commented Jun 11, 2018

Exactly. I have similar issue. Parameters of my actions are custom classes (not interfaces) annotated with attributes like @minlength ... The problem is that parameters that I received are pure literal objects and not instances of my classes. So problem seems to be class-transformer.

I force options (that normally default to true) during creation of express server :

createExpressServer({
  controllers: [ MyController ],
  classTransformer: true,
  validation: true
});

@LogansUA
Copy link

Same for me.
So I have typeorm model

Group.ts
import { IsNotEmpty, IsUppercase, MaxLength } from 'class-validator';
import { Column, Entity, Index, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
import { SubGroup } from './SubGroup';

@Entity({
    name: 'pss_groups',
})
export class Group {
    @PrimaryGeneratedColumn()
    public id: number;

    @Column({
        length: 1,
        name: 'name',
    })
    @Index({ unique: true })
    @IsNotEmpty()
    @MaxLength(1)
    @IsUppercase()
    public name: string;

    @Column({
        name: 'description',
    })
    public description: string;

    @OneToMany(type => SubGroup, subGroup => subGroup.group)
    public subGroups: SubGroup[];
}

And here's my controller

GroupController.ts
import { Body, HttpCode, JsonController, Post } from 'routing-controllers';
import { Service } from 'typedi';
import { Group } from '../models/Group';
import { GroupService } from '../services/GroupService';

@JsonController('/groups')
export class GroupController {
    constructor(@Service() private groupService: GroupService) {
    }

    @HttpCode(201)
    @Post('')
    public async createGroup(@Body({ required: true, validate: true }) group: Group): Promise<Group> {
        return this.groupService.createGroup(group);
    }
}

In result action is not validating data at all, even if I include validate function and use it on group value, the result will be an empty array.

My app initialization

app.ts
const expressApp = express();

expressApp.use(json({ limit: '50mb' }));
expressApp.use(urlencoded({ extended: true }));

useExpressServer(expressApp, {
    cors: true,
    routePrefix: env.app.routePrefix,
    defaultErrorHandler: false,
    classTransformer: true,
    validation: true,
    controllers: env.app.dirs.controllers,
    middlewares: env.app.dirs.middlewares,
});

Any thoughts?

@MardariG
Copy link

MardariG commented Aug 1, 2018

Any News about this issue?
I noticed that if using @controller works, if using @jsoncontroller not works.

@ghost
Copy link

ghost commented Aug 1, 2018

The issue is caused because the version of class-validator that you have installed does not match the version of class-validator that routing-controllers is using. (see issue #424 and typestack/class-validator#166)

For a temporary fix you can run the following commands:

npm uninstall routing-controllers class-validator
npm install --save routing-controllers@0.7.7 class-validator@0.8.1

Edit: This will not work if you are using class-transformer. If you are using class-transformer read below for a solution

@MardariG
Copy link

MardariG commented Aug 2, 2018

I've tried your solution, still same issue, @Body is not validated.

@Nufflee
Copy link

Nufflee commented Aug 2, 2018

@Andrew5569 Nope, doesn't work...

@ghost
Copy link

ghost commented Aug 2, 2018

I created an example: https://github.com/Andrew5569/rc-body-validation

This is working for me

@Nufflee
Copy link

Nufflee commented Aug 2, 2018

Yup, that works @Andrew5569 but I have basically the exact same setup but with controller and the model in a separate file and it just doesn't want to work.

@KwabenBerko
Copy link

@Andrew5569
Temporary fix works.
Cheers!

@nolazybits
Copy link
Contributor

nolazybits commented Aug 28, 2018

So I have done few test and this is what I found:

library t1 t2 t3 t4 t5 t6 t7 t8 t9 t10
RC version 0.7.6 0.7.6 0.7.7 0.7.7 0.7.7 0.7.7 0.7.7 0.7.7 0.7.7 0.7.7
CV version 0.7.3 0.8.0-4 0.7.3 0.8.x 0.9.1 0.9.1 0.8.1 not in package.json 0.8.5 0.9.0
CT version 0.1.9 0.1.9 0.1.9 0.1.9 not in package.json 0.1.9 0.1.9 not in package.json 0.1.9 0.1.9
Working ?
normal as RC requires CV ^0.7.0

normal as RC requires CV ^0.8.1

So it seems to be a mix of class-validation / routing-controller changes breaking the validation, as the newer version of class-transformer works with routing-controller 0.7.6 and class-validator 0.7.3

@pleerock @NoNameProvided would you have any chance looking at this issue as it makes the stack (routing controller, class validator, class transfomers) non useable with latest version?

Thanks again 👍

@MardariG
Copy link

@nolazybits I don't think you will have a fix soon, looking at latest commit date. I forked it and just updated package.json versions to latest and it seems to work. This is the modified version you can use from npm until official release: https://www.npmjs.com/package/@mardari/routing-controllers

@nolazybits
Copy link
Contributor

thanks @MardariG I will give it a go 👍
@pleerock if you need help to maintain those projects I am happy to help, at least to review the existing PR etc, and try to merge and publish.

@konraddysput
Copy link

konraddysput commented Sep 5, 2018

@MardariG Im sorry but your changes doesnt work on my project.

Sample:

import {  Body, Post, Controller } from '@mardari/routing-controllers';
import { MinLength, IsDefined, IsNotEmpty } from '../../../node_modules/class-validator';

export class User {
    @IsNotEmpty({message: 'is empty'})
    @IsDefined({message: 'is not defined'})
    @MinLength(5, { message: 'FIRST NAME '})
    public firstName: string;
    public lastName: string;
 
    public getName(): string {
        return this.lastName + ' ' + this.firstName;
    }
}
 
// tslint:disable-next-line:max-classes-per-file
@Controller('/user')
export class UserController {
 
    @Post()
    public post(@Body({required: true, validate: true}) user: User): number {
        console.log('saving user ' + user.getName());

        return 1;
    }
 
}

accepts

{
	"firstName": "kond",
	"lastName": "dysput"
}

@MardariG
Copy link

MardariG commented Sep 5, 2018

@konraddysput
Not sure what can be the cause as what I did was just updating package.json from original repo to latest versions.
So I have a sample just like yours, and it works:

import {IsEmail, IsNotEmpty, MinLength} from 'class-validator';

export class Auth {
    @IsNotEmpty()
    @IsEmail()
    public email: string;
    @IsNotEmpty()
    @MinLength(8)
    public password: string;
}
import {Body, JsonController, Post} from '@mardari/routing-controllers';
import {Auth} from './requests/Auth';
import {AuthService} from '../../auth/AuthService';
import {AuthResponse} from './responses/AuthResponse';

@JsonController('/auth')
export class AuthController {

    constructor(
        private authService: AuthService
    ) {
    }

    @Post()
    public authorize(@Body() auth: Auth): Promise<AuthResponse> {
        return this.authService.login(auth);
    }

}

Request: {email: "admin@admin.com", password: "admin"} gives validation error:

{
"name":"BadRequestError",
"message":"Invalid body, check 'errors' property for more info.",
"errors":[
{"target":{"email":"admin@admin.com","password":"admin"},
"value":"admin",
"property":"password",
"children":[],
"constraints":{"minLength":"password must be longer than or equal to 8 characters"}}
]
}

note: all classes are separate files, not sure if this can be problems;

@konraddysput
Copy link

I think the problem isn't in class-validation or routing-controllers. Can you provide your package.json? Maybe you use diffrent version of class-transform?

@MardariG
Copy link

MardariG commented Sep 5, 2018

"dependencies": {
    "@mardari/routing-controllers": "^0.7.9",
    "@types/bluebird": "^3.5.18",
    "@types/body-parser": "^1.17.0",
    "@types/chalk": "^2.2.0",
    "@types/cors": "^2.8.1",
    "@types/dotenv": "^4.0.3",
    "@types/express": "^4.16.0",
    "@types/faker": "^4.1.2",
    "@types/helmet": "^0.0.39",
    "@types/lodash": "^4.14.116",
    "@types/morgan": "^1.7.35",
    "@types/multer": "^1.3.7",
    "@types/reflect-metadata": "0.1.0",
    "@types/request": "^2.47.1",
    "@types/serve-favicon": "^2.2.30",
    "@types/uuid": "^3.4.3",
    "@types/winston": "^2.3.7",
    "adm-zip": "^0.4.11",
    "bcrypt": "^3.0.0",
    "body-parser": "^1.18.3",
    "chalk": "^2.4.1",
    "class-validator": "0.9.1",
    "commander": "^2.17.1",
    "compression": "^1.7.3",
    "copyfiles": "^2.0.0",
    "cors": "^2.8.4",
    "dataloader": "^1.4.0",
    "dotenv": "^6.0.0",
    "ejs": "^2.6.1",
    "event-dispatch": "^0.4.1",
    "express": "^4.16.3",
    "express-basic-auth": "^1.1.5",
    "express-graphql": "^0.6.12",
    "express-status-monitor": "^1.1.5",
    "faker": "^4.1.0",
    "figlet": "^1.2.0",
    "glob": "^7.1.3",
    "graphql": "^0.13.2",
    "helmet": "^3.13.0",
    "html-pdf": "^2.2.0",
    "jsonfile": "^4.0.0",
    "jsonwebtoken": "^8.3.0",
    "lodash": "^4.17.10",
    "microframework-w3tec": "^0.6.3",
    "moment": "^2.22.2",
    "morgan": "^1.9.0",
    "multer": "^1.3.1",
    "nodemon": "^1.18.3",
    "npm": "^6.4.0",
    "nps": "^5.9.3",
    "nps-utils": "^1.7.0",
    "path": "^0.12.7",
    "pg": "^7.4.3",
    "reflect-metadata": "^0.1.12",
    "request": "^2.88.0",
    "serve-favicon": "^2.5.0",
    "swagger-ui-express": "^4.0.0",
    "ts-node": "^6.0.5",
    "tslint": "^5.11.0",
    "typedi": "^0.8.0",
    "typeorm": "^0.2.7",
    "typeorm-typedi-extensions": "^0.2.1",
    "typescript": "3.0.1",
    "uuid": "^3.3.2",
    "winston": "^2.4.3",
    "xml2js": "^0.4.19"
  },
  "devDependencies": {
    "@types/ejs": "^2.6.0",
    "@types/nock": "^9.3.0",
    "@types/node": "^10.9.2",
    "cross-env": "^5.2.0",
    "nock": "^9.6.1",
    "shelljs": "^0.8.2"
  }

@konraddysput
Copy link

Ok. Current version working well with:

    "class-transformer": "~0.1.6",
    "class-validator": "^0.7.0",

Validation start working on my side and working really well. Key part is to use class-validator from version 0.7.0 - 0.7.3.

Following example showing how my express changes works:
https://github.com/pleerock/routing-controllers-express-demo

@Diluka
Copy link
Contributor

Diluka commented Sep 10, 2018

import {ActionParameterHandler} from "routing-controllers/ActionParameterHandler";
import {validateOrReject as validate, ValidationError} from "class-validator";
import {BadRequestError} from "routing-controllers";

/**
 * issue https://github.com/typestack/routing-controllers/issues/384
 * a copy of https://github.com/typestack/routing-controllers/blob/4a56d176db77bc081dfcd3d8550e8433b5bc476e/src/ActionParameterHandler.ts#L179-L199
 * @param value
 * @param paramMetadata
 */
ActionParameterHandler.prototype["validateValue"] = function (this: any, value: any, paramMetadata: any) {
    const isValidationEnabled = (paramMetadata.validate instanceof Object || paramMetadata.validate === true)
        || (this.driver.enableValidation === true && paramMetadata.validate !== false);
    const shouldValidate = paramMetadata.targetType
        && (paramMetadata.targetType !== Object)
        && (value instanceof paramMetadata.targetType);

    if (isValidationEnabled && shouldValidate) {
        const options = paramMetadata.validate instanceof Object ? paramMetadata.validate : this.driver.validationOptions;
        return validate(value, options)
            .then(() => value)
            .catch((validationErrors: ValidationError[]) => {
                const error: any = new BadRequestError(`Invalid ${paramMetadata.type}, check 'errors' property for more info.`);
                error.errors = validationErrors;
                error.paramName = paramMetadata.name;
                throw error;
            });
    }

    return value;
};

Temporarily solved this problem. I don't know why it works.

@teevik
Copy link

teevik commented Sep 14, 2018

What i did to fix it was to look in node_modules\routing-controllers\package.json, and copy the versions for class-validator and class-transformer (if you use that) over to my own package.json. And then download the dependencies again

@dyaacov
Copy link

dyaacov commented Dec 4, 2018

where exactly?
I'm using class-transformer and it doesn't work.
what is the solution for that?

Thanks in advance :)

@klassicd
Copy link

A temporary fix that's working for me is to remove class-validator from package.json, and instead just import from routing-controllers module. E.g.

import { IsNotEmpty } from 'routing-controllers/node_modules/class-validator';

@realityfilter
Copy link

I found that it is sufficient to declare the dependency to class-validator to the same version as in routing-controllers: "class-validator": "^0.8.1",. I had to remove my node_modules folder and the lock file and run npm install again.
After that here was no additional class-validator package in the node_modules folder for routing-controllers. That way the app and routing-controllers were using the exactly same version from the root nod_modules folder. No error anymore.

@viniciustodesco
Copy link

From 11000100111000 in typestack/class-validator#128

When using the automatic validation from routing-controllers for some reason the email "patch@patch" is valid when we are using these validators and the "patch" validator group

@IsEmail({
        allow_display_name: false,
        allow_utf8_local_part: true,
        require_tld: true
    }, {
        groups: ["post", "patch", "login"]
    })
    @IsOptional({
        groups: ["patch"]
    })
    public email;

Doing it the manual way like so works however

let user = new UserValidation();
user.email = "patch@patch";
validate(user, {groups: ["patch"]}).then(console.log);

A temporary fix that's working for me is to remove class-validator from package.json, and instead just import from routing-controllers module. E.g.

import { IsNotEmpty } from 'routing-controllers/node_modules/class-validator';

Worked for me tks !!

@satanTime
Copy link

I have similar issue and found that it could be caused by usage of useContainer for class-validator. Related issue is here: typestack/class-validator#328.

@adenhertog
Copy link
Contributor

adenhertog commented Mar 27, 2019

I think the reason for the bug is that class-validator maintains an internal registry of the contracts that you decorate. If you install class-validator as a package dependency, you'll likely get the newer one where your contracts will be registered; but when routing-controllers runs it uses its internal class-validator (v0.8.5) that would have an empty registry and hence not validate anything.

A proposed solution to routing-controllers:
add class-validator as an optional-peer-dependency and force consumers that want to validate to add it themselves.

A proposed solution to people who declare contracts in their api project:
don't add class-validator to your project. By default if you add routing-controllers you'll also get class-validator automatically, so just use taht

A proposed solution to people who keep contracts in a separate package that gets installed to their api:
add class-validator as a dev-dependency in your contracts package. add routing-controllers to your api

@BlackRider97
Copy link

A temporary fix that's working for me is to remove class-validator from package.json, and instead just import from routing-controllers module. E.g.

import { IsNotEmpty } from 'routing-controllers/node_modules/class-validator';

Anyone have better solution than this ? so far this is the only working solution for me.

Dependencies are

    "body-parser": "^1.19.0",
    "class-transformer": "^0.2.3",
    "cors": "^2.8.5",
    "express": "^4.17.1",
    "express-winston": "^3.1.0",
    "express-ws": "^4.0.0",
    "install": "^0.12.2",
    "jwt-simple": "^0.5.5",
    "mongoose": "^5.4.8",
    "mongoose-autopopulate": "^0.9.1",
    "multer": "^1.4.1",
    "reflect-metadata": "^0.1.13",
    "routing-controllers": "^0.7.7",

"class-validator": "^0.9.1", uninstalled package

@wmattei
Copy link

wmattei commented Jun 19, 2019

Any better solution? Only import class-validators from routing controllers works

@adenhertog
Copy link
Contributor

I've published @gritcode/routing-controllers that includes this fix (ie: class-validator as a peer dependency).

To use it: npm i @gritcode/routing-controllers class-validator

I've sent a PR back to this abandoned lib, but doubt it'll ever be merged

@gstamac
Copy link

gstamac commented Jul 16, 2019

@adenhertog thank you very much for your solution. Maybe you could create a smaller PR with just that change and it would be merged. I'll be happy to poke them :)

@jotamorais jotamorais added this to the 0.8.x release milestone Oct 1, 2019
@jotamorais jotamorais added the type: fix Issues describing a broken feature. label Oct 1, 2019
@jotamorais
Copy link
Member

jotamorais commented Feb 5, 2020

I've published @gritcode/routing-controllers that includes this fix (ie: class-validator as a peer dependency).

To use it: npm i @gritcode/routing-controllers class-validator

I've sent a PR back to this abandoned lib, but doubt it'll ever be merged

@adenhertog I've merged your changes in the next branch and will soon package and publish to NPM as a release candidate for testing (0.8.1-alpha.1)

@adenhertog
Copy link
Contributor

that's great @jotamorais, thanks for reviving this lib!

@jotamorais
Copy link
Member

jotamorais commented Feb 6, 2020

that's great @jotamorais, thanks for reviving this lib!

Published to NPM, the pre-release including your changes (tagged as 0.8.1-alpha.2 off of next branch).

Please, update your package.json and try it out before I merge into master and publish v0.8.1 to NPM.

@jotamorais
Copy link
Member

jotamorais commented Feb 20, 2020

I will close this issue as we made class-transformer and class-validator peer dependencies in the last release (8.0.0) and it seems stable so far.
I've recently released 0.8.1-alpha.2 and it also fixes a related issue to include the current request context when resolving controller from IoC (ref #497)

@juanitopons
Copy link

juanitopons commented Mar 26, 2020

Hi there!
Thanks for the fix, but I'm confused.
I have your last version on my project: 0.8.1 and I'm trying to validate a Generic class as @Body request, on a @jsoncontroller defined POST controller for entity Employee.

controller method

@Post('/')
public async post(
    @Body({ required: true, validate: true })
    body: EntityPostRequest<Employee | Employee[]>,
)

EntityPostRequest definition

export class EntityPostRequest<T extends BaseCoreEntity | BaseCoreEntity[]> {
  @ValidateNested()
  data: T;
}

BaseCoreEntity

export class BaseCoreEntity extends BaseEntity {
  public id: number;
}

Employee entity

@Entity()
export class Employee extends CoreEntity {
  public static QUERY_MAPPINGS_TYPES = ['id', 'departmentId', 'age'];
  @IsOptional()
  @IsPositive()
  @PrimaryGeneratedColumn('increment')
  public id: number;
  @MaxLength(30)
  @Column({ type: 'varchar', length: 30, nullable: false })
  public name: string;
  @MaxLength(30)
  @Column({ type: 'varchar', length: 30, nullable: false })
  public lastname: string;
  @IsPositive()
  @Max(120)
  @Column('int')
  public age?: number;
  @ManyToOne(
    () => Department,
    (department) => department.employees,
  )
  @JoinColumn()
  public department: Department;
  @IsPositive()
  @Column()
  @RelationId((employee: Employee) => employee.department)
  public departmentId: number;
  @IsOptional()
  @CreateDateColumn()
  public created?: Date;

  constructor(id?, name?, lastname?, age?, created?, department?: Department) {
    super();
    this.id = id;
    this.name = name;
    this.lastname = lastname;
    this.age = age;
    this.created = created;
    this.department = department;
  }
}

I don't know what the hell I'm doing worong, but for example, I can't even get 'age' key of Employee to get validates. I'm inserting ages of values higher than 120 and It doesn't thhrow an error, actually it inserts the entity into the database. So no validation exists.

Can anyone guide me?

@satanTime
Copy link

Hi @juanitopons,

if I'm right, the class-validator doesn't work with generics or unions, because it isn't possible to detect their types during the gathering metadata process of decorators.

Therefore I would suggest you to simply split your code into two endpoints for a single record and for an array of records.

if you want to have a single endpoint, then you need to change body structure to be for example:

{
  @ValidateNested()
  item?: Employee,

  @ValidateNested()
  items?: Array<Employee>
}

then it should work as you want.

@NoNameProvided
Copy link
Member Author

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.