arnaucube
arnaucube/thoughtsPublic Self-hosted repository
thoughts/node_modules/mongoose/lib/schematype.js
node_modules/mongoose/lib/schematype.jsRaw
  1/*!
  2 * Module dependencies.
  3 */
  4
  5var utils = require('./utils');
  6var MongooseError = require('./error');
  7var CastError = MongooseError.CastError;
  8var ValidatorError = MongooseError.ValidatorError;
  9
 10/**
 11 * SchemaType constructor
 12 *
 13 * @param {String} path
 14 * @param {Object} [options]
 15 * @param {String} [instance]
 16 * @api public
 17 */
 18
 19function SchemaType(path, options, instance) {
 20  this.path = path;
 21  this.instance = instance;
 22  this.validators = [];
 23  this.setters = [];
 24  this.getters = [];
 25  this.options = options;
 26  this._index = null;
 27  this.selected;
 28
 29  for (var i in options) {
 30    if (this[i] && typeof this[i] === 'function') {
 31      // { unique: true, index: true }
 32      if (i === 'index' && this._index) {
 33        continue;
 34      }
 35
 36      var opts = Array.isArray(options[i])
 37          ? options[i]
 38          : [options[i]];
 39
 40      this[i].apply(this, opts);
 41    }
 42  }
 43}
 44
 45/**
 46 * Sets a default value for this SchemaType.
 47 *
 48 * ####Example:
 49 *
 50 *     var schema = new Schema({ n: { type: Number, default: 10 })
 51 *     var M = db.model('M', schema)
 52 *     var m = new M;
 53 *     console.log(m.n) // 10
 54 *
 55 * 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.
 56 *
 57 * ####Example:
 58 *
 59 *     // values are cast:
 60 *     var schema = new Schema({ aNumber: { type: Number, default: 4.815162342 }})
 61 *     var M = db.model('M', schema)
 62 *     var m = new M;
 63 *     console.log(m.aNumber) // 4.815162342
 64 *
 65 *     // default unique objects for Mixed types:
 66 *     var schema = new Schema({ mixed: Schema.Types.Mixed });
 67 *     schema.path('mixed').default(function () {
 68 *       return {};
 69 *     });
 70 *
 71 *     // if we don't use a function to return object literals for Mixed defaults,
 72 *     // each document will receive a reference to the same object literal creating
 73 *     // a "shared" object instance:
 74 *     var schema = new Schema({ mixed: Schema.Types.Mixed });
 75 *     schema.path('mixed').default({});
 76 *     var M = db.model('M', schema);
 77 *     var m1 = new M;
 78 *     m1.mixed.added = 1;
 79 *     console.log(m1.mixed); // { added: 1 }
 80 *     var m2 = new M;
 81 *     console.log(m2.mixed); // { added: 1 }
 82 *
 83 * @param {Function|any} val the default value
 84 * @return {defaultValue}
 85 * @api public
 86 */
 87
 88SchemaType.prototype.default = function(val) {
 89  if (arguments.length === 1) {
 90    if (val === void 0) {
 91      this.defaultValue = void 0;
 92      return void 0;
 93    }
 94    this.defaultValue = val;
 95    return this.defaultValue;
 96  } else if (arguments.length > 1) {
 97    this.defaultValue = utils.args(arguments);
 98  }
 99  return this.defaultValue;
100};
101
102/**
103 * Declares the index options for this schematype.
104 *
105 * ####Example:
106 *
107 *     var s = new Schema({ name: { type: String, index: true })
108 *     var s = new Schema({ loc: { type: [Number], index: 'hashed' })
109 *     var s = new Schema({ loc: { type: [Number], index: '2d', sparse: true })
110 *     var s = new Schema({ loc: { type: [Number], index: { type: '2dsphere', sparse: true }})
111 *     var s = new Schema({ date: { type: Date, index: { unique: true, expires: '1d' }})
112 *     Schema.path('my.path').index(true);
113 *     Schema.path('my.date').index({ expires: 60 });
114 *     Schema.path('my.path').index({ unique: true, sparse: true });
115 *
116 * ####NOTE:
117 *
118 * _Indexes are created in the background by default. Specify `background: false` to override._
119 *
120 * [Direction doesn't matter for single key indexes](http://www.mongodb.org/display/DOCS/Indexes#Indexes-CompoundKeysIndexes)
121 *
122 * @param {Object|Boolean|String} options
123 * @return {SchemaType} this
124 * @api public
125 */
126
127SchemaType.prototype.index = function(options) {
128  this._index = options;
129  utils.expires(this._index);
130  return this;
131};
132
133/**
134 * Declares an unique index.
135 *
136 * ####Example:
137 *
138 *     var s = new Schema({ name: { type: String, unique: true }});
139 *     Schema.path('name').index({ unique: true });
140 *
141 * _NOTE: violating the constraint returns an `E11000` error from MongoDB when saving, not a Mongoose validation error._
142 *
143 * @param {Boolean} bool
144 * @return {SchemaType} this
145 * @api public
146 */
147
148SchemaType.prototype.unique = function(bool) {
149  if (this._index === false) {
150    if (!bool) {
151      return;
152    }
153    throw new Error('Path "' + this.path + '" may not have `index` set to ' +
154      'false and `unique` set to true');
155  }
156  if (this._index == null || this._index === true) {
157    this._index = {};
158  } else if (typeof this._index === 'string') {
159    this._index = {type: this._index};
160  }
161
162  this._index.unique = bool;
163  return this;
164};
165
166/**
167 * Declares a full text index.
168 *
169 * ###Example:
170 *
171 *      var s = new Schema({name : {type: String, text : true })
172 *      Schema.path('name').index({text : true});
173 * @param {Boolean} bool
174 * @return {SchemaType} this
175 * @api public
176 */
177
178SchemaType.prototype.text = function(bool) {
179  if (this._index === null || this._index === undefined ||
180    typeof this._index === 'boolean') {
181    this._index = {};
182  } else if (typeof this._index === 'string') {
183    this._index = {type: this._index};
184  }
185
186  this._index.text = bool;
187  return this;
188};
189
190/**
191 * Declares a sparse index.
192 *
193 * ####Example:
194 *
195 *     var s = new Schema({ name: { type: String, sparse: true })
196 *     Schema.path('name').index({ sparse: true });
197 *
198 * @param {Boolean} bool
199 * @return {SchemaType} this
200 * @api public
201 */
202
203SchemaType.prototype.sparse = function(bool) {
204  if (this._index === null || this._index === undefined ||
205    typeof this._index === 'boolean') {
206    this._index = {};
207  } else if (typeof this._index === 'string') {
208    this._index = {type: this._index};
209  }
210
211  this._index.sparse = bool;
212  return this;
213};
214
215/**
216 * Adds a setter to this schematype.
217 *
218 * ####Example:
219 *
220 *     function capitalize (val) {
221 *       if (typeof val !== 'string') val = '';
222 *       return val.charAt(0).toUpperCase() + val.substring(1);
223 *     }
224 *
225 *     // defining within the schema
226 *     var s = new Schema({ name: { type: String, set: capitalize }})
227 *
228 *     // or by retreiving its SchemaType
229 *     var s = new Schema({ name: String })
230 *     s.path('name').set(capitalize)
231 *
232 * Setters allow you to transform the data before it gets to the raw mongodb document and is set as a value on an actual key.
233 *
234 * Suppose you are implementing user registration for a website. Users provide an email and password, which gets saved to mongodb. The email is a string that you will want to normalize to lower case, in order to avoid one email 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.
235 *
236 * You can set up email lower case normalization easily via a Mongoose setter.
237 *
238 *     function toLower (v) {
239 *       return v.toLowerCase();
240 *     }
241 *
242 *     var UserSchema = new Schema({
243 *       email: { type: String, set: toLower }
244 *     })
245 *
246 *     var User = db.model('User', UserSchema)
247 *
248 *     var user = new User({email: 'AVENUE@Q.COM'})
249 *     console.log(user.email); // 'avenue@q.com'
250 *
251 *     // or
252 *     var user = new User
253 *     user.email = 'Avenue@Q.com'
254 *     console.log(user.email) // 'avenue@q.com'
255 *
256 * As you can see above, setters allow you to transform the data before it gets to the raw mongodb document and is set as a value on an actual key.
257 *
258 * _NOTE: we could have also just used the built-in `lowercase: true` SchemaType option instead of defining our own function._
259 *
260 *     new Schema({ email: { type: String, lowercase: true }})
261 *
262 * 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.
263 *
264 *     function inspector (val, schematype) {
265 *       if (schematype.options.required) {
266 *         return schematype.path + ' is required';
267 *       } else {
268 *         return val;
269 *       }
270 *     }
271 *
272 *     var VirusSchema = new Schema({
273 *       name: { type: String, required: true, set: inspector },
274 *       taxonomy: { type: String, set: inspector }
275 *     })
276 *
277 *     var Virus = db.model('Virus', VirusSchema);
278 *     var v = new Virus({ name: 'Parvoviridae', taxonomy: 'Parvovirinae' });
279 *
280 *     console.log(v.name);     // name is required
281 *     console.log(v.taxonomy); // Parvovirinae
282 *
283 * @param {Function} fn
284 * @return {SchemaType} this
285 * @api public
286 */
287
288SchemaType.prototype.set = function(fn) {
289  if (typeof fn !== 'function') {
290    throw new TypeError('A setter must be a function.');
291  }
292  this.setters.push(fn);
293  return this;
294};
295
296/**
297 * Adds a getter to this schematype.
298 *
299 * ####Example:
300 *
301 *     function dob (val) {
302 *       if (!val) return val;
303 *       return (val.getMonth() + 1) + "/" + val.getDate() + "/" + val.getFullYear();
304 *     }
305 *
306 *     // defining within the schema
307 *     var s = new Schema({ born: { type: Date, get: dob })
308 *
309 *     // or by retreiving its SchemaType
310 *     var s = new Schema({ born: Date })
311 *     s.path('born').get(dob)
312 *
313 * Getters allow you to transform the representation of the data as it travels from the raw mongodb document to the value that you see.
314 *
315 * 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:
316 *
317 *     function obfuscate (cc) {
318 *       return '****-****-****-' + cc.slice(cc.length-4, cc.length);
319 *     }
320 *
321 *     var AccountSchema = new Schema({
322 *       creditCardNumber: { type: String, get: obfuscate }
323 *     });
324 *
325 *     var Account = db.model('Account', AccountSchema);
326 *
327 *     Account.findById(id, function (err, found) {
328 *       console.log(found.creditCardNumber); // '****-****-****-1234'
329 *     });
330 *
331 * 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.
332 *
333 *     function inspector (val, schematype) {
334 *       if (schematype.options.required) {
335 *         return schematype.path + ' is required';
336 *       } else {
337 *         return schematype.path + ' is not';
338 *       }
339 *     }
340 *
341 *     var VirusSchema = new Schema({
342 *       name: { type: String, required: true, get: inspector },
343 *       taxonomy: { type: String, get: inspector }
344 *     })
345 *
346 *     var Virus = db.model('Virus', VirusSchema);
347 *
348 *     Virus.findById(id, function (err, virus) {
349 *       console.log(virus.name);     // name is required
350 *       console.log(virus.taxonomy); // taxonomy is not
351 *     })
352 *
353 * @param {Function} fn
354 * @return {SchemaType} this
355 * @api public
356 */
357
358SchemaType.prototype.get = function(fn) {
359  if (typeof fn !== 'function') {
360    throw new TypeError('A getter must be a function.');
361  }
362  this.getters.push(fn);
363  return this;
364};
365
366/**
367 * Adds validator(s) for this document path.
368 *
369 * Validators always receive the value to validate as their first argument and must return `Boolean`. Returning `false` means validation failed.
370 *
371 * The error message argument is optional. If not passed, the [default generic error message template](#error_messages_MongooseError-messages) will be used.
372 *
373 * ####Examples:
374 *
375 *     // make sure every value is equal to "something"
376 *     function validator (val) {
377 *       return val == 'something';
378 *     }
379 *     new Schema({ name: { type: String, validate: validator }});
380 *
381 *     // with a custom error message
382 *
383 *     var custom = [validator, 'Uh oh, {PATH} does not equal "something".']
384 *     new Schema({ name: { type: String, validate: custom }});
385 *
386 *     // adding many validators at a time
387 *
388 *     var many = [
389 *         { validator: validator, msg: 'uh oh' }
390 *       , { validator: anotherValidator, msg: 'failed' }
391 *     ]
392 *     new Schema({ name: { type: String, validate: many }});
393 *
394 *     // or utilizing SchemaType methods directly:
395 *
396 *     var schema = new Schema({ name: 'string' });
397 *     schema.path('name').validate(validator, 'validation of `{PATH}` failed with value `{VALUE}`');
398 *
399 * ####Error message templates:
400 *
401 * From the examples above, you may have noticed that error messages support basic templating. There are a few other template keywords besides `{PATH}` and `{VALUE}` too. To find out more, details are available [here](#error_messages_MongooseError-messages)
402 *
403 * ####Asynchronous validation:
404 *
405 * Passing a validator function that receives two arguments tells mongoose that the validator is an asynchronous validator. The first argument passed to the validator function is the value being validated. The second argument is a callback function that must called when you finish validating the value and passed either `true` or `false` to communicate either success or failure respectively.
406 *
407 *     schema.path('name').validate(function (value, respond) {
408 *       doStuff(value, function () {
409 *         ...
410 *         respond(false); // validation failed
411 *       })
412 *     }, '{PATH} failed validation.');
413 *
414 *     // or with dynamic message
415 *
416 *     schema.path('name').validate(function (value, respond) {
417 *       doStuff(value, function () {
418 *         ...
419 *         respond(false, 'this message gets to the validation error');
420 *       });
421 *     }, 'this message does not matter');
422 *
423 * You might use asynchronous validators to retreive other documents from the database to validate against or to meet other I/O bound validation needs.
424 *
425 * Validation occurs `pre('save')` or whenever you manually execute [document#validate](#document_Document-validate).
426 *
427 * 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.
428 *
429 *     var conn = mongoose.createConnection(..);
430 *     conn.on('error', handleError);
431 *
432 *     var Product = conn.model('Product', yourSchema);
433 *     var dvd = new Product(..);
434 *     dvd.save(); // emits error on the `conn` above
435 *
436 * If you desire handling these errors at the Model level, attach an `error` listener to your Model and the event will instead be emitted there.
437 *
438 *     // registering an error listener on the Model lets us handle errors more locally
439 *     Product.on('error', handleError);
440 *
441 * @param {RegExp|Function|Object} obj validator
442 * @param {String} [errorMsg] optional error message
443 * @param {String} [type] optional validator type
444 * @return {SchemaType} this
445 * @api public
446 */
447
448SchemaType.prototype.validate = function(obj, message, type) {
449  if (typeof obj === 'function' || obj && utils.getFunctionName(obj.constructor) === 'RegExp') {
450    var properties;
451    if (message instanceof Object && !type) {
452      properties = utils.clone(message);
453      if (!properties.message) {
454        properties.message = properties.msg;
455      }
456      properties.validator = obj;
457      properties.type = properties.type || 'user defined';
458    } else {
459      if (!message) {
460        message = MongooseError.messages.general.default;
461      }
462      if (!type) {
463        type = 'user defined';
464      }
465      properties = {message: message, type: type, validator: obj};
466    }
467    this.validators.push(properties);
468    return this;
469  }
470
471  var i,
472      length,
473      arg;
474
475  for (i = 0, length = arguments.length; i < length; i++) {
476    arg = arguments[i];
477    if (!(arg && utils.getFunctionName(arg.constructor) === 'Object')) {
478      var msg = 'Invalid validator. Received (' + typeof arg + ') '
479          + arg
480          + '. See http://mongoosejs.com/docs/api.html#schematype_SchemaType-validate';
481
482      throw new Error(msg);
483    }
484    this.validate(arg.validator, arg);
485  }
486
487  return this;
488};
489
490/**
491 * Adds a required validator to this SchemaType. The validator gets added
492 * to the front of this SchemaType's validators array using `unshift()`.
493 *
494 * ####Example:
495 *
496 *     var s = new Schema({ born: { type: Date, required: true })
497 *
498 *     // or with custom error message
499 *
500 *     var s = new Schema({ born: { type: Date, required: '{PATH} is required!' })
501 *
502 *     // or through the path API
503 *
504 *     Schema.path('name').required(true);
505 *
506 *     // with custom error messaging
507 *
508 *     Schema.path('name').required(true, 'grrr :( ');
509 *
510 *     // or make a path conditionally required based on a function
511 *     var isOver18 = function() { return this.age >= 18; };
512 *     Schema.path('voterRegistrationId').required(isOver18);
513 *
514 * The required validator uses the SchemaType's `checkRequired` function to
515 * determine whether a given value satisfies the required validator. By default,
516 * a value satisfies the required validator if `val != null` (that is, if
517 * the value is not null nor undefined). However, most built-in mongoose schema
518 * types override the default `checkRequired` function:
519 *
520 * @param {Boolean} required enable/disable the validator
521 * @param {String} [message] optional custom error message
522 * @return {SchemaType} this
523 * @see Customized Error Messages #error_messages_MongooseError-messages
524 * @see SchemaArray#checkRequired #schema_array_SchemaArray.checkRequired
525 * @see SchemaBoolean#checkRequired #schema_boolean_SchemaBoolean-checkRequired
526 * @see SchemaBuffer#checkRequired #schema_buffer_SchemaBuffer.schemaName
527 * @see SchemaNumber#checkRequired #schema_number_SchemaNumber-min
528 * @see SchemaObjectId#checkRequired #schema_objectid_ObjectId-auto
529 * @see SchemaString#checkRequired #schema_string_SchemaString-checkRequired
530 * @api public
531 */
532
533SchemaType.prototype.required = function(required, message) {
534  if (required === false) {
535    this.validators = this.validators.filter(function(v) {
536      return v.validator !== this.requiredValidator;
537    }, this);
538
539    this.isRequired = false;
540    return this;
541  }
542
543  var _this = this;
544  this.isRequired = true;
545
546  this.requiredValidator = function(v) {
547    // in here, `this` refers to the validating document.
548    // no validation when this path wasn't selected in the query.
549    if ('isSelected' in this && !this.isSelected(_this.path) && !this.isModified(_this.path)) {
550      return true;
551    }
552
553    return ((typeof required === 'function') && !required.apply(this)) ||
554        _this.checkRequired(v, this);
555  };
556  this.originalRequiredValue = required;
557
558  if (typeof required === 'string') {
559    message = required;
560    required = undefined;
561  }
562
563  var msg = message || MongooseError.messages.general.required;
564  this.validators.unshift({
565    validator: this.requiredValidator,
566    message: msg,
567    type: 'required'
568  });
569
570  return this;
571};
572
573/**
574 * Gets the default value
575 *
576 * @param {Object} scope the scope which callback are executed
577 * @param {Boolean} init
578 * @api private
579 */
580
581SchemaType.prototype.getDefault = function(scope, init) {
582  var ret = typeof this.defaultValue === 'function'
583      ? this.defaultValue.call(scope)
584      : this.defaultValue;
585
586  if (ret !== null && ret !== undefined) {
587    var casted = this.cast(ret, scope, init);
588    if (casted && casted.$isSingleNested) {
589      casted.$parent = scope;
590    }
591    return casted;
592  }
593  return ret;
594};
595
596/**
597 * Applies setters
598 *
599 * @param {Object} value
600 * @param {Object} scope
601 * @param {Boolean} init
602 * @api private
603 */
604
605SchemaType.prototype.applySetters = function(value, scope, init, priorVal, options) {
606  var v = value,
607      setters = this.setters,
608      len = setters.length,
609      caster = this.caster;
610
611  while (len--) {
612    v = setters[len].call(scope, v, this);
613  }
614
615  if (Array.isArray(v) && caster && caster.setters) {
616    var newVal = [];
617    for (var i = 0; i < v.length; i++) {
618      newVal.push(caster.applySetters(v[i], scope, init, priorVal));
619    }
620    v = newVal;
621  }
622
623  if (v === null || v === undefined) {
624    return v;
625  }
626
627  // do not cast until all setters are applied #665
628  v = this.cast(v, scope, init, priorVal, options);
629
630  return v;
631};
632
633/**
634 * Applies getters to a value
635 *
636 * @param {Object} value
637 * @param {Object} scope
638 * @api private
639 */
640
641SchemaType.prototype.applyGetters = function(value, scope) {
642  var v = value,
643      getters = this.getters,
644      len = getters.length;
645
646  if (!len) {
647    return v;
648  }
649
650  while (len--) {
651    v = getters[len].call(scope, v, this);
652  }
653
654  return v;
655};
656
657/**
658 * Sets default `select()` behavior for this path.
659 *
660 * 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.
661 *
662 * ####Example:
663 *
664 *     T = db.model('T', new Schema({ x: { type: String, select: true }}));
665 *     T.find(..); // field x will always be selected ..
666 *     // .. unless overridden;
667 *     T.find().select('-x').exec(callback);
668 *
669 * @param {Boolean} val
670 * @return {SchemaType} this
671 * @api public
672 */
673
674SchemaType.prototype.select = function select(val) {
675  this.selected = !!val;
676  return this;
677};
678
679/**
680 * Performs a validation of `value` using the validators declared for this SchemaType.
681 *
682 * @param {any} value
683 * @param {Function} callback
684 * @param {Object} scope
685 * @api private
686 */
687
688SchemaType.prototype.doValidate = function(value, fn, scope) {
689  var err = false,
690      path = this.path,
691      count = this.validators.length;
692
693  if (!count) {
694    return fn(null);
695  }
696
697  var validate = function(ok, validatorProperties) {
698    if (err) {
699      return;
700    }
701    if (ok === undefined || ok) {
702      --count || fn(null);
703    } else {
704      err = new ValidatorError(validatorProperties);
705      fn(err);
706    }
707  };
708
709  var _this = this;
710  this.validators.forEach(function(v) {
711    if (err) {
712      return;
713    }
714
715    var validator = v.validator;
716
717    var validatorProperties = utils.clone(v);
718    validatorProperties.path = path;
719    validatorProperties.value = value;
720
721    if (validator instanceof RegExp) {
722      validate(validator.test(value), validatorProperties);
723    } else if (typeof validator === 'function') {
724      if (value === undefined && !_this.isRequired) {
725        validate(true, validatorProperties);
726        return;
727      }
728      if (validator.length === 2) {
729        var returnVal = validator.call(scope, value, function(ok, customMsg) {
730          if (typeof returnVal === 'boolean') {
731            return;
732          }
733          if (customMsg) {
734            validatorProperties.message = customMsg;
735          }
736          validate(ok, validatorProperties);
737        });
738        if (typeof returnVal === 'boolean') {
739          validate(returnVal, validatorProperties);
740        }
741      } else {
742        validate(validator.call(scope, value), validatorProperties);
743      }
744    }
745  });
746};
747
748/**
749 * Performs a validation of `value` using the validators declared for this SchemaType.
750 *
751 * ####Note:
752 *
753 * This method ignores the asynchronous validators.
754 *
755 * @param {any} value
756 * @param {Object} scope
757 * @return {MongooseError|undefined}
758 * @api private
759 */
760
761SchemaType.prototype.doValidateSync = function(value, scope) {
762  var err = null,
763      path = this.path,
764      count = this.validators.length;
765
766  if (!count) {
767    return null;
768  }
769
770  var validate = function(ok, validatorProperties) {
771    if (err) {
772      return;
773    }
774    if (ok !== undefined && !ok) {
775      err = new ValidatorError(validatorProperties);
776    }
777  };
778
779  var _this = this;
780  if (value === undefined && !_this.isRequired) {
781    return null;
782  }
783
784  this.validators.forEach(function(v) {
785    if (err) {
786      return;
787    }
788
789    var validator = v.validator;
790    var validatorProperties = utils.clone(v);
791    validatorProperties.path = path;
792    validatorProperties.value = value;
793
794    if (validator instanceof RegExp) {
795      validate(validator.test(value), validatorProperties);
796    } else if (typeof validator === 'function') {
797      // if not async validators
798      if (validator.length !== 2) {
799        validate(validator.call(scope, value), validatorProperties);
800      }
801    }
802  });
803
804  return err;
805};
806
807/**
808 * Determines if value is a valid Reference.
809 *
810 * @param {SchemaType} self
811 * @param {Object} value
812 * @param {Document} doc
813 * @param {Boolean} init
814 * @return {Boolean}
815 * @api private
816 */
817
818SchemaType._isRef = function(self, value, doc, init) {
819  // fast path
820  var ref = init && self.options && self.options.ref;
821
822  if (!ref && doc && doc.$__fullPath) {
823    // checks for
824    // - this populated with adhoc model and no ref was set in schema OR
825    // - setting / pushing values after population
826    var path = doc.$__fullPath(self.path);
827    var owner = doc.ownerDocument ? doc.ownerDocument() : doc;
828    ref = owner.populated(path);
829  }
830
831  if (ref) {
832    if (value == null) {
833      return true;
834    }
835    if (!Buffer.isBuffer(value) &&  // buffers are objects too
836        value._bsontype !== 'Binary' // raw binary value from the db
837        && utils.isObject(value)    // might have deselected _id in population query
838    ) {
839      return true;
840    }
841  }
842
843  return false;
844};
845
846/*!
847 * ignore
848 */
849
850function handleSingle(val) {
851  return this.castForQuery(val);
852}
853
854/*!
855 * ignore
856 */
857
858function handleArray(val) {
859  var _this = this;
860  if (!Array.isArray(val)) {
861    return [this.castForQuery(val)];
862  }
863  return val.map(function(m) {
864    return _this.castForQuery(m);
865  });
866}
867
868/*!
869 * ignore
870 */
871
872SchemaType.prototype.$conditionalHandlers = {
873  $all: handleArray,
874  $eq: handleSingle,
875  $in: handleArray,
876  $ne: handleSingle,
877  $nin: handleArray
878};
879
880/**
881 * Cast the given value with the given optional query operator.
882 *
883 * @param {String} [$conditional] query operator, like `$eq` or `$in`
884 * @param {any} val
885 * @api private
886 */
887
888SchemaType.prototype.castForQuery = function($conditional, val) {
889  var handler;
890  if (arguments.length === 2) {
891    handler = this.$conditionalHandlers[$conditional];
892    if (!handler) {
893      throw new Error('Can\'t use ' + $conditional);
894    }
895    return handler.call(this, val);
896  }
897  val = $conditional;
898  return this.cast(val);
899};
900
901/**
902 * Default check for if this path satisfies the `required` validator.
903 *
904 * @param {any} val
905 * @api private
906 */
907
908SchemaType.prototype.checkRequired = function(val) {
909  return val != null;
910};
911
912/*!
913 * Module exports.
914 */
915
916module.exports = exports = SchemaType;
917
918exports.CastError = CastError;
919
920exports.ValidatorError = ValidatorError;