Search code examples
typescriptjoi

Validate fixed array with Joi


Struggling to figure out how I can do this since it's a different type of "array validation".

Essentially, I want to take dynamic sets of rest parameters from function arguments and validate them against a schema for a REST api. For example:

@Validation(Schema.update)
public async update(id: string, model: Model) => { }

Then use my decorator to intercept the function call and perform validation based on the schema provided:


export function Validation(schema: ObjectSchema) {
  return (target: any, propName: string | symbol, descriptor: PropertyDescriptor): void => {
    const originalMethod = descriptor.value!;

    descriptor.value = function (...rest: any[]): ApiResult {
      const { error } = schema.validate({ ...rest });

      if (error) {
        console.error(error.message);
        return new ErrorApiResult({ statusCode: 400, message: error?.message });
      }

      return originalMethod.apply(this, arguments);
    };
  };
}

Currently, I want to note that this works perfectly except I have to create my validation schema like so:

export const Schema = {
  update: Joi.object({
    0: Joi.string(),
    1: ModelSchema,
  }),
}

The main problem with this is that the validation error messages Joi generates label the fields like 0.user.name instead of model.user.name, etc.

I would like to instead write the above endpoint schema like:

export const Schema = {
  update: Joi.object({
    id: Joi.string().required(),
    model: ModelSchema,
  }),
}

But I'm at a loss as to how. Looking at Joi.array(), it seems to be designed only to handle collections of objects, and not strict argument arrays.

EDIT: I've tried to use the .label() method to change the label in error messages, but this doesn't work if the error lies with a nested key. For example, it works if my id argument doesn't validate properly, but it doesn't display properly if my ModelSchema fails to validate on a child property. It still displays as "1.user.name" in error messages.


Solution

  • I was not able to solve my issue with defining Joi validation on a specific array, but I was able to use my Joi schema metadata to build a custom object that satisfies the Joi schema, as follows:

    export function Validation(schema: ObjectSchema) {
      return (target: any, propName: string | symbol, descriptor: PropertyDescriptor): void => {
        const originalMethod = descriptor.value!;
    
        descriptor.value = function (...rest: any[]): ApiResult {
          const validationObject: any = {};
          const keys: string[] = Array.from((schema as any)._ids._byKey.keys());
          for (let i = 0; i < keys.length; i++) {
            validationObject[keys[i]] = rest[i];
          }
          const { error } = schema.validate(validationObject);
    
          if (error) {
            console.error(error.message);
            return new ErrorApiResult({ statusCode: 400, message: error?.message });
          }
    
          return originalMethod.apply(this, arguments);
        };
      };
    }
    

    I have to break Typescript's type safety to access these fields at runtime. So this isn't super ideal. But as long as the order of the keys in the validation object matches the order of the arguments in my function I'm validating, it does work.