1

I have this:

    import * as val from 'class-validator';
    import {ObjectId as MongoId} from 'mongodb';
    
    @val.ValidatorConstraint({async: false})
    export class IsTraceValidConstraint implements val.ValidatorConstraintInterface {
      validate(trace: any, args: val.ValidationArguments) { 
        return trace.deviceId && typeof trace.deviceId === 'string'; // << trace is undefined
      }
    
      defaultMessage(args: val.ValidationArguments) {
        return 'Trace validation failed';
      }
    }
    
    // Function to create the class-level decorator
    export function IsTraceValid(validationOptions?: val.ValidationOptions) {
      return function (target: Function) {
        val.registerDecorator({
          target: target,
          options: validationOptions,
          constraints: [],
          validator: IsTraceValidConstraint,
          name: 'isTraceValid',
          propertyName: 'isTraceValid',
        });
      };
    }

then I use it like so:

    @IsTraceValid()
    @t.Unique("user-hash-device", ["hash", "userId", "deviceId"])
    @t.Entity('vibe_trace')
    export class Trace extends From<Trace> { }

but if I run it, I get a null pointer -

trace is undefined in validate callback - so trace.deviceId throws an error

Clearly I am doing something wrong - but what?

1 Answer 1

1
+50

Update propertyName in your decorator registration to match the name of the property you want to validate in your custom constraint. i.e. 'deviceId'.

In your custom constraint you now check directly against that value in your validate method. If you want to check against other properties you can access the trace instance via args.object

import * as val from 'class-validator';
import { ObjectId as MongoId } from 'mongodb';

@val.ValidatorConstraint({ async: false })
export class IsTraceValidConstraint implements val.ValidatorConstraintInterface {
  validate(value: any, args: val.ValidationArguments) {
    // trace instance is accessible via `args.object`
    return typeof value === 'string';
  }

  defaultMessage(args: val.ValidationArguments) {
    return 'Trace validation failed';
  }
}

// Function to create the class-level decorator
export function IsTraceValid(validationOptions?: val.ValidationOptions) {
  return function (target: Function) {
    val.registerDecorator({
      target: target,
      options: validationOptions,
      constraints: [],
      validator: IsTraceValidConstraint,
      name: 'isTraceValid',
      propertyName: 'deviceId',
    });
  };
}
Sign up to request clarification or add additional context in comments.

4 Comments

thanks, upvoted - but what if I just want a custom validator for the entire object, not just the property "deviceId"?
@AlexanderMills Well I’d say it’s an atypical usecase for class-validator and the decorators can get hard to reason about when mixing types. If you have some constraints you want to enforce on your traces consider a builder pattern to avoid creating objects with invalid state in the first place or if you really want to explicitly define your schema you‘ll likely get more mileage out of something like zod.
well, I want as much type checking as possible (with typescript), and since the annotation/decorator is at the class level, not property level, I wanted/expected it to accept the class instance not a property etc
@AlexanderMills yeah understood but a class level validator just isn’t compatible with how they keep track of validators under the hood, nevermind the fact that extracting a custom decorator is really only worthwhile when it’s more general/reusable. If it’s just about validating the properties of your class, use the property-level decorators and custom constraints if need be. Check out zod, it might align more with your expectations and the syntax is in my opinion much simpler and more expressive.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.