2

I have issues extending joi class with custom operators. I want to validate mongodb Ids, but the extended object throws following error:

error: uncaughtException: JoiObj.string(...).objectId is not a function
TypeError: JoiObj.string(...).objectId is not a function

Code is following:

import Joi from 'joi';
import * as mongodb from 'mongodb';

interface ExtendedStringSchema extends Joi.StringSchema {
    objectId(): this;
}

interface ExtendedJoi extends Joi.Root {
    string(): ExtendedStringSchema;
}

const JoiObj: ExtendedJoi = Joi.extend({
    base: Joi.string(),
    type: 'objectId',
    messages: {
        'objectId.invalid': '"{{#label}}" must be a valid mongo id'
    },
    validate(value, helpers) {
        if (!mongodb.ObjectId.isValid(value)) {
            return helpers.error('objectId.invalid');
        }

        return value;
    }
});

const objIdSchema = JoiObj.object({
    id: JoiObj.string().objectId()
});

I found 2 examples:

https://github.com/sideway/joi/issues/2357

How to extend a module from npm using TypeScript?

however they use different properties than what is described in TS definition file and thus does not work.

P. Vitvera
  • 95
  • 1
  • 7

2 Answers2

2

You want to extend the Joi.string() base. Keep in mind, that you can't validate new mongodb.ObjectID() because it is of type object. You extended Joi.string() and this checks first if your value is of type string. And it will stop validating if it isn't. You can only validate new mongodb.ObjectID().toHexString() which looks like: "5f91a1449b13e3010c5548a2".

This answers is using joi 17.2.1 and mongodb 3.6.2

import Joi from 'joi';
import * as mongodb from 'mongodb';

interface ExtendedStringSchema extends Joi.StringSchema {
    objectId(): this;
}

interface ExtendedJoi extends Joi.Root {
    string(): ExtendedStringSchema;
}

const stringObjectExtension: Joi.Extension = {
    type: 'string',
    base: Joi.string(),
    messages: {
        'string.objectId': '{{#label}} must be a valid mongo id'
    },
    rules: {
      objectId: {
        validate: (value: any, helpers) => {
          if (!mongodb.ObjectId.isValid(value)) {
              return helpers.error('string.objectId')
          }

          return value;
        }
      }
    }
};

// create extended Joi
const JoiObj: ExtendedJoi = Joi.extend(stringObjectExtension);

// create new mongodb id
const id = new mongodb.ObjectID();

const objIdSchema = JoiObj.object({
    id: JoiObj.string().objectId()
});

// will fail because it won't pass the Joi.string() validation
const data1 = {
  id: id
};
console.log(objIdSchema.validate(data1).error);

// will succeed
const data2 = {
  id: id.toHexString()
};
console.log(objIdSchema.validate(data2).error);

a1300
  • 2,633
  • 2
  • 14
  • 18
  • 1
    Thanks a lot a1300, I was also missing the rules part even tho I messed with it a bit, the structure was not also correct. This works flawlesly. Thank you! – P. Vitvera Oct 22 '20 at 17:19
  • extending your solution, you can pass `prepare: (val) => ({ value: val.toHexString ? val.toHexString() : val })` in `stringObjectExtension` object. It will take care of conversion before validation instead of manually converting it in data. – gtmsingh May 09 '21 at 12:26
0

I also had this problem. I solved it with this package.

https://www.npmjs.com/package/joi-oid

const Joi = require('joi-oid')

const schema = Joi.object({
  id: Joi.objectId(),
  name: Joi.string(),
  age: Joi.number().min(18),
})

Good luck :)