UNPKG

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