UNPKG

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