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;