Mongoose: Support mongoose.SchemaType.cast() to overwrite Mongoose's built-in casting for a given SchemaType

Created on 24 Sep 2018  路  9Comments  路  Source: Automattic/mongoose

I find it very hard to write reliable validation code when values are silently converted to another type.

#!/usr/bin/env node
(async function () {
  const mongoose = require('mongoose');
  mongoose.connect('mongodb://localhost:27017/test', {useNewUrlParser: true});

  const schema = new mongoose.Schema({
    text: String
  });
  const Model = mongoose.model('model', schema);
  const doc = new Model();

  doc.text = 123123;
  console.log(doc.text);
  console.log(typeof doc.text);
  console.log(doc.validateSync());
})();

I think there should be a toggle to enable strict mode which would cause a validation error when there is a type mismatch.

docs

Most helpful comment

@sebastian-nowak what do you think about making it possible to edit this on a per-schema-type basis? So instead of toggling casting on/off at the global level, you can do stuff like:

mongoose.Number.cast(false); // Disable casting for numbers
mongoose.ObjectId.cast(v => typeof v === 'string' ? new ObjectId(v) : v); // Custom casting for ObjectIds

What do you think of that idea?

All 9 comments

This is a great idea. We'll give some thought to the implementation and add this. Thanks @sebastian-nowak !

Thanks @lineus, that's great news :)

I think one edge case to consider is what to do about ObjectIds. I rarely see code that creates them as objects, they're often used as strings. I don't know what's the best option.

  • Maybe it's fine to require creating proper ObjectId objects by hand? After all, it's called 'strict mode'.
  • Or maybe ObjectIds should be still automatically converted, while all other types would throw on type mismatch
  • Or maybe strict mode should be flexible and it would be possible to set the behavior manually for each type

I think I would be in favor of the first option, but I don't have a strong opinion. One benefit of this approach is that it's probably the easiest option to implement.

Cheers!

@sebastian-nowak what do you think about making it possible to edit this on a per-schema-type basis? So instead of toggling casting on/off at the global level, you can do stuff like:

mongoose.Number.cast(false); // Disable casting for numbers
mongoose.ObjectId.cast(v => typeof v === 'string' ? new ObjectId(v) : v); // Custom casting for ObjectIds

What do you think of that idea?

I think it would be okay for majority of use cases, assuming that:

  • Cast callback would be called only on type mismatch, otherwise there would be a lot of boilerplate code
  • It would be somehow possible to invalidate the field from within the callback. Perhaps by throwing an exception? The value would be assigned as is, but the field would be invalidated.

A nice extra thing to have would be an ability to override the cast method on a schema level. It would add a lot of flexibility and definitely cover all use cases:

mongoose.String.cast(false);

const schema = new mongoose.Schema({
  userId: {
    type: String,
    cast: (
      v => typeof v === 'number' ? 
        String(v) : 
        throw new SomeKindOfValidationError('Validation error goes here')
    )
  }
});

Yeah that sounds like a great idea as well. Right now, Mongoose's cast functions throw a CastError if casting fails, so your logic would work in theory. I'll prioritize custom cast functions for built-in types for the next release.

Are there docs for this somewhere?

@ronakvora we could use a tutorial for this, but right now the only docs are the schematype API docs for SchemaType.cast()

@vkarpov15 thanks for this solution, it's actually vital for validation!

From what i understand by reading the documentation the override concerns all the properties of that specific type. I think @sebastian-nowak's suggestion for a more fine-grained solution will be very welcomed in addition to the current solution:

A nice extra thing to have would be an ability to override the cast method on a schema level. It would add a lot of flexibility and definitely cover all use cases:

const schema = new mongoose.Schema({
  userId: {
    type: String,
    cast: (
      v => typeof v === 'number' ? 
        String(v) : 
        throw new SomeKindOfValidationError('Validation error goes here')
    )
  }
});

For simplicity, also consider adding something like the following option that can keep it clean for some cases where you just don't want any casting:

const schema = new mongoose.Schema({
  userId: {
    type: String,
    cast: false
  }
});

What do you say?

@nironater we added something similar for #8300 for the ability to change the cast error for a given path, I opened a separate issue to track this feature request: #8407

Was this page helpful?
0 / 5 - 0 ratings