Skip to content

Commit

Permalink
feat: support generating custom cast error message with a function
Browse files Browse the repository at this point in the history
Fix #3162
  • Loading branch information
vkarpov15 committed Jul 12, 2023
1 parent c1c0dcc commit 57a5db5
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 16 deletions.
19 changes: 10 additions & 9 deletions lib/error/cast.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,9 @@ class CastError extends MongooseError {
constructor(type, value, path, reason, schemaType) {
// If no args, assume we'll `init()` later.
if (arguments.length > 0) {
const stringValue = getStringValue(value);
const valueType = getValueType(value);
const messageFormat = getMessageFormat(schemaType);
const msg = formatMessage(null, type, stringValue, path, messageFormat, valueType, reason);
const msg = formatMessage(null, type, value, path, messageFormat, valueType, reason);
super(msg);
this.init(type, value, path, reason, schemaType);
} else {
Expand Down Expand Up @@ -77,7 +76,7 @@ class CastError extends MongooseError {
*/
setModel(model) {
this.model = model;
this.message = formatMessage(model, this.kind, this.stringValue, this.path,
this.message = formatMessage(model, this.kind, this.value, this.path,
this.messageFormat, this.valueType);
}
}
Expand Down Expand Up @@ -111,10 +110,8 @@ function getValueType(value) {
}

function getMessageFormat(schemaType) {
const messageFormat = schemaType &&
schemaType.options &&
schemaType.options.cast || null;
if (typeof messageFormat === 'string') {
const messageFormat = schemaType && schemaType._castErrorMessage || null;
if (typeof messageFormat === 'string' || typeof messageFormat === 'function') {
return messageFormat;
}
}
Expand All @@ -123,8 +120,9 @@ function getMessageFormat(schemaType) {
* ignore
*/

function formatMessage(model, kind, stringValue, path, messageFormat, valueType, reason) {
if (messageFormat != null) {
function formatMessage(model, kind, value, path, messageFormat, valueType, reason) {
if (typeof messageFormat === 'string') {
const stringValue = getStringValue(value);
let ret = messageFormat.
replace('{KIND}', kind).
replace('{VALUE}', stringValue).
Expand All @@ -134,7 +132,10 @@ function formatMessage(model, kind, stringValue, path, messageFormat, valueType,
}

return ret;
} else if (typeof messageFormat === 'function') {
return messageFormat(value, path, model, kind);
} else {
const stringValue = getStringValue(value);
const valueTypeMsg = valueType ? ' (type ' + valueType + ')' : '';
let ret = 'Cast to ' + kind + ' failed for value ' +
stringValue + valueTypeMsg + ' at path "' + path + '"';
Expand Down
20 changes: 17 additions & 3 deletions lib/schematype.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,11 @@ function SchemaType(path, options, instance) {
const keys = Object.keys(this.options);
for (const prop of keys) {
if (prop === 'cast') {
this.castFunction(this.options[prop]);
if (Array.isArray(this.options[prop])) {
this.castFunction.apply(this, this.options[prop]);
} else {
this.castFunction(this.options[prop]);
}
continue;
}
if (utils.hasUserDefinedProperty(this.options, prop) && typeof this[prop] === 'function') {
Expand Down Expand Up @@ -255,14 +259,24 @@ SchemaType.cast = function cast(caster) {
* @api public
*/

SchemaType.prototype.castFunction = function castFunction(caster) {
SchemaType.prototype.castFunction = function castFunction(caster, message) {
if (arguments.length === 0) {
return this._castFunction;
}

if (caster === false) {
caster = this.constructor._defaultCaster || (v => v);
}
this._castFunction = caster;
if (typeof caster === 'string') {
this._castErrorMessage = caster;
return this._castFunction;
}
if (caster != null) {
this._castFunction = caster;
}
if (message != null) {
this._castErrorMessage = message;
}

return this._castFunction;
};
Expand Down
6 changes: 3 additions & 3 deletions test/model.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,8 @@ describe('Model', function() {
describe('defaults', function() {
it('to a non-empty array', function() {
const DefaultArraySchema = new Schema({
arr: { type: Array, cast: String, default: ['a', 'b', 'c'] },
single: { type: Array, cast: String, default: ['a'] }
arr: { type: Array, default: ['a', 'b', 'c'] },
single: { type: Array, default: ['a'] }
});
const DefaultArray = db.model('Test', DefaultArraySchema);
const arr = new DefaultArray();
Expand All @@ -223,7 +223,7 @@ describe('Model', function() {

it('empty', function() {
const DefaultZeroCardArraySchema = new Schema({
arr: { type: Array, cast: String, default: [] },
arr: { type: Array, default: [] },
auto: [Number]
});
const DefaultZeroCardArray = db.model('Test', DefaultZeroCardArraySchema);
Expand Down
19 changes: 19 additions & 0 deletions test/schema.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2396,6 +2396,25 @@ describe('schema', function() {
assert.ok(threw);
});

it('with function cast error format', function() {
const schema = Schema({
num: {
type: Number,
cast: [null, value => `${value} isn't a number`]
}
});

let threw = false;
try {
schema.path('num').cast('horseradish');
} catch (err) {
threw = true;
assert.equal(err.name, 'CastError');
assert.equal(err.message, 'horseradish isn\'t a number');
}
assert.ok(threw);
});

it('with objectids', function() {
const schema = Schema({
userId: {
Expand Down
6 changes: 5 additions & 1 deletion types/schematypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,11 @@ declare module 'mongoose' {
validate?: SchemaValidator<T> | AnyArray<SchemaValidator<T>>;

/** Allows overriding casting logic for this individual path. If a string, the given string overwrites Mongoose's default cast error message. */
cast?: string;
cast?: string |
boolean |

Check warning on line 67 in types/schematypes.d.ts

View workflow job for this annotation

GitHub Actions / Lint TS-Files

Expected indentation of 4 spaces but found 6
((value: any) => T) |

Check warning on line 68 in types/schematypes.d.ts

View workflow job for this annotation

GitHub Actions / Lint TS-Files

Expected indentation of 4 spaces but found 6
[(value: any) => T, string] |

Check warning on line 69 in types/schematypes.d.ts

View workflow job for this annotation

GitHub Actions / Lint TS-Files

Expected indentation of 4 spaces but found 6
[((value: any) => T) | null, (value: any, path: string, model: Model<T>, kind: string) => string];

Check warning on line 70 in types/schematypes.d.ts

View workflow job for this annotation

GitHub Actions / Lint TS-Files

Expected indentation of 4 spaces but found 6

/**
* If true, attach a required validator to this path, which ensures this path
Expand Down

0 comments on commit 57a5db5

Please sign in to comment.