how to make sure at least one field is not empty?(its ok if only one is not empty)
Asked Answered
F

3

20

I'm writing a registration endpoint for an api i'm working on and i'm using nestjs and class-validator to validate the input data. The user can register using either their phone number or email address or both. So to validate the input, I should make sure that at least one of them is provided. But I'm having a hard time figuring out how to do it without making a mess.

This is my dto:

export class register {
  @ApiModelProperty()
  @IsNotEmpty()
  @IsAlpha()
  firstName: string

  @ApiModelProperty()
  @IsNotEmpty()
  @IsAlpha()
  lastName: string

  @ApiModelProperty()
  @ValidateIf(o => o.email == undefined)
  @IsNotEmpty()
  @isIRMobile()
  phone: string

  @ApiModelProperty()
  @ValidateIf(o => o.phone == undefined)
  @IsNotEmpty()
  @IsEmail()
  email: string

  @ApiModelProperty()
  @IsNotEmpty()
  password: string
}

As you can see, i've used conditional validation which works for the cases that only one of phone number or email address is provided. But the problem is that when both are provided, one of them won't be validated and an invalid value will be allowed.

Any suggestions?

Feminacy answered 30/6, 2019 at 17:22 Comment(0)
S
1

I made a simpler solution, the whole idea of "AnyOf" is to invoke @ValidateIf() on properties. But in order this to be done in a clean way, you have to use Descriptors, here is my solution:

export function AnyOf(properties: string[]) {
  return function (target: any) {
    for (const property of properties) {
      const otherProps = properties.filter(prop => prop !== property);
      const decorators = [
        // Validates if all other properties are undefined.
        ValidateIf((obj: any) =>
          obj[property] !== undefined || otherProps.reduce(
            (acc, prop) => acc && obj[prop] === undefined,
            true,
          ),
        ),
      ];

      for (const decorator of decorators) {
        applyDecorators(decorator)(target.prototype, property);
      }
    }
  };
}

@AnyOf(['email', 'phone'])
class CreateUserDto {
  @IsString()
  email: string

  @IsString()
  phone: string
}
Secessionist answered 9/8 at 20:42 Comment(0)
D
45

I think you just need to add

@ValidateIf(o => o.email == undefined || o.phone)
phone: string

and

@ValidateIf(o => o.phone == undefined || o.email)
email: string

EDITED

A small addition

I'd suggest validating like this:

@ValidateIf(o => !o.email || o.phone)
phone: string

@ValidateIf(o => !o.phone || o.email)
email: string

to properly handle null, 0 and "" (empty string)

otherwise, this will be correct as well (it shouldn't be):

const data = new register();
data.phone = '';
data.email = '';
Dovap answered 1/7, 2019 at 8:50 Comment(1)
You can see this feat request too: github.com/typestack/class-validator/issues/1581Declension
M
1

This is how you can validate at least one field, It will validate only if at least one filed is correct.

import {  IsNotEmpty, IsNumber, ValidateIf, Length } from 'class-validator';

    export class ProposalBody {
      @ValidateIf((req) => !req.proposalNo || req.mobileNumber)
      @IsNotEmpty()
      @IsNumber()
      @Length(10, 10, { message: "Invalid Mobile Number. It should be a 10-digit number." })
      mobileNumber: number;
    
      @ValidateIf((req) => !req.mobileNumber || req.proposalNo)
      @IsNotEmpty()
      @IsNumber()
      proposalNo: number;
    }
Manumission answered 4/9, 2023 at 9:29 Comment(0)
S
1

I made a simpler solution, the whole idea of "AnyOf" is to invoke @ValidateIf() on properties. But in order this to be done in a clean way, you have to use Descriptors, here is my solution:

export function AnyOf(properties: string[]) {
  return function (target: any) {
    for (const property of properties) {
      const otherProps = properties.filter(prop => prop !== property);
      const decorators = [
        // Validates if all other properties are undefined.
        ValidateIf((obj: any) =>
          obj[property] !== undefined || otherProps.reduce(
            (acc, prop) => acc && obj[prop] === undefined,
            true,
          ),
        ),
      ];

      for (const decorator of decorators) {
        applyDecorators(decorator)(target.prototype, property);
      }
    }
  };
}

@AnyOf(['email', 'phone'])
class CreateUserDto {
  @IsString()
  email: string

  @IsString()
  phone: string
}
Secessionist answered 9/8 at 20:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.