schematype.js 48 KB

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