mongoose icon indicating copy to clipboard operation
mongoose copied to clipboard

Ability to change CastError error message

Open AlphaHydrae opened this issue 6 years ago • 5 comments

Do you want to request a feature or report a bug?

I am reporting a bug.

What is the current behavior?

A schema property of type ObjectId cannot be validated with a custom validator because the casting to ObjectId fails before the validator is called.

If the current behavior is a bug, please provide the steps to reproduce.

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const schema = new Schema({
  user: {
    type: Schema.Types.ObjectId,
    validate: {
      message: 'User ID is invalid',
      validator: function(value) {
        return mongoose.Types.ObjectId.isValid(value);
      }
    }
  }
});

const Thing = mongoose.model('Thing', schema);

const thing = new Thing({
  user: 'foo'
});

mongoose.connect('mongodb://localhost/test');

thing.save()
  .then(() => console.log('Thing saved'))
  .catch(err => console.error(err.stack))
  .finally(() => mongoose.disconnect());

The error is ValidationError: Thing validation failed: user: Cast to ObjectID failed for value "foo" at path "user".

What is the expected behavior?

I should be able to validate the format of the property before casting. With the code above, I am expecting to see a validation error with my custom error message.

Note that this seems to have already happened in the pase: https://github.com/Automattic/mongoose/issues/2237 (Aug-Dec 2014)

What are the versions of Node.js, Mongoose and MongoDB you are using? Note that "latest" is not a version.

  • Node.js: 12.10.0
  • MongoDB: 4.0.3
  • Mongoose: 5.7.7

AlphaHydrae avatar Nov 04 '19 16:11 AlphaHydrae

This is by design - casting is a separate process that happens before validation, which means custom validators can assume that the value they're validating either:

  1. The correct type, e.g. type: Number means typeof value === 'number'
  2. Or value == null (nullish)

This behavior is not something we're going to change, and I'd question why you need a custom validator that checks if value is something that can be casted to an ObjectId when Mongoose already performs that check. However, you can define custom casting for individual types if you deem it necessary.

vkarpov15 avatar Nov 06 '19 18:11 vkarpov15

@vkarpov15 Sorry, I had not read the changelog for v5 closely enough so I missed the changes regarding casting logic. Thanks for the explanation.

The reason I want to validate the value with a custom validator is that assuming I am using Mongoose to validate all my API input, I may want my users to get a more human-friendly validation message than a casting error. In the case of an ObjectId referencing a user like in my example, I might not even want them to know that it was a casting error, just that it's not a valid user ID.

I used to make async validators that use the same error message whether the reason is that the user doesn't exist or the ID is not a valid ObjectId, which is an internal implementation detail of my API I don't want to tell the user about. In both cases the message would be: there is no user with that ID. Knowing that it's not a valid ObjectId allows me to skip the database query internally if I want, but I would prefer that this fact does not make it into the error message.

Is there a way to customize the error messages of casting errors?

Otherwise, I seem to get the behavior I want by bypassing casting completely. Although I assume this may not be a good idea, as in it might break something else in Mongoose?

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const identity = v => v;
mongoose.ObjectId.cast(identity);
mongoose.Number.cast(identity);

const schema = new Schema({
  test: {
    type: Number,
    validate: {
      message: '{VALUE} is not a valid number',
      validator: function(value) {
        return typeof value === 'number';
      }
    }
  },
  user: {
    type: Schema.Types.ObjectId,
    validate: {
      message: 'No user with ID {VALUE}',
      validator: function(value) {
        return mongoose.Types.ObjectId.isValid(value);
      }
    }
  }
});

const Thing = mongoose.model('Thing', schema);

const thing = new Thing({
  test: '胃',
  user: 'foo'
});

mongoose.connect('mongodb://localhost/test');

thing.save()
  .then(() => console.log('Thing saved'))
  .catch(err => console.error(err.stack))
  .finally(() => mongoose.disconnect());

Error message with this solution, which is what I wanted: ValidationError: Thing validation failed: test: 胃 is not a valid number, user: No user with ID foo

AlphaHydrae avatar Nov 06 '19 19:11 AlphaHydrae

That's a good point, there's no way to change the error message for cast errors. You would have to transform the error in error handling middleware, or define custom casting.

vkarpov15 avatar Nov 09 '19 18:11 vkarpov15

@vkarpov15 I think the functionality for {MODEL} does not work.

Am I using it incorrectly?

'use strict';
const mongoose = require('./');
const { Schema } = mongoose;
const assert = require('node:assert');


run().catch(console.error);

async function run() {
  await mongoose.connect('mongodb://localhost:27017/test');
  await mongoose.connection.dropDatabase();

  const userSchema = new Schema({
    age: { type: Number, cast: '{VALUE} is not a valid number for model {MODEL}' }
  });


  const User = mongoose.model('User', userSchema);
  User;
  const user = new User({ age: 'twenty' });
  const err = user.validateSync();
  assert.ok(err);
  assert.equal(err.errors['age'].message, 'CastError: "twenty" is not a valid number for model User');

  console.log(mongoose.version);
  console.log('All assertions passed.');
}

Output:

AssertionError [ERR_ASSERTION]: '"twenty" is not a valid number for model {MODEL}' == 'CastError: "twenty" is not a valid number for model User'
    at run (/Users/hafez/dev/mongoose/test.js:23:10)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
  generatedMessage: true,
  code: 'ERR_ASSERTION',
  actual: '"twenty" is not a valid number for model {MODEL}',
  expected: 'CastError: "twenty" is not a valid number for model User',
  operator: '=='
}

If you think this is a bug, I'm happy to look at it, I'm asking because I suspect I'm missing something.

AbdelrahmanHafez avatar Jun 16 '25 21:06 AbdelrahmanHafez

{MODEL} is not currently supported, we only support PATH, VALUE, and KIND currently: https://mongoosejs.com/docs/validation.html#cast-errors. You can add {MODEL} if you think that's useful :+1:

vkarpov15 avatar Jun 19 '25 20:06 vkarpov15