class-transformer icon indicating copy to clipboard operation
class-transformer copied to clipboard

fix: When I use `plainToClass` in my nestjs app to validate nested env it cannot convert them

Open kasir-barati opened this issue 2 years ago • 6 comments

Description

I am using class-validator and class-transformer to validate my .env but it cannot validate nested objects even though I am using @type(() => ClassName) in my code. But at the same time I am able to validate incoming http request with it. I mean I created a dummy endpoint which receives an object with a nested object inside it and it works just fine. IDK what i am doing wrong. I really appreciate your helps.

Minimal code-snippet showcasing the problem https://github.com/kasir-barati/bugs/tree/class-transformer-bug Clone the repo and follow the instructions in the readme.md

Expected behavior

Validate the .env successfully and not log the value of KEYCLOAK_SECRET like a string.

Actual behavior

It cannot convert the type KEYCLOAK_SECRET to it's respective type as I code.

kasir-barati avatar Apr 28 '23 15:04 kasir-barati

There is no bug in class-transformer/class-validation. The problem is with your configuration.

  1. Your env (KEYCLOAK_SECRET) is a JSON, so you need to parse is into object. You can do this by adding Transform
class EnvironmentVariables {
  @ValidateNested()
  @Transform(({ value }) => JSON.parse(value)) // <- this line of code
  @Type(() => KeycloakSecretValidator)
  KEYCLOAK_SECRET: KeycloakSecret;
}
  1. process.env contains not only properties from .env but much more so you have two solutions:
  • Set forbidUnknownValues as false
  const validatedConfigsErrors = validateSync(validatedConfigs, {
    skipMissingProperties: false,
    forbidUnknownValues: false, // <- this line
    forbidNonWhitelisted: true,
  });
  • Or add expose and remove all unknown properties during parsing to class:
class EnvironmentVariables {
  @ValidateNested()
  @Transform(({ value }) => JSON.parse(value))
  @Type(() => KeycloakSecretValidator)
  @Expose() // < -- this line of code
  KEYCLOAK_SECRET: KeycloakSecret;
}
  const validatedConfigs = plainToClass(EnvironmentVariables, config, {
    enableImplicitConversion: true,
    excludeExtraneousValues: true, // <- this line of code
  });

After this changes it should work

addun avatar May 19 '23 07:05 addun

Thanks for the clarification, but still I cannot stop wondering why in the auth.config.ts I have to go through that @Transform(({ value }) => JSON.parse(value)) and in t.dto I do not need to do that?

image

Though I think basically my global ValidationPipe in main.ts is the same as what I did at auth.config.ts, are not they?

kasir-barati avatar May 19 '23 11:05 kasir-barati

Because nestjs have built in parser from JSON to plain JS object when request comes from API (via controller). So nestjs does it but under the hood.

When you load data to app via env, nestjs parser is not run, so you have to do it manually.


  app.useGlobalPipes(
    new ValidationPipe({
      transformOptions: {
        enableImplicitConversion: false,
      },
    }),
  );

This global pipe only touch HTTP requests, not all used of class-transform/class-validation

addun avatar May 19 '23 11:05 addun

This global pipe only touch HTTP requests, not all used of class-transform/class-validation

I knew this, but I did not know about the builtin parser. Thanks a lot. Now everything is clear as day to me.

kasir-barati avatar May 19 '23 16:05 kasir-barati

Question-- Is it not possible to get the exact list of properties from the class itself, and to tell plainToClass() to only parse those properties? Why is it necessary to add Expose() to each property? On the surface, seems redundant.

emileindik avatar Oct 17 '23 21:10 emileindik

Nope, it is not possible. Properties declarations only exist in TS. Library has to somehow get those information and is does... but from Expose decorator.

You can read more about it in README.md

addun avatar Nov 22 '23 20:11 addun