UNPKG

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