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

question: is there a way to ensure validators and types match? #1951

Open
nicolaschambrier opened this issue Feb 13, 2023 · 1 comment
Open
Labels
type: question Questions about the usage of the library.

Comments

@nicolaschambrier
Copy link

nicolaschambrier commented Feb 13, 2023

We're comparing different schema validation libraries and one of my strongest concerns is the possibility to lie with class-validator. Here is an obviously stupid example, but it shows how we can have validators that make zero sense with the type, but will still wait for runtime to fail:

class TestClass {
  @IsOptional()
  @IsString()
  age: number

  @IsBoolean()
  lastName?: string

  @IsBoolean()
  firstName?: string
}

const tstObj1 = new TestClass()
tstObj1.age = 42
tstObj1.firstName = undefined
tstObj1.lastName = 'test'

expect(validateSync(tstObj1)).toEqual([
  {
    target: tstObj1,
    value: 42,
    property: 'age',
    children: [],
    constraints: { isString: 'age must be a string' },
  },
  {
    target: tstObj1,
    value: 'test',
    property: 'lastName',
    children: [],
    constraints: { isBoolean: 'lastName must be a boolean value' },
  },
  {
    target: tstObj1,
    value: undefined,
    property: 'firstName',
    children: [],
    constraints: { isBoolean: 'firstName must be a boolean value' },
  },
])

In "real life" this happened with @IsOptional() which had been added where it should have not, and was not here where it should have. But I guess in bigger models it can happen on more sever cases.

How can I ensure the validators and the types always match, at least for the basics?

What I expected here

Typing errors preventing compilation:

  • Cannot use @IsOptional() @IsString() on "age" as string | null | undefined is not compatible with number
  • Cannot use @IsBoolean() on "lastName" as boolean is not compatible with string | undefined
  • Cannot use @IsBoolean() on "firstName" as boolean is not compatible with string | undefined

Maybe it's just a limitation of the decorators, I still hope it can be achieved with strictier typing on validators.

@nicolaschambrier nicolaschambrier added the type: question Questions about the usage of the library. label Feb 13, 2023
@karlismelderis-mckinsey

Does it make sense to invest time in this change?

I decided to create my own wrapper decorators and wait for lib to switch away from legacy decorators first.
Suspect that a lot of type safety will be solved when class-validator will switch away from legacy decorators.

export type OurPropertyDecorator<T, K extends string | symbol, KType> = (
  target: K extends keyof T
    ? T[K] extends KType | undefined
      ? T
      : never
    : never,
  propertyKey: K,
) => void;


function IsOurEnum<T extends object, K extends string | symbol>({
  required,
}: {
  required: boolean;
}): OurPropertyDecorator<T, K, OurEnum> {
  return function (target, propertyKey): void {
    Expose()(target, propertyKey);
    IsEnum(Support)(target, propertyKey);
    ApiProperty({
      enum: OurEnum,
      example: 'example-value',
      required,
    })(target, propertyKey);
    if (required) {
      IsDefined()(target, propertyKey);
    } else {
      IsOptional()(target, propertyKey);
    }
  };
}

class A {
  @IsOurEnum
  k: OurEnum
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: question Questions about the usage of the library.
Development

No branches or pull requests

2 participants