UNPKG

44.2 kBJavaScriptView Raw
1'use strict';
2
3/*!
4 * Module dependencies.
5 */
6
7const MongooseError = require('./error/index');
8const SchemaTypeOptions = require('./options/SchemaTypeOptions');
9const $exists = require('./schema/operators/exists');
10const $type = require('./schema/operators/type');
11const get = require('./helpers/get');
12const handleImmutable = require('./helpers/schematype/handleImmutable');
13const immediate = require('./helpers/immediate');
14const schemaTypeSymbol = require('./helpers/symbols').schemaTypeSymbol;
15const util = require('util');
16const utils = require('./utils');
17const validatorErrorSymbol = require('./helpers/symbols').validatorErrorSymbol;
18
19const CastError = MongooseError.CastError;
20const ValidatorError = MongooseError.ValidatorError;
21
22/**
23 * SchemaType constructor. Do **not** instantiate `SchemaType` directly.
24 * Mongoose converts your schema paths into SchemaTypes automatically.
25 *
26 * ####Example:
27 *
28 * const schema = new Schema({ name: String });
29 * schema.path('name') instanceof SchemaType; // true
30 *
31 * @param {String} path
32 * @param {SchemaTypeOptions} [options] See [SchemaTypeOptions docs](/docs/api/schematypeoptions.html)
33 * @param {String} [instance]
34 * @api public
35 */
36
37function SchemaType(path, options, instance) {
38 this[schemaTypeSymbol] = true;
39 this.path = path;
40 this.instance = instance;
41 this.validators = [];
42 this.getters = this.constructor.hasOwnProperty('getters') ?
43 this.constructor.getters.slice() :
44 [];
45 this.setters = [];
46
47 options = options || {};
48 const defaultOptions = this.constructor.defaultOptions || {};
49 const defaultOptionsKeys = Object.keys(defaultOptions);
50
51 for (const option of defaultOptionsKeys) {
52 if (defaultOptions.hasOwnProperty(option) && !options.hasOwnProperty(option)) {
53 options[option] = defaultOptions[option];
54 }
55 }
56
57
58 if (options.select == null) {
59 delete options.select;
60 }
61
62 const Options = this.OptionsConstructor || SchemaTypeOptions;
63 this.options = new Options(options);
64 this._index = null;
65
66
67 if (utils.hasUserDefinedProperty(this.options, 'immutable')) {
68 this.$immutable = this.options.immutable;
69
70 handleImmutable(this);
71 }
72
73 const keys = Object.keys(this.options);
74 for (const prop of keys) {
75 if (prop === 'cast') {
76 continue;
77 }
78 if (utils.hasUserDefinedProperty(this.options, prop) && typeof this[prop] === 'function') {
79 // { unique: true, index: true }
80 if (prop === 'index' && this._index) {
81 if (options.index === false) {
82 const index = this._index;
83 if (typeof index === 'object' && index != null) {
84 if (index.unique) {
85 throw new Error('Path "' + this.path + '" may not have `index` ' +
86 'set to false and `unique` set to true');
87 }
88 if (index.sparse) {
89 throw new Error('Path "' + this.path + '" may not have `index` ' +
90 'set to false and `sparse` set to true');
91 }
92 }
93
94 this._index = false;
95 }
96 continue;
97 }
98
99 const val = options[prop];
100 // Special case so we don't screw up array defaults, see gh-5780
101 if (prop === 'default') {
102 this.default(val);
103 continue;
104 }
105
106 const opts = Array.isArray(val) ? val : [val];
107
108 this[prop].apply(this, opts);
109 }
110 }
111
112 Object.defineProperty(this, '$$context', {
113 enumerable: false,
114 configurable: false,
115 writable: true,
116 value: null
117 });
118}
119
120/*!
121 * ignore
122 */
123
124SchemaType.prototype.OptionsConstructor = SchemaTypeOptions;
125
126/**
127 * Get/set the function used to cast arbitrary values to this type.
128 *
129 * ####Example:
130 *
131 * // Disallow `null` for numbers, and don't try to cast any values to
132 * // numbers, so even strings like '123' will cause a CastError.
133 * mongoose.Number.cast(function(v) {
134 * assert.ok(v === undefined || typeof v === 'number');
135 * return v;
136 * });
137 *
138 * @param {Function|false} caster Function that casts arbitrary values to this type, or throws an error if casting failed
139 * @return {Function}
140 * @static
141 * @receiver SchemaType
142 * @function cast
143 * @api public
144 */
145
146SchemaType.cast = function cast(caster) {
147 if (arguments.length === 0) {
148 return this._cast;
149 }
150 if (caster === false) {
151 caster = v => v;
152 }
153 this._cast = caster;
154
155 return this._cast;
156};
157
158/**
159 * Sets a default option for this schema type.
160 *
161 * ####Example:
162 *
163 * // Make all strings be trimmed by default
164 * mongoose.SchemaTypes.String.set('trim', true);
165 *
166 * @param {String} option The name of the option you'd like to set (e.g. trim, lowercase, etc...)
167 * @param {*} value The value of the option you'd like to set.
168 * @return {void}
169 * @static
170 * @receiver SchemaType
171 * @function set
172 * @api public
173 */
174
175SchemaType.set = function set(option, value) {
176 if (!this.hasOwnProperty('defaultOptions')) {
177 this.defaultOptions = Object.assign({}, this.defaultOptions);
178 }
179 this.defaultOptions[option] = value;
180};
181
182/**
183 * Attaches a getter for all instances of this schema type.
184 *
185 * ####Example:
186 *
187 * // Make all numbers round down
188 * mongoose.Number.get(function(v) { return Math.floor(v); });
189 *
190 * @param {Function} getter
191 * @return {this}
192 * @static
193 * @receiver SchemaType
194 * @function get
195 * @api public
196 */
197
198SchemaType.get = function(getter) {
199 this.getters = this.hasOwnProperty('getters') ? this.getters : [];
200 this.getters.push(getter);
201};
202
203/**
204 * Sets a default value for this SchemaType.
205 *
206 * ####Example:
207 *
208 * var schema = new Schema({ n: { type: Number, default: 10 })
209 * var M = db.model('M', schema)
210 * var m = new M;
211 * console.log(m.n) // 10
212 *
213 * Defaults can be either `functions` which return the value to use as the default or the literal value itself. Either way, the value will be cast based on its schema type before being set during document creation.
214 *
215 * ####Example:
216 *
217 * // values are cast:
218 * var schema = new Schema({ aNumber: { type: Number, default: 4.815162342 }})
219 * var M = db.model('M', schema)
220 * var m = new M;
221 * console.log(m.aNumber) // 4.815162342
222 *
223 * // default unique objects for Mixed types:
224 * var schema = new Schema({ mixed: Schema.Types.Mixed });
225 * schema.path('mixed').default(function () {
226 * return {};
227 * });
228 *
229 * // if we don't use a function to return object literals for Mixed defaults,
230 * // each document will receive a reference to the same object literal creating
231 * // a "shared" object instance:
232 * var schema = new Schema({ mixed: Schema.Types.Mixed });
233 * schema.path('mixed').default({});
234 * var M = db.model('M', schema);
235 * var m1 = new M;
236 * m1.mixed.added = 1;
237 * console.log(m1.mixed); // { added: 1 }
238 * var m2 = new M;
239 * console.log(m2.mixed); // { added: 1 }
240 *
241 * @param {Function|any} val the default value
242 * @return {defaultValue}
243 * @api public
244 */
245
246SchemaType.prototype.default = function(val) {
247 if (arguments.length === 1) {
248 if (val === void 0) {
249 this.defaultValue = void 0;
250 return void 0;
251 }
252
253 if (val != null && val.instanceOfSchema) {
254 throw new MongooseError('Cannot set default value of path `' + this.path +
255 '` to a mongoose Schema instance.');
256 }
257
258 this.defaultValue = val;
259 return this.defaultValue;
260 } else if (arguments.length > 1) {
261 this.defaultValue = utils.args(arguments);
262 }
263 return this.defaultValue;
264};
265
266/**
267 * Declares the index options for this schematype.
268 *
269 * ####Example:
270 *
271 * var s = new Schema({ name: { type: String, index: true })
272 * var s = new Schema({ loc: { type: [Number], index: 'hashed' })
273 * var s = new Schema({ loc: { type: [Number], index: '2d', sparse: true })
274 * var s = new Schema({ loc: { type: [Number], index: { type: '2dsphere', sparse: true }})
275 * var s = new Schema({ date: { type: Date, index: { unique: true, expires: '1d' }})
276 * s.path('my.path').index(true);
277 * s.path('my.date').index({ expires: 60 });
278 * s.path('my.path').index({ unique: true, sparse: true });
279 *
280 * ####NOTE:
281 *
282 * _Indexes are created [in the background](https://docs.mongodb.com/manual/core/index-creation/#index-creation-background)
283 * by default. If `background` is set to `false`, MongoDB will not execute any
284 * read/write operations you send until the index build.
285 * Specify `background: false` to override Mongoose's default._
286 *
287 * @param {Object|Boolean|String} options
288 * @return {SchemaType} this
289 * @api public
290 */
291
292SchemaType.prototype.index = function(options) {
293 this._index = options;
294 utils.expires(this._index);
295 return this;
296};
297
298/**
299 * Declares an unique index.
300 *
301 * ####Example:
302 *
303 * var s = new Schema({ name: { type: String, unique: true }});
304 * s.path('name').index({ unique: true });
305 *
306 * _NOTE: violating the constraint returns an `E11000` error from MongoDB when saving, not a Mongoose validation error._
307 *
308 * @param {Boolean} bool
309 * @return {SchemaType} this
310 * @api public
311 */
312
313SchemaType.prototype.unique = function(bool) {
314 if (this._index === false) {
315 if (!bool) {
316 return;
317 }
318 throw new Error('Path "' + this.path + '" may not have `index` set to ' +
319 'false and `unique` set to true');
320 }
321 if (this._index == null || this._index === true) {
322 this._index = {};
323 } else if (typeof this._index === 'string') {
324 this._index = { type: this._index };
325 }
326
327 this._index.unique = bool;
328 return this;
329};
330
331/**
332 * Declares a full text index.
333 *
334 * ###Example:
335 *
336 * var s = new Schema({name : {type: String, text : true })
337 * s.path('name').index({text : true});
338 * @param {Boolean} bool
339 * @return {SchemaType} this
340 * @api public
341 */
342
343SchemaType.prototype.text = function(bool) {
344 if (this._index === false) {
345 if (!bool) {
346 return;
347 }
348 throw new Error('Path "' + this.path + '" may not have `index` set to ' +
349 'false and `text` set to true');
350 }
351
352 if (this._index === null || this._index === undefined ||
353 typeof this._index === 'boolean') {
354 this._index = {};
355 } else if (typeof this._index === 'string') {
356 this._index = { type: this._index };
357 }
358
359 this._index.text = bool;
360 return this;
361};
362
363/**
364 * Declares a sparse index.
365 *
366 * ####Example:
367 *
368 * var s = new Schema({ name: { type: String, sparse: true } });
369 * s.path('name').index({ sparse: true });
370 *
371 * @param {Boolean} bool
372 * @return {SchemaType} this
373 * @api public
374 */
375
376SchemaType.prototype.sparse = function(bool) {
377 if (this._index === false) {
378 if (!bool) {
379 return;
380 }
381 throw new Error('Path "' + this.path + '" may not have `index` set to ' +
382 'false and `sparse` set to true');
383 }
384
385 if (this._index == null || typeof this._index === 'boolean') {
386 this._index = {};
387 } else if (typeof this._index === 'string') {
388 this._index = { type: this._index };
389 }
390
391 this._index.sparse = bool;
392 return this;
393};
394
395/**
396 * Defines this path as immutable. Mongoose prevents you from changing
397 * immutable paths unless the parent document has [`isNew: true`](/docs/api.html#document_Document-isNew).
398 *
399 * ####Example:
400 *
401 * const schema = new Schema({
402 * name: { type: String, immutable: true },
403 * age: Number
404 * });
405 * const Model = mongoose.model('Test', schema);
406 *
407 * await Model.create({ name: 'test' });
408 * const doc = await Model.findOne();
409 *
410 * doc.isNew; // false
411 * doc.name = 'new name';
412 * doc.name; // 'test', because `name` is immutable
413 *
414 * Mongoose also prevents changing immutable properties using `updateOne()`
415 * and `updateMany()` based on [strict mode](/docs/guide.html#strict).
416 *
417 * ####Example:
418 *
419 * // Mongoose will strip out the `name` update, because `name` is immutable
420 * Model.updateOne({}, { $set: { name: 'test2' }, $inc: { age: 1 } });
421 *
422 * // If `strict` is set to 'throw', Mongoose will throw an error if you
423 * // update `name`
424 * const err = await Model.updateOne({}, { name: 'test2' }, { strict: 'throw' }).
425 * then(() => null, err => err);
426 * err.name; // StrictModeError
427 *
428 * // If `strict` is `false`, Mongoose allows updating `name` even though
429 * // the property is immutable.
430 * Model.updateOne({}, { name: 'test2' }, { strict: false });
431 *
432 * @param {Boolean} bool
433 * @return {SchemaType} this
434 * @see isNew /docs/api.html#document_Document-isNew
435 * @api public
436 */
437
438SchemaType.prototype.immutable = function(bool) {
439 this.$immutable = bool;
440 handleImmutable(this);
441
442 return this;
443};
444
445/**
446 * Adds a setter to this schematype.
447 *
448 * ####Example:
449 *
450 * function capitalize (val) {
451 * if (typeof val !== 'string') val = '';
452 * return val.charAt(0).toUpperCase() + val.substring(1);
453 * }
454 *
455 * // defining within the schema
456 * var s = new Schema({ name: { type: String, set: capitalize }});
457 *
458 * // or with the SchemaType
459 * var s = new Schema({ name: String })
460 * s.path('name').set(capitalize);
461 *
462 * Setters allow you to transform the data before it gets to the raw mongodb
463 * document or query.
464 *
465 * Suppose you are implementing user registration for a website. Users provide
466 * an email and password, which gets saved to mongodb. The email is a string
467 * that you will want to normalize to lower case, in order to avoid one email
468 * having more than one account -- e.g., otherwise, avenue@q.com can be registered for 2 accounts via avenue@q.com and AvEnUe@Q.CoM.
469 *
470 * You can set up email lower case normalization easily via a Mongoose setter.
471 *
472 * function toLower(v) {
473 * return v.toLowerCase();
474 * }
475 *
476 * var UserSchema = new Schema({
477 * email: { type: String, set: toLower }
478 * });
479 *
480 * var User = db.model('User', UserSchema);
481 *
482 * var user = new User({email: 'AVENUE@Q.COM'});
483 * console.log(user.email); // 'avenue@q.com'
484 *
485 * // or
486 * var user = new User();
487 * user.email = 'Avenue@Q.com';
488 * console.log(user.email); // 'avenue@q.com'
489 * User.updateOne({ _id: _id }, { $set: { email: 'AVENUE@Q.COM' } }); // update to 'avenue@q.com'
490 *
491 * As you can see above, setters allow you to transform the data before it
492 * stored in MongoDB, or before executing a query.
493 *
494 * _NOTE: we could have also just used the built-in `lowercase: true` SchemaType option instead of defining our own function._
495 *
496 * new Schema({ email: { type: String, lowercase: true }})
497 *
498 * Setters are also passed a second argument, the schematype on which the setter was defined. This allows for tailored behavior based on options passed in the schema.
499 *
500 * function inspector (val, schematype) {
501 * if (schematype.options.required) {
502 * return schematype.path + ' is required';
503 * } else {
504 * return val;
505 * }
506 * }
507 *
508 * var VirusSchema = new Schema({
509 * name: { type: String, required: true, set: inspector },
510 * taxonomy: { type: String, set: inspector }
511 * })
512 *
513 * var Virus = db.model('Virus', VirusSchema);
514 * var v = new Virus({ name: 'Parvoviridae', taxonomy: 'Parvovirinae' });
515 *
516 * console.log(v.name); // name is required
517 * console.log(v.taxonomy); // Parvovirinae
518 *
519 * You can also use setters to modify other properties on the document. If
520 * you're setting a property `name` on a document, the setter will run with
521 * `this` as the document. Be careful, in mongoose 5 setters will also run
522 * when querying by `name` with `this` as the query.
523 *
524 * ```javascript
525 * const nameSchema = new Schema({ name: String, keywords: [String] });
526 * nameSchema.path('name').set(function(v) {
527 * // Need to check if `this` is a document, because in mongoose 5
528 * // setters will also run on queries, in which case `this` will be a
529 * // mongoose query object.
530 * if (this instanceof Document && v != null) {
531 * this.keywords = v.split(' ');
532 * }
533 * return v;
534 * });
535 * ```
536 *
537 * @param {Function} fn
538 * @return {SchemaType} this
539 * @api public
540 */
541
542SchemaType.prototype.set = function(fn) {
543 if (typeof fn !== 'function') {
544 throw new TypeError('A setter must be a function.');
545 }
546 this.setters.push(fn);
547 return this;
548};
549
550/**
551 * Adds a getter to this schematype.
552 *
553 * ####Example:
554 *
555 * function dob (val) {
556 * if (!val) return val;
557 * return (val.getMonth() + 1) + "/" + val.getDate() + "/" + val.getFullYear();
558 * }
559 *
560 * // defining within the schema
561 * var s = new Schema({ born: { type: Date, get: dob })
562 *
563 * // or by retreiving its SchemaType
564 * var s = new Schema({ born: Date })
565 * s.path('born').get(dob)
566 *
567 * Getters allow you to transform the representation of the data as it travels from the raw mongodb document to the value that you see.
568 *
569 * Suppose you are storing credit card numbers and you want to hide everything except the last 4 digits to the mongoose user. You can do so by defining a getter in the following way:
570 *
571 * function obfuscate (cc) {
572 * return '****-****-****-' + cc.slice(cc.length-4, cc.length);
573 * }
574 *
575 * var AccountSchema = new Schema({
576 * creditCardNumber: { type: String, get: obfuscate }
577 * });
578 *
579 * var Account = db.model('Account', AccountSchema);
580 *
581 * Account.findById(id, function (err, found) {
582 * console.log(found.creditCardNumber); // '****-****-****-1234'
583 * });
584 *
585 * Getters are also passed a second argument, the schematype on which the getter was defined. This allows for tailored behavior based on options passed in the schema.
586 *
587 * function inspector (val, schematype) {
588 * if (schematype.options.required) {
589 * return schematype.path + ' is required';
590 * } else {
591 * return schematype.path + ' is not';
592 * }
593 * }
594 *
595 * var VirusSchema = new Schema({
596 * name: { type: String, required: true, get: inspector },
597 * taxonomy: { type: String, get: inspector }
598 * })
599 *
600 * var Virus = db.model('Virus', VirusSchema);
601 *
602 * Virus.findById(id, function (err, virus) {
603 * console.log(virus.name); // name is required
604 * console.log(virus.taxonomy); // taxonomy is not
605 * })
606 *
607 * @param {Function} fn
608 * @return {SchemaType} this
609 * @api public
610 */
611
612SchemaType.prototype.get = function(fn) {
613 if (typeof fn !== 'function') {
614 throw new TypeError('A getter must be a function.');
615 }
616 this.getters.push(fn);
617 return this;
618};
619
620/**
621 * Adds validator(s) for this document path.
622 *
623 * Validators always receive the value to validate as their first argument and
624 * must return `Boolean`. Returning `false` or throwing an error means
625 * validation failed.
626 *
627 * The error message argument is optional. If not passed, the [default generic error message template](#error_messages_MongooseError-messages) will be used.
628 *
629 * ####Examples:
630 *
631 * // make sure every value is equal to "something"
632 * function validator (val) {
633 * return val == 'something';
634 * }
635 * new Schema({ name: { type: String, validate: validator }});
636 *
637 * // with a custom error message
638 *
639 * var custom = [validator, 'Uh oh, {PATH} does not equal "something".']
640 * new Schema({ name: { type: String, validate: custom }});
641 *
642 * // adding many validators at a time
643 *
644 * var many = [
645 * { validator: validator, msg: 'uh oh' }
646 * , { validator: anotherValidator, msg: 'failed' }
647 * ]
648 * new Schema({ name: { type: String, validate: many }});
649 *
650 * // or utilizing SchemaType methods directly:
651 *
652 * var schema = new Schema({ name: 'string' });
653 * schema.path('name').validate(validator, 'validation of `{PATH}` failed with value `{VALUE}`');
654 *
655 * ####Error message templates:
656 *
657 * From the examples above, you may have noticed that error messages support
658 * basic templating. There are a few other template keywords besides `{PATH}`
659 * and `{VALUE}` too. To find out more, details are available
660 * [here](#error_messages_MongooseError.messages).
661 *
662 * If Mongoose's built-in error message templating isn't enough, Mongoose
663 * supports setting the `message` property to a function.
664 *
665 * schema.path('name').validate({
666 * validator: function() { return v.length > 5; },
667 * // `errors['name']` will be "name must have length 5, got 'foo'"
668 * message: function(props) {
669 * return `${props.path} must have length 5, got '${props.value}'`;
670 * }
671 * });
672 *
673 * To bypass Mongoose's error messages and just copy the error message that
674 * the validator throws, do this:
675 *
676 * schema.path('name').validate({
677 * validator: function() { throw new Error('Oops!'); },
678 * // `errors['name']` will be "Oops!"
679 * message: function(props) { return props.reason.message; }
680 * });
681 *
682 * ####Asynchronous validation:
683 *
684 * Mongoose supports validators that return a promise. A validator that returns
685 * a promise is called an _async validator_. Async validators run in
686 * parallel, and `validate()` will wait until all async validators have settled.
687 *
688 * schema.path('name').validate({
689 * validator: function (value) {
690 * return new Promise(function (resolve, reject) {
691 * resolve(false); // validation failed
692 * });
693 * }
694 * });
695 *
696 * You might use asynchronous validators to retreive other documents from the database to validate against or to meet other I/O bound validation needs.
697 *
698 * Validation occurs `pre('save')` or whenever you manually execute [document#validate](#document_Document-validate).
699 *
700 * If validation fails during `pre('save')` and no callback was passed to receive the error, an `error` event will be emitted on your Models associated db [connection](#connection_Connection), passing the validation error object along.
701 *
702 * var conn = mongoose.createConnection(..);
703 * conn.on('error', handleError);
704 *
705 * var Product = conn.model('Product', yourSchema);
706 * var dvd = new Product(..);
707 * dvd.save(); // emits error on the `conn` above
708 *
709 * If you want to handle these errors at the Model level, add an `error`
710 * listener to your Model as shown below.
711 *
712 * // registering an error listener on the Model lets us handle errors more locally
713 * Product.on('error', handleError);
714 *
715 * @param {RegExp|Function|Object} obj validator function, or hash describing options
716 * @param {Function} [obj.validator] validator function. If the validator function returns `undefined` or a truthy value, validation succeeds. If it returns [falsy](https://masteringjs.io/tutorials/fundamentals/falsy) (except `undefined`) or throws an error, validation fails.
717 * @param {String|Function} [obj.message] optional error message. If function, should return the error message as a string
718 * @param {Boolean} [obj.propsParameter=false] If true, Mongoose will pass the validator properties object (with the `validator` function, `message`, etc.) as the 2nd arg to the validator function. This is disabled by default because many validators [rely on positional args](https://github.com/chriso/validator.js#validators), so turning this on may cause unpredictable behavior in external validators.
719 * @param {String|Function} [errorMsg] optional error message. If function, should return the error message as a string
720 * @param {String} [type] optional validator type
721 * @return {SchemaType} this
722 * @api public
723 */
724
725SchemaType.prototype.validate = function(obj, message, type) {
726 if (typeof obj === 'function' || obj && utils.getFunctionName(obj.constructor) === 'RegExp') {
727 let properties;
728 if (typeof message === 'function') {
729 properties = { validator: obj, message: message };
730 properties.type = type || 'user defined';
731 } else if (message instanceof Object && !type) {
732 properties = utils.clone(message);
733 if (!properties.message) {
734 properties.message = properties.msg;
735 }
736 properties.validator = obj;
737 properties.type = properties.type || 'user defined';
738 } else {
739 if (message == null) {
740 message = MongooseError.messages.general.default;
741 }
742 if (!type) {
743 type = 'user defined';
744 }
745 properties = { message: message, type: type, validator: obj };
746 }
747
748 if (properties.isAsync) {
749 handleIsAsync();
750 }
751
752 this.validators.push(properties);
753 return this;
754 }
755
756 let i;
757 let length;
758 let arg;
759
760 for (i = 0, length = arguments.length; i < length; i++) {
761 arg = arguments[i];
762 if (!utils.isPOJO(arg)) {
763 const msg = 'Invalid validator. Received (' + typeof arg + ') '
764 + arg
765 + '. See http://mongoosejs.com/docs/api.html#schematype_SchemaType-validate';
766
767 throw new Error(msg);
768 }
769 this.validate(arg.validator, arg);
770 }
771
772 return this;
773};
774
775/*!
776 * ignore
777 */
778
779const handleIsAsync = util.deprecate(function handleIsAsync() {},
780 'Mongoose: the `isAsync` option for custom validators is deprecated. Make ' +
781 'your async validators return a promise instead: ' +
782 'https://mongoosejs.com/docs/validation.html#async-custom-validators');
783
784/**
785 * Adds a required validator to this SchemaType. The validator gets added
786 * to the front of this SchemaType's validators array using `unshift()`.
787 *
788 * ####Example:
789 *
790 * var s = new Schema({ born: { type: Date, required: true })
791 *
792 * // or with custom error message
793 *
794 * var s = new Schema({ born: { type: Date, required: '{PATH} is required!' })
795 *
796 * // or with a function
797 *
798 * var s = new Schema({
799 * userId: ObjectId,
800 * username: {
801 * type: String,
802 * required: function() { return this.userId != null; }
803 * }
804 * })
805 *
806 * // or with a function and a custom message
807 * var s = new Schema({
808 * userId: ObjectId,
809 * username: {
810 * type: String,
811 * required: [
812 * function() { return this.userId != null; },
813 * 'username is required if id is specified'
814 * ]
815 * }
816 * })
817 *
818 * // or through the path API
819 *
820 * s.path('name').required(true);
821 *
822 * // with custom error messaging
823 *
824 * s.path('name').required(true, 'grrr :( ');
825 *
826 * // or make a path conditionally required based on a function
827 * var isOver18 = function() { return this.age >= 18; };
828 * s.path('voterRegistrationId').required(isOver18);
829 *
830 * The required validator uses the SchemaType's `checkRequired` function to
831 * determine whether a given value satisfies the required validator. By default,
832 * a value satisfies the required validator if `val != null` (that is, if
833 * the value is not null nor undefined). However, most built-in mongoose schema
834 * types override the default `checkRequired` function:
835 *
836 * @param {Boolean|Function|Object} required enable/disable the validator, or function that returns required boolean, or options object
837 * @param {Boolean|Function} [options.isRequired] enable/disable the validator, or function that returns required boolean
838 * @param {Function} [options.ErrorConstructor] custom error constructor. The constructor receives 1 parameter, an object containing the validator properties.
839 * @param {String} [message] optional custom error message
840 * @return {SchemaType} this
841 * @see Customized Error Messages #error_messages_MongooseError-messages
842 * @see SchemaArray#checkRequired #schema_array_SchemaArray.checkRequired
843 * @see SchemaBoolean#checkRequired #schema_boolean_SchemaBoolean-checkRequired
844 * @see SchemaBuffer#checkRequired #schema_buffer_SchemaBuffer.schemaName
845 * @see SchemaNumber#checkRequired #schema_number_SchemaNumber-min
846 * @see SchemaObjectId#checkRequired #schema_objectid_ObjectId-auto
847 * @see SchemaString#checkRequired #schema_string_SchemaString-checkRequired
848 * @api public
849 */
850
851SchemaType.prototype.required = function(required, message) {
852 let customOptions = {};
853
854 if (arguments.length > 0 && required == null) {
855 this.validators = this.validators.filter(function(v) {
856 return v.validator !== this.requiredValidator;
857 }, this);
858
859 this.isRequired = false;
860 delete this.originalRequiredValue;
861 return this;
862 }
863
864 if (typeof required === 'object') {
865 customOptions = required;
866 message = customOptions.message || message;
867 required = required.isRequired;
868 }
869
870 if (required === false) {
871 this.validators = this.validators.filter(function(v) {
872 return v.validator !== this.requiredValidator;
873 }, this);
874
875 this.isRequired = false;
876 delete this.originalRequiredValue;
877 return this;
878 }
879
880 const _this = this;
881 this.isRequired = true;
882
883 this.requiredValidator = function(v) {
884 const cachedRequired = get(this, '$__.cachedRequired');
885
886 // no validation when this path wasn't selected in the query.
887 if (cachedRequired != null && !this.isSelected(_this.path) && !this.isModified(_this.path)) {
888 return true;
889 }
890
891 // `$cachedRequired` gets set in `_evaluateRequiredFunctions()` so we
892 // don't call required functions multiple times in one validate call
893 // See gh-6801
894 if (cachedRequired != null && _this.path in cachedRequired) {
895 const res = cachedRequired[_this.path] ?
896 _this.checkRequired(v, this) :
897 true;
898 delete cachedRequired[_this.path];
899 return res;
900 } else if (typeof required === 'function') {
901 return required.apply(this) ? _this.checkRequired(v, this) : true;
902 }
903
904 return _this.checkRequired(v, this);
905 };
906 this.originalRequiredValue = required;
907
908 if (typeof required === 'string') {
909 message = required;
910 required = undefined;
911 }
912
913 const msg = message || MongooseError.messages.general.required;
914 this.validators.unshift(Object.assign({}, customOptions, {
915 validator: this.requiredValidator,
916 message: msg,
917 type: 'required'
918 }));
919
920 return this;
921};
922
923/**
924 * Set the model that this path refers to. This is the option that [populate](https://mongoosejs.com/docs/populate.html)
925 * looks at to determine the foreign collection it should query.
926 *
927 * ####Example:
928 * const userSchema = new Schema({ name: String });
929 * const User = mongoose.model('User', userSchema);
930 *
931 * const postSchema = new Schema({ user: mongoose.ObjectId });
932 * postSchema.path('user').ref('User'); // By model name
933 * postSchema.path('user').ref(User); // Can pass the model as well
934 *
935 * // Or you can just declare the `ref` inline in your schema
936 * const postSchema2 = new Schema({
937 * user: { type: mongoose.ObjectId, ref: User }
938 * });
939 *
940 * @param {String|Model|Function} ref either a model name, a [Model](https://mongoosejs.com/docs/models.html), or a function that returns a model name or model.
941 * @return {SchemaType} this
942 * @api public
943 */
944
945SchemaType.prototype.ref = function(ref) {
946 this.options.ref = ref;
947 return this;
948};
949
950/**
951 * Gets the default value
952 *
953 * @param {Object} scope the scope which callback are executed
954 * @param {Boolean} init
955 * @api private
956 */
957
958SchemaType.prototype.getDefault = function(scope, init) {
959 let ret = typeof this.defaultValue === 'function'
960 ? this.defaultValue.call(scope)
961 : this.defaultValue;
962
963 if (ret !== null && ret !== undefined) {
964 if (typeof ret === 'object' && (!this.options || !this.options.shared)) {
965 ret = utils.clone(ret);
966 }
967
968 const casted = this.applySetters(ret, scope, init);
969 if (casted && casted.$isSingleNested) {
970 casted.$parent = scope;
971 }
972 return casted;
973 }
974 return ret;
975};
976
977/*!
978 * Applies setters without casting
979 *
980 * @api private
981 */
982
983SchemaType.prototype._applySetters = function(value, scope, init, priorVal) {
984 let v = value;
985 const setters = this.setters;
986 const caster = this.caster;
987
988 for (const setter of utils.clone(setters).reverse()) {
989 v = setter.call(scope, v, this);
990 }
991
992 if (Array.isArray(v) && caster && caster.setters) {
993 const newVal = [];
994
995 for (let i = 0; i < v.length; ++i) {
996 const value = v[i];
997 try {
998 newVal.push(caster.applySetters(value, scope, init, priorVal));
999 } catch (err) {
1000 if (err instanceof MongooseError.CastError) {
1001 err.$originalErrorPath = err.path;
1002 err.path = err.path + '.' + i;
1003 }
1004 throw err;
1005 }
1006 }
1007 v = newVal;
1008 }
1009
1010
1011 return v;
1012};
1013
1014/**
1015 * Applies setters
1016 *
1017 * @param {Object} value
1018 * @param {Object} scope
1019 * @param {Boolean} init
1020 * @api private
1021 */
1022
1023SchemaType.prototype.applySetters = function(value, scope, init, priorVal, options) {
1024 let v = this._applySetters(value, scope, init, priorVal, options);
1025
1026 if (v == null) {
1027 return v;
1028 }
1029
1030 // do not cast until all setters are applied #665
1031 v = this.cast(v, scope, init, priorVal, options);
1032
1033 return v;
1034};
1035
1036/**
1037 * Applies getters to a value
1038 *
1039 * @param {Object} value
1040 * @param {Object} scope
1041 * @api private
1042 */
1043
1044SchemaType.prototype.applyGetters = function(value, scope) {
1045 let v = value;
1046 const getters = this.getters;
1047 const len = getters.length;
1048
1049 if (len === 0) {
1050 return v;
1051 }
1052
1053 for (let i = 0; i < len; ++i) {
1054 v = getters[i].call(scope, v, this);
1055 }
1056
1057 return v;
1058};
1059
1060/**
1061 * Sets default `select()` behavior for this path.
1062 *
1063 * Set to `true` if this path should always be included in the results, `false` if it should be excluded by default. This setting can be overridden at the query level.
1064 *
1065 * ####Example:
1066 *
1067 * T = db.model('T', new Schema({ x: { type: String, select: true }}));
1068 * T.find(..); // field x will always be selected ..
1069 * // .. unless overridden;
1070 * T.find().select('-x').exec(callback);
1071 *
1072 * @param {Boolean} val
1073 * @return {SchemaType} this
1074 * @api public
1075 */
1076
1077SchemaType.prototype.select = function select(val) {
1078 this.selected = !!val;
1079 return this;
1080};
1081
1082/**
1083 * Performs a validation of `value` using the validators declared for this SchemaType.
1084 *
1085 * @param {any} value
1086 * @param {Function} callback
1087 * @param {Object} scope
1088 * @api private
1089 */
1090
1091SchemaType.prototype.doValidate = function(value, fn, scope, options) {
1092 let err = false;
1093 const path = this.path;
1094
1095 // Avoid non-object `validators`
1096 const validators = this.validators.
1097 filter(v => v != null && typeof v === 'object');
1098
1099 let count = validators.length;
1100
1101 if (!count) {
1102 return fn(null);
1103 }
1104
1105 const _this = this;
1106 validators.forEach(function(v) {
1107 if (err) {
1108 return;
1109 }
1110
1111 const validator = v.validator;
1112 let ok;
1113
1114 const validatorProperties = utils.clone(v);
1115 validatorProperties.path = options && options.path ? options.path : path;
1116 validatorProperties.value = value;
1117
1118 if (validator instanceof RegExp) {
1119 validate(validator.test(value), validatorProperties);
1120 return;
1121 }
1122
1123 if (typeof validator !== 'function') {
1124 return;
1125 }
1126
1127 if (value === undefined && validator !== _this.requiredValidator) {
1128 validate(true, validatorProperties);
1129 return;
1130 }
1131
1132 if (validatorProperties.isAsync) {
1133 asyncValidate(validator, scope, value, validatorProperties, validate);
1134 return;
1135 }
1136
1137 try {
1138 if (validatorProperties.propsParameter) {
1139 ok = validator.call(scope, value, validatorProperties);
1140 } else {
1141 ok = validator.call(scope, value);
1142 }
1143 } catch (error) {
1144 ok = false;
1145 validatorProperties.reason = error;
1146 if (error.message) {
1147 validatorProperties.message = error.message;
1148 }
1149 }
1150
1151 if (ok != null && typeof ok.then === 'function') {
1152 ok.then(
1153 function(ok) { validate(ok, validatorProperties); },
1154 function(error) {
1155 validatorProperties.reason = error;
1156 validatorProperties.message = error.message;
1157 ok = false;
1158 validate(ok, validatorProperties);
1159 });
1160 } else {
1161 validate(ok, validatorProperties);
1162 }
1163
1164 });
1165
1166 function validate(ok, validatorProperties) {
1167 if (err) {
1168 return;
1169 }
1170 if (ok === undefined || ok) {
1171 if (--count <= 0) {
1172 immediate(function() {
1173 fn(null);
1174 });
1175 }
1176 } else {
1177 const ErrorConstructor = validatorProperties.ErrorConstructor || ValidatorError;
1178 err = new ErrorConstructor(validatorProperties);
1179 err[validatorErrorSymbol] = true;
1180 immediate(function() {
1181 fn(err);
1182 });
1183 }
1184 }
1185};
1186
1187/*!
1188 * Handle async validators
1189 */
1190
1191function asyncValidate(validator, scope, value, props, cb) {
1192 let called = false;
1193 const returnVal = validator.call(scope, value, function(ok, customMsg) {
1194 if (called) {
1195 return;
1196 }
1197 called = true;
1198 if (customMsg) {
1199 props.message = customMsg;
1200 }
1201 cb(ok, props);
1202 });
1203 if (typeof returnVal === 'boolean') {
1204 called = true;
1205 cb(returnVal, props);
1206 } else if (returnVal && typeof returnVal.then === 'function') {
1207 // Promise
1208 returnVal.then(
1209 function(ok) {
1210 if (called) {
1211 return;
1212 }
1213 called = true;
1214 cb(ok, props);
1215 },
1216 function(error) {
1217 if (called) {
1218 return;
1219 }
1220 called = true;
1221
1222 props.reason = error;
1223 props.message = error.message;
1224 cb(false, props);
1225 });
1226 }
1227}
1228
1229/**
1230 * Performs a validation of `value` using the validators declared for this SchemaType.
1231 *
1232 * ####Note:
1233 *
1234 * This method ignores the asynchronous validators.
1235 *
1236 * @param {any} value
1237 * @param {Object} scope
1238 * @return {MongooseError|undefined}
1239 * @api private
1240 */
1241
1242SchemaType.prototype.doValidateSync = function(value, scope, options) {
1243 const path = this.path;
1244 const count = this.validators.length;
1245
1246 if (!count) {
1247 return null;
1248 }
1249
1250 let validators = this.validators;
1251 if (value === void 0) {
1252 if (this.validators.length > 0 && this.validators[0].type === 'required') {
1253 validators = [this.validators[0]];
1254 } else {
1255 return null;
1256 }
1257 }
1258
1259 let err = null;
1260 validators.forEach(function(v) {
1261 if (err) {
1262 return;
1263 }
1264
1265 if (v == null || typeof v !== 'object') {
1266 return;
1267 }
1268
1269 const validator = v.validator;
1270 const validatorProperties = utils.clone(v);
1271 validatorProperties.path = options && options.path ? options.path : path;
1272 validatorProperties.value = value;
1273 let ok;
1274
1275 // Skip any explicit async validators. Validators that return a promise
1276 // will still run, but won't trigger any errors.
1277 if (validator.isAsync) {
1278 return;
1279 }
1280
1281 if (validator instanceof RegExp) {
1282 validate(validator.test(value), validatorProperties);
1283 return;
1284 }
1285
1286 if (typeof validator !== 'function') {
1287 return;
1288 }
1289
1290 try {
1291 if (validatorProperties.propsParameter) {
1292 ok = validator.call(scope, value, validatorProperties);
1293 } else {
1294 ok = validator.call(scope, value);
1295 }
1296 } catch (error) {
1297 ok = false;
1298 validatorProperties.reason = error;
1299 }
1300
1301 // Skip any validators that return a promise, we can't handle those
1302 // synchronously
1303 if (ok != null && typeof ok.then === 'function') {
1304 return;
1305 }
1306 validate(ok, validatorProperties);
1307 });
1308
1309 return err;
1310
1311 function validate(ok, validatorProperties) {
1312 if (err) {
1313 return;
1314 }
1315 if (ok !== undefined && !ok) {
1316 const ErrorConstructor = validatorProperties.ErrorConstructor || ValidatorError;
1317 err = new ErrorConstructor(validatorProperties);
1318 err[validatorErrorSymbol] = true;
1319 }
1320 }
1321};
1322
1323/**
1324 * Determines if value is a valid Reference.
1325 *
1326 * @param {SchemaType} self
1327 * @param {Object} value
1328 * @param {Document} doc
1329 * @param {Boolean} init
1330 * @return {Boolean}
1331 * @api private
1332 */
1333
1334SchemaType._isRef = function(self, value, doc, init) {
1335 // fast path
1336 let ref = init && self.options && (self.options.ref || self.options.refPath);
1337
1338 if (!ref && doc && doc.$__ != null) {
1339 // checks for
1340 // - this populated with adhoc model and no ref was set in schema OR
1341 // - setting / pushing values after population
1342 const path = doc.$__fullPath(self.path);
1343 const owner = doc.ownerDocument ? doc.ownerDocument() : doc;
1344 ref = owner.populated(path) || doc.populated(self.path);
1345 }
1346
1347 if (ref) {
1348 if (value == null) {
1349 return true;
1350 }
1351 if (!Buffer.isBuffer(value) && // buffers are objects too
1352 value._bsontype !== 'Binary' // raw binary value from the db
1353 && utils.isObject(value) // might have deselected _id in population query
1354 ) {
1355 return true;
1356 }
1357 }
1358
1359 return false;
1360};
1361
1362/*!
1363 * ignore
1364 */
1365
1366function handleSingle(val) {
1367 return this.castForQuery(val);
1368}
1369
1370/*!
1371 * ignore
1372 */
1373
1374function handleArray(val) {
1375 const _this = this;
1376 if (!Array.isArray(val)) {
1377 return [this.castForQuery(val)];
1378 }
1379 return val.map(function(m) {
1380 return _this.castForQuery(m);
1381 });
1382}
1383
1384/*!
1385 * Just like handleArray, except also allows `[]` because surprisingly
1386 * `$in: [1, []]` works fine
1387 */
1388
1389function handle$in(val) {
1390 const _this = this;
1391 if (!Array.isArray(val)) {
1392 return [this.castForQuery(val)];
1393 }
1394 return val.map(function(m) {
1395 if (Array.isArray(m) && m.length === 0) {
1396 return m;
1397 }
1398 return _this.castForQuery(m);
1399 });
1400}
1401
1402/*!
1403 * ignore
1404 */
1405
1406SchemaType.prototype.$conditionalHandlers = {
1407 $all: handleArray,
1408 $eq: handleSingle,
1409 $in: handle$in,
1410 $ne: handleSingle,
1411 $nin: handle$in,
1412 $exists: $exists,
1413 $type: $type
1414};
1415
1416/*!
1417 * Wraps `castForQuery` to handle context
1418 */
1419
1420SchemaType.prototype.castForQueryWrapper = function(params) {
1421 this.$$context = params.context;
1422 if ('$conditional' in params) {
1423 return this.castForQuery(params.$conditional, params.val);
1424 }
1425 if (params.$skipQueryCastForUpdate || params.$applySetters) {
1426 return this._castForQuery(params.val);
1427 }
1428 return this.castForQuery(params.val);
1429};
1430
1431/**
1432 * Cast the given value with the given optional query operator.
1433 *
1434 * @param {String} [$conditional] query operator, like `$eq` or `$in`
1435 * @param {any} val
1436 * @api private
1437 */
1438
1439SchemaType.prototype.castForQuery = function($conditional, val) {
1440 let handler;
1441 if (arguments.length === 2) {
1442 handler = this.$conditionalHandlers[$conditional];
1443 if (!handler) {
1444 throw new Error('Can\'t use ' + $conditional);
1445 }
1446 return handler.call(this, val);
1447 }
1448 val = $conditional;
1449 return this._castForQuery(val);
1450};
1451
1452/*!
1453 * Internal switch for runSetters
1454 *
1455 * @api private
1456 */
1457
1458SchemaType.prototype._castForQuery = function(val) {
1459 return this.applySetters(val, this.$$context);
1460};
1461
1462/**
1463 * Override the function the required validator uses to check whether a value
1464 * passes the `required` check. Override this on the individual SchemaType.
1465 *
1466 * ####Example:
1467 *
1468 * // Use this to allow empty strings to pass the `required` validator
1469 * mongoose.Schema.Types.String.checkRequired(v => typeof v === 'string');
1470 *
1471 * @param {Function} fn
1472 * @return {Function}
1473 * @static
1474 * @receiver SchemaType
1475 * @function checkRequired
1476 * @api public
1477 */
1478
1479SchemaType.checkRequired = function(fn) {
1480 if (arguments.length > 0) {
1481 this._checkRequired = fn;
1482 }
1483
1484 return this._checkRequired;
1485};
1486
1487/**
1488 * Default check for if this path satisfies the `required` validator.
1489 *
1490 * @param {any} val
1491 * @api private
1492 */
1493
1494SchemaType.prototype.checkRequired = function(val) {
1495 return val != null;
1496};
1497
1498/*!
1499 * ignore
1500 */
1501
1502SchemaType.prototype.clone = function() {
1503 const options = Object.assign({}, this.options);
1504 const schematype = new this.constructor(this.path, options, this.instance);
1505 schematype.validators = this.validators.slice();
1506 if (this.requiredValidator !== undefined) schematype.requiredValidator = this.requiredValidator;
1507 if (this.defaultValue !== undefined) schematype.defaultValue = this.defaultValue;
1508 if (this.$immutable !== undefined && this.options.immutable === undefined) {
1509 schematype.$immutable = this.$immutable;
1510
1511 handleImmutable(schematype);
1512 }
1513 if (this._index !== undefined) schematype._index = this._index;
1514 if (this.selected !== undefined) schematype.selected = this.selected;
1515 if (this.isRequired !== undefined) schematype.isRequired = this.isRequired;
1516 if (this.originalRequiredValue !== undefined) schematype.originalRequiredValue = this.originalRequiredValue;
1517 schematype.getters = this.getters.slice();
1518 schematype.setters = this.setters.slice();
1519 return schematype;
1520};
1521
1522/*!
1523 * Module exports.
1524 */
1525
1526module.exports = exports = SchemaType;
1527
1528exports.CastError = CastError;
1529
1530exports.ValidatorError = ValidatorError;