arnaucube
arnaucube/thoughtsPublic Self-hosted repository
thoughts/node_modules/mongoose/lib/schema/string.js
node_modules/mongoose/lib/schema/string.jsRaw
  1/*!
  2 * Module dependencies.
  3 */
  4
  5var SchemaType = require('../schematype');
  6var CastError = SchemaType.CastError;
  7var MongooseError = require('../error');
  8var utils = require('../utils');
  9var Document;
 10
 11/**
 12 * String SchemaType constructor.
 13 *
 14 * @param {String} key
 15 * @param {Object} options
 16 * @inherits SchemaType
 17 * @api public
 18 */
 19
 20function SchemaString(key, options) {
 21  this.enumValues = [];
 22  this.regExp = null;
 23  SchemaType.call(this, key, options, 'String');
 24}
 25
 26/**
 27 * This schema type's name, to defend against minifiers that mangle
 28 * function names.
 29 *
 30 * @api public
 31 */
 32SchemaString.schemaName = 'String';
 33
 34/*!
 35 * Inherits from SchemaType.
 36 */
 37SchemaString.prototype = Object.create(SchemaType.prototype);
 38SchemaString.prototype.constructor = SchemaString;
 39
 40/**
 41 * Adds an enum validator
 42 *
 43 * ####Example:
 44 *
 45 *     var states = 'opening open closing closed'.split(' ')
 46 *     var s = new Schema({ state: { type: String, enum: states }})
 47 *     var M = db.model('M', s)
 48 *     var m = new M({ state: 'invalid' })
 49 *     m.save(function (err) {
 50 *       console.error(String(err)) // ValidationError: `invalid` is not a valid enum value for path `state`.
 51 *       m.state = 'open'
 52 *       m.save(callback) // success
 53 *     })
 54 *
 55 *     // or with custom error messages
 56 *     var enu = {
 57 *       values: 'opening open closing closed'.split(' '),
 58 *       message: 'enum validator failed for path `{PATH}` with value `{VALUE}`'
 59 *     }
 60 *     var s = new Schema({ state: { type: String, enum: enu })
 61 *     var M = db.model('M', s)
 62 *     var m = new M({ state: 'invalid' })
 63 *     m.save(function (err) {
 64 *       console.error(String(err)) // ValidationError: enum validator failed for path `state` with value `invalid`
 65 *       m.state = 'open'
 66 *       m.save(callback) // success
 67 *     })
 68 *
 69 * @param {String|Object} [args...] enumeration values
 70 * @return {SchemaType} this
 71 * @see Customized Error Messages #error_messages_MongooseError-messages
 72 * @api public
 73 */
 74
 75SchemaString.prototype.enum = function() {
 76  if (this.enumValidator) {
 77    this.validators = this.validators.filter(function(v) {
 78      return v.validator !== this.enumValidator;
 79    }, this);
 80    this.enumValidator = false;
 81  }
 82
 83  if (arguments[0] === void 0 || arguments[0] === false) {
 84    return this;
 85  }
 86
 87  var values;
 88  var errorMessage;
 89
 90  if (utils.isObject(arguments[0])) {
 91    values = arguments[0].values;
 92    errorMessage = arguments[0].message;
 93  } else {
 94    values = arguments;
 95    errorMessage = MongooseError.messages.String.enum;
 96  }
 97
 98  for (var i = 0; i < values.length; i++) {
 99    if (undefined !== values[i]) {
100      this.enumValues.push(this.cast(values[i]));
101    }
102  }
103
104  var vals = this.enumValues;
105  this.enumValidator = function(v) {
106    return undefined === v || ~vals.indexOf(v);
107  };
108  this.validators.push({
109    validator: this.enumValidator,
110    message: errorMessage,
111    type: 'enum',
112    enumValues: vals
113  });
114
115  return this;
116};
117
118/**
119 * Adds a lowercase setter.
120 *
121 * ####Example:
122 *
123 *     var s = new Schema({ email: { type: String, lowercase: true }})
124 *     var M = db.model('M', s);
125 *     var m = new M({ email: 'SomeEmail@example.COM' });
126 *     console.log(m.email) // someemail@example.com
127 *
128 * @api public
129 * @return {SchemaType} this
130 */
131
132SchemaString.prototype.lowercase = function() {
133  return this.set(function(v, self) {
134    if (typeof v !== 'string') {
135      v = self.cast(v);
136    }
137    if (v) {
138      return v.toLowerCase();
139    }
140    return v;
141  });
142};
143
144/**
145 * Adds an uppercase setter.
146 *
147 * ####Example:
148 *
149 *     var s = new Schema({ caps: { type: String, uppercase: true }})
150 *     var M = db.model('M', s);
151 *     var m = new M({ caps: 'an example' });
152 *     console.log(m.caps) // AN EXAMPLE
153 *
154 * @api public
155 * @return {SchemaType} this
156 */
157
158SchemaString.prototype.uppercase = function() {
159  return this.set(function(v, self) {
160    if (typeof v !== 'string') {
161      v = self.cast(v);
162    }
163    if (v) {
164      return v.toUpperCase();
165    }
166    return v;
167  });
168};
169
170/**
171 * Adds a trim setter.
172 *
173 * The string value will be trimmed when set.
174 *
175 * ####Example:
176 *
177 *     var s = new Schema({ name: { type: String, trim: true }})
178 *     var M = db.model('M', s)
179 *     var string = ' some name '
180 *     console.log(string.length) // 11
181 *     var m = new M({ name: string })
182 *     console.log(m.name.length) // 9
183 *
184 * @api public
185 * @return {SchemaType} this
186 */
187
188SchemaString.prototype.trim = function() {
189  return this.set(function(v, self) {
190    if (typeof v !== 'string') {
191      v = self.cast(v);
192    }
193    if (v && self.options.trim) {
194      return v.trim();
195    }
196    return v;
197  });
198};
199
200/**
201 * Sets a minimum length validator.
202 *
203 * ####Example:
204 *
205 *     var schema = new Schema({ postalCode: { type: String, minlength: 5 })
206 *     var Address = db.model('Address', schema)
207 *     var address = new Address({ postalCode: '9512' })
208 *     address.save(function (err) {
209 *       console.error(err) // validator error
210 *       address.postalCode = '95125';
211 *       address.save() // success
212 *     })
213 *
214 *     // custom error messages
215 *     // We can also use the special {MINLENGTH} token which will be replaced with the minimum allowed length
216 *     var minlength = [5, 'The value of path `{PATH}` (`{VALUE}`) is shorter than the minimum allowed length ({MINLENGTH}).'];
217 *     var schema = new Schema({ postalCode: { type: String, minlength: minlength })
218 *     var Address = mongoose.model('Address', schema);
219 *     var address = new Address({ postalCode: '9512' });
220 *     address.validate(function (err) {
221 *       console.log(String(err)) // ValidationError: The value of path `postalCode` (`9512`) is shorter than the minimum length (5).
222 *     })
223 *
224 * @param {Number} value minimum string length
225 * @param {String} [message] optional custom error message
226 * @return {SchemaType} this
227 * @see Customized Error Messages #error_messages_MongooseError-messages
228 * @api public
229 */
230
231SchemaString.prototype.minlength = function(value, message) {
232  if (this.minlengthValidator) {
233    this.validators = this.validators.filter(function(v) {
234      return v.validator !== this.minlengthValidator;
235    }, this);
236  }
237
238  if (value !== null && value !== undefined) {
239    var msg = message || MongooseError.messages.String.minlength;
240    msg = msg.replace(/{MINLENGTH}/, value);
241    this.validators.push({
242      validator: this.minlengthValidator = function(v) {
243        return v === null || v.length >= value;
244      },
245      message: msg,
246      type: 'minlength',
247      minlength: value
248    });
249  }
250
251  return this;
252};
253
254/**
255 * Sets a maximum length validator.
256 *
257 * ####Example:
258 *
259 *     var schema = new Schema({ postalCode: { type: String, maxlength: 9 })
260 *     var Address = db.model('Address', schema)
261 *     var address = new Address({ postalCode: '9512512345' })
262 *     address.save(function (err) {
263 *       console.error(err) // validator error
264 *       address.postalCode = '95125';
265 *       address.save() // success
266 *     })
267 *
268 *     // custom error messages
269 *     // We can also use the special {MAXLENGTH} token which will be replaced with the maximum allowed length
270 *     var maxlength = [9, 'The value of path `{PATH}` (`{VALUE}`) exceeds the maximum allowed length ({MAXLENGTH}).'];
271 *     var schema = new Schema({ postalCode: { type: String, maxlength: maxlength })
272 *     var Address = mongoose.model('Address', schema);
273 *     var address = new Address({ postalCode: '9512512345' });
274 *     address.validate(function (err) {
275 *       console.log(String(err)) // ValidationError: The value of path `postalCode` (`9512512345`) exceeds the maximum allowed length (9).
276 *     })
277 *
278 * @param {Number} value maximum string length
279 * @param {String} [message] optional custom error message
280 * @return {SchemaType} this
281 * @see Customized Error Messages #error_messages_MongooseError-messages
282 * @api public
283 */
284
285SchemaString.prototype.maxlength = function(value, message) {
286  if (this.maxlengthValidator) {
287    this.validators = this.validators.filter(function(v) {
288      return v.validator !== this.maxlengthValidator;
289    }, this);
290  }
291
292  if (value !== null && value !== undefined) {
293    var msg = message || MongooseError.messages.String.maxlength;
294    msg = msg.replace(/{MAXLENGTH}/, value);
295    this.validators.push({
296      validator: this.maxlengthValidator = function(v) {
297        return v === null || v.length <= value;
298      },
299      message: msg,
300      type: 'maxlength',
301      maxlength: value
302    });
303  }
304
305  return this;
306};
307
308/**
309 * Sets a regexp validator.
310 *
311 * Any value that does not pass `regExp`.test(val) will fail validation.
312 *
313 * ####Example:
314 *
315 *     var s = new Schema({ name: { type: String, match: /^a/ }})
316 *     var M = db.model('M', s)
317 *     var m = new M({ name: 'I am invalid' })
318 *     m.validate(function (err) {
319 *       console.error(String(err)) // "ValidationError: Path `name` is invalid (I am invalid)."
320 *       m.name = 'apples'
321 *       m.validate(function (err) {
322 *         assert.ok(err) // success
323 *       })
324 *     })
325 *
326 *     // using a custom error message
327 *     var match = [ /\.html$/, "That file doesn't end in .html ({VALUE})" ];
328 *     var s = new Schema({ file: { type: String, match: match }})
329 *     var M = db.model('M', s);
330 *     var m = new M({ file: 'invalid' });
331 *     m.validate(function (err) {
332 *       console.log(String(err)) // "ValidationError: That file doesn't end in .html (invalid)"
333 *     })
334 *
335 * Empty strings, `undefined`, and `null` values always pass the match validator. If you require these values, enable the `required` validator also.
336 *
337 *     var s = new Schema({ name: { type: String, match: /^a/, required: true }})
338 *
339 * @param {RegExp} regExp regular expression to test against
340 * @param {String} [message] optional custom error message
341 * @return {SchemaType} this
342 * @see Customized Error Messages #error_messages_MongooseError-messages
343 * @api public
344 */
345
346SchemaString.prototype.match = function match(regExp, message) {
347  // yes, we allow multiple match validators
348
349  var msg = message || MongooseError.messages.String.match;
350
351  var matchValidator = function(v) {
352    if (!regExp) {
353      return false;
354    }
355
356    var ret = ((v != null && v !== '')
357        ? regExp.test(v)
358        : true);
359    return ret;
360  };
361
362  this.validators.push({
363    validator: matchValidator,
364    message: msg,
365    type: 'regexp',
366    regexp: regExp
367  });
368  return this;
369};
370
371/**
372 * Check if the given value satisfies a required validator.
373 *
374 * @param {Any} value
375 * @param {Document} doc
376 * @return {Boolean}
377 * @api public
378 */
379
380SchemaString.prototype.checkRequired = function checkRequired(value, doc) {
381  if (SchemaType._isRef(this, value, doc, true)) {
382    return !!value;
383  }
384  return (value instanceof String || typeof value === 'string') && value.length;
385};
386
387/**
388 * Casts to String
389 *
390 * @api private
391 */
392
393SchemaString.prototype.cast = function(value, doc, init) {
394  if (SchemaType._isRef(this, value, doc, init)) {
395    // wait! we may need to cast this to a document
396
397    if (value === null || value === undefined) {
398      return value;
399    }
400
401    // lazy load
402    Document || (Document = require('./../document'));
403
404    if (value instanceof Document) {
405      value.$__.wasPopulated = true;
406      return value;
407    }
408
409    // setting a populated path
410    if (typeof value === 'string') {
411      return value;
412    } else if (Buffer.isBuffer(value) || !utils.isObject(value)) {
413      throw new CastError('string', value, this.path);
414    }
415
416    // Handle the case where user directly sets a populated
417    // path to a plain object; cast to the Model used in
418    // the population query.
419    var path = doc.$__fullPath(this.path);
420    var owner = doc.ownerDocument ? doc.ownerDocument() : doc;
421    var pop = owner.populated(path, true);
422    var ret = new pop.options.model(value);
423    ret.$__.wasPopulated = true;
424    return ret;
425  }
426
427  // If null or undefined
428  if (value === null || value === undefined) {
429    return value;
430  }
431
432  if (typeof value !== 'undefined') {
433    // handle documents being passed
434    if (value._id && typeof value._id === 'string') {
435      return value._id;
436    }
437
438    // Re: gh-647 and gh-3030, we're ok with casting using `toString()`
439    // **unless** its the default Object.toString, because "[object Object]"
440    // doesn't really qualify as useful data
441    if (value.toString && value.toString !== Object.prototype.toString) {
442      return value.toString();
443    }
444  }
445
446  throw new CastError('string', value, this.path);
447};
448
449/*!
450 * ignore
451 */
452
453function handleSingle(val) {
454  return this.castForQuery(val);
455}
456
457function handleArray(val) {
458  var _this = this;
459  if (!Array.isArray(val)) {
460    return [this.castForQuery(val)];
461  }
462  return val.map(function(m) {
463    return _this.castForQuery(m);
464  });
465}
466
467SchemaString.prototype.$conditionalHandlers =
468    utils.options(SchemaType.prototype.$conditionalHandlers, {
469      $all: handleArray,
470      $gt: handleSingle,
471      $gte: handleSingle,
472      $lt: handleSingle,
473      $lte: handleSingle,
474      $options: handleSingle,
475      $regex: handleSingle
476    });
477
478/**
479 * Casts contents for queries.
480 *
481 * @param {String} $conditional
482 * @param {any} [val]
483 * @api private
484 */
485
486SchemaString.prototype.castForQuery = function($conditional, val) {
487  var handler;
488  if (arguments.length === 2) {
489    handler = this.$conditionalHandlers[$conditional];
490    if (!handler) {
491      throw new Error('Can\'t use ' + $conditional + ' with String.');
492    }
493    return handler.call(this, val);
494  }
495  val = $conditional;
496  if (Object.prototype.toString.call(val) === '[object RegExp]') {
497    return val;
498  }
499  return this.cast(val);
500};
501
502/*!
503 * Module exports.
504 */
505
506module.exports = SchemaString;