UNPKG

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