Skip to content

Commit

Permalink
Merge pull request #12462 from hasezoey/setMultipleOptionsAtOnce
Browse files Browse the repository at this point in the history
feat(index): add ability to set multiple options at once in ".set"
  • Loading branch information
vkarpov15 authored Oct 2, 2022
2 parents 24a768b + 6596b2c commit 4d84a38
Show file tree
Hide file tree
Showing 7 changed files with 276 additions and 48 deletions.
101 changes: 101 additions & 0 deletions lib/error/setOptionError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*!
* Module requirements
*/

'use strict';

const MongooseError = require('./mongooseError');
const util = require('util');
const combinePathErrors = require('../helpers/error/combinePathErrors');

class SetOptionError extends MongooseError {
/**
* Mongoose.set Error
*
* @api private
* @inherits MongooseError
*/
constructor() {
super('');

this.errors = {};
}

/**
* Console.log helper
*/
toString() {
return combinePathErrors(this);
}

/**
* inspect helper
* @api private
*/
inspect() {
return Object.assign(new Error(this.message), this);
}

/**
* add message
* @param {String} key
* @param {String|Error} error
* @api private
*/
addError(key, error) {
if (error instanceof SetOptionError) {
const { errors } = error;
for (const optionKey of Object.keys(errors)) {
this.addError(optionKey, errors[optionKey]);
}

return;
}

this.errors[key] = error;
this.message = combinePathErrors(this);
}
}


if (util.inspect.custom) {
// Avoid Node deprecation warning DEP0079
SetOptionError.prototype[util.inspect.custom] = SetOptionError.prototype.inspect;
}

/**
* Helper for JSON.stringify
* Ensure `name` and `message` show up in toJSON output re: gh-9847
* @api private
*/
Object.defineProperty(SetOptionError.prototype, 'toJSON', {
enumerable: false,
writable: false,
configurable: true,
value: function() {
return Object.assign({}, this, { name: this.name, message: this.message });
}
});


Object.defineProperty(SetOptionError.prototype, 'name', {
value: 'SetOptionError'
});

class SetOptionInnerError extends MongooseError {
/**
* Error for the "errors" array in "SetOptionError" with consistent message
* @param {String} key
*/
constructor(key) {
super(`"${key}" is not a valid option to set`);
}
}

SetOptionError.SetOptionInnerError = SetOptionInnerError;

/*!
* Module exports
*/

module.exports = SetOptionError;
26 changes: 3 additions & 23 deletions lib/error/validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
const MongooseError = require('./mongooseError');
const getConstructorName = require('../helpers/getConstructorName');
const util = require('util');
const combinePathErrors = require('../helpers/error/combinePathErrors');

class ValidationError extends MongooseError {
/**
Expand Down Expand Up @@ -38,7 +39,7 @@ class ValidationError extends MongooseError {
* Console.log helper
*/
toString() {
return this.name + ': ' + _generateMessage(this);
return this.name + ': ' + combinePathErrors(this);
}

/**
Expand Down Expand Up @@ -66,7 +67,7 @@ class ValidationError extends MongooseError {
}

this.errors[path] = error;
this.message = this._message + ': ' + _generateMessage(this);
this.message = this._message + ': ' + combinePathErrors(this);
}
}

Expand Down Expand Up @@ -95,27 +96,6 @@ Object.defineProperty(ValidationError.prototype, 'name', {
value: 'ValidationError'
});

/*!
* ignore
*/

function _generateMessage(err) {
const keys = Object.keys(err.errors || {});
const len = keys.length;
const msgs = [];
let key;

for (let i = 0; i < len; ++i) {
key = keys[i];
if (err === err.errors[key]) {
continue;
}
msgs.push(key + ': ' + err.errors[key].message);
}

return msgs.join(', ');
}

/*!
* Module exports
*/
Expand Down
22 changes: 22 additions & 0 deletions lib/helpers/error/combinePathErrors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict';

/*!
* ignore
*/

module.exports = function combinePathErrors(err) {
const keys = Object.keys(err.errors || {});
const len = keys.length;
const msgs = [];
let key;

for (let i = 0; i < len; ++i) {
key = keys[i];
if (err === err.errors[key]) {
continue;
}
msgs.push(key + ': ' + err.errors[key].message);
}

return msgs.join(', ');
};
72 changes: 54 additions & 18 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const trusted = require('./helpers/query/trusted').trusted;
const sanitizeFilter = require('./helpers/query/sanitizeFilter');
const isBsonType = require('./helpers/isBsonType');
const MongooseError = require('./error/mongooseError');
const SetOptionError = require('./error/setOptionError');

const defaultMongooseSymbol = Symbol.for('mongoose:default');

Expand Down Expand Up @@ -182,6 +183,9 @@ Mongoose.prototype.setDriver = function setDriver(driver) {
/**
* Sets mongoose options
*
* `key` can be used a object to set multiple options at once.
* If a error gets thrown for one option, other options will still be evaluated.
*
* #### Example:
*
* mongoose.set('test', value) // sets the 'test' option to `value`
Expand All @@ -190,6 +194,8 @@ Mongoose.prototype.setDriver = function setDriver(driver) {
*
* mongoose.set('debug', function(collectionName, methodName, ...methodArgs) {}); // use custom function to log collection methods + arguments
*
* mongoose.set({ debug: true, autoIndex: false }); // set multiple options at once
*
* Currently supported options are:
* - 'applyPluginsToChildSchemas': `true` by default. Set to false to skip applying global plugins to child schemas
* - 'applyPluginsToDiscriminators': `false` by default. Set to true to apply global plugins to discriminator schemas. This typically isn't necessary because plugins are applied to the base schema and discriminators copy all middleware, methods, statics, and properties from the base schema.
Expand All @@ -213,36 +219,66 @@ Mongoose.prototype.setDriver = function setDriver(driver) {
* - 'toJSON': `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toJSON()`](/docs/api.html#document_Document-toJSON), for determining how Mongoose documents get serialized by `JSON.stringify()`
* - 'toObject': `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toObject()`](/docs/api.html#document_Document-toObject)
*
* @param {String} key
* @param {String|Function|Boolean} value
* @param {String|Object} key The name of the option or a object of multiple key-value pairs
* @param {String|Function|Boolean} value The value of the option, unused if "key" is a object
* @returns {Mongoose} The used Mongoose instnace
* @api public
*/

Mongoose.prototype.set = function(key, value) {
const _mongoose = this instanceof Mongoose ? this : mongoose;

if (VALID_OPTIONS.indexOf(key) === -1) {
throw new Error(`\`${key}\` is an invalid option.`);
}
if (arguments.length === 1 && typeof key !== 'object') {
if (VALID_OPTIONS.indexOf(key) === -1) {
const error = new SetOptionError();
error.addError(key, new SetOptionError.SetOptionInnerError(key));
throw error;
}

if (arguments.length === 1) {
return _mongoose.options[key];
}

_mongoose.options[key] = value;
let options = {};

if (key === 'objectIdGetter') {
if (value) {
Object.defineProperty(mongoose.Types.ObjectId.prototype, '_id', {
enumerable: false,
configurable: true,
get: function() {
return this;
}
});
} else {
delete mongoose.Types.ObjectId.prototype._id;
if (arguments.length === 2) {
options = { [key]: value };
}

if (arguments.length === 1 && typeof key === 'object') {
options = key;
}

// array for errors to collect all errors for all key-value pairs, like ".validate"
let error = undefined;

for (const [optionKey, optionValue] of Object.entries(options)) {
if (VALID_OPTIONS.indexOf(optionKey) === -1) {
if (!error) {
error = new SetOptionError();
}
error.addError(optionKey, new SetOptionError.SetOptionInnerError(optionKey));
continue;
}

_mongoose.options[optionKey] = optionValue;

if (optionKey === 'objectIdGetter') {
if (optionValue) {
Object.defineProperty(mongoose.Types.ObjectId.prototype, '_id', {
enumerable: false,
configurable: true,
get: function() {
return this;
}
});
} else {
delete mongoose.Types.ObjectId.prototype._id;
}
}
}

if (error) {
throw error;
}

return _mongoose;
Expand Down
Loading

0 comments on commit 4d84a38

Please sign in to comment.