UNPKG

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