Search code examples
typescriptmongodbmongoose

Typing Mongoose validation functions


I am helping with a typescript app using MongoDB to persist data. One of the things that we are trying to do is to get rid of any usage.

The below code used to define a part of the mongoose schema:

priceMax: {
  max: 10000000,
  min: 0,
  required: function (this: FeePricing & Document) {
    return this.priceMin === undefined;
  },
  type: Number,
  validate: [
    {
      message: 'Max price cannot be lower than min price',
      validator: function (v: number) {
        if ((this as any).priceMax === null || (this as any).priceMax === undefined) return true;
        return (this as any).priceMin ? v >= (this as any).priceMin : v >= 0;
      },
    },
    {
      message: 'Max price cannot be higher than 50000 for this feeType',
      validator: function (v: number) {
        return !(!feeTypesWithoutMaxLimit.includes((this as any).feeType) && v > 50000);
      },
    },
  ],
},
priceMin: {
  max: 10000000,
  min: 0,
  required: function () {
    return (this as any).priceMax === undefined;
  },
  type: Number,
  validate: {
    message: 'priceMin cannot be higher than priceMax',
    validator: function (v: number) {
      return (this as any).priceMax ? v <= (this as any).priceMax : v >= 0;
    },
  },
},
updatedAt: { type: Date },
updatedBy: { type: String },

I, sort of, understand what the functions are doing, but the types here confuse me.

How could I get rid of this as any? Why not just use FeePricing for the type - eg (this as FeePricing)? FeePricing appears that to be is just another type from my app [, which has priceMin and priceMax] combined with a Document interface. How does the Document from ReactJS help here? Why is it needed? Is this in validate the above-defined type FeePricing & Document?

Thank you


Solution

  • this is the context of your validation configuration. Because TypeScript cannot infer its type (due to the fact that it can be changed to anything), I would recommend creating your own custom type, like FeePricing. I am not too sure what properties your current FeePricing contains as it was not included in the example, but I would have it as follows:

    interface FeePricing {
      priceMin?: mongoose.Schema.Types.Number | null,
      priceMax?: mongoose.Schema.Types.Number | null,
      feeType?: mongoose.Schema.Types.Number | null,
    }
    

    Then you can use it like this:

    (this as FeePricing).priceMax
    

    The reason why the properties are optional and also null is because I can see that some of your logic checks whether they are undefined or null, hence these types will reflect that they may not exist in runtime and help you validate correctly. Additionally if FeePricing type is used for something else you can ofcourse change this type name to something else.

    To answer your question about ReactJs Document, it does not add any help to infer the mongoose configuration type and can really be removed.