UNPKG

11.2 kBJavaScriptView Raw
1'use strict';
2
3/*!
4 * Module requirements.
5 */
6
7const MongooseError = require('../error/index');
8const SchemaNumberOptions = require('../options/SchemaNumberOptions');
9const SchemaType = require('../schematype');
10const castNumber = require('../cast/number');
11const handleBitwiseOperator = require('./operators/bitwise');
12const utils = require('../utils');
13
14const populateModelSymbol = require('../helpers/symbols').populateModelSymbol;
15
16const CastError = SchemaType.CastError;
17let Document;
18
19/**
20 * Number SchemaType constructor.
21 *
22 * @param {String} key
23 * @param {Object} options
24 * @inherits SchemaType
25 * @api public
26 */
27
28function SchemaNumber(key, options) {
29 SchemaType.call(this, key, options, 'Number');
30}
31
32/**
33 * Attaches a getter for all Number instances.
34 *
35 * ####Example:
36 *
37 * // Make all numbers round down
38 * mongoose.Number.get(function(v) { return Math.floor(v); });
39 *
40 * const Model = mongoose.model('Test', new Schema({ test: Number }));
41 * new Model({ test: 3.14 }).test; // 3
42 *
43 * @param {Function} getter
44 * @return {this}
45 * @function get
46 * @static
47 * @api public
48 */
49
50SchemaNumber.get = SchemaType.get;
51
52/**
53 * Sets a default option for all Number instances.
54 *
55 * ####Example:
56 *
57 * // Make all numbers have option `min` equal to 0.
58 * mongoose.Schema.Number.set('min', 0);
59 *
60 * const Order = mongoose.model('Order', new Schema({ amount: Number }));
61 * new Order({ amount: -10 }).validateSync().errors.amount.message; // Path `amount` must be larger than 0.
62 *
63 * @param {String} option - The option you'd like to set the value for
64 * @param {*} value - value for option
65 * @return {undefined}
66 * @function set
67 * @static
68 * @api public
69 */
70
71SchemaNumber.set = SchemaType.set;
72
73/*!
74 * ignore
75 */
76
77SchemaNumber._cast = castNumber;
78
79/**
80 * Get/set the function used to cast arbitrary values to numbers.
81 *
82 * ####Example:
83 *
84 * // Make Mongoose cast empty strings '' to 0 for paths declared as numbers
85 * const original = mongoose.Number.cast();
86 * mongoose.Number.cast(v => {
87 * if (v === '') { return 0; }
88 * return original(v);
89 * });
90 *
91 * // Or disable casting entirely
92 * mongoose.Number.cast(false);
93 *
94 * @param {Function} caster
95 * @return {Function}
96 * @function get
97 * @static
98 * @api public
99 */
100
101SchemaNumber.cast = function cast(caster) {
102 if (arguments.length === 0) {
103 return this._cast;
104 }
105 if (caster === false) {
106 caster = v => {
107 if (typeof v !== 'number') {
108 throw new Error();
109 }
110 return v;
111 };
112 }
113 this._cast = caster;
114
115 return this._cast;
116};
117
118/**
119 * This schema type's name, to defend against minifiers that mangle
120 * function names.
121 *
122 * @api public
123 */
124SchemaNumber.schemaName = 'Number';
125
126SchemaNumber.defaultOptions = {};
127
128/*!
129 * Inherits from SchemaType.
130 */
131SchemaNumber.prototype = Object.create(SchemaType.prototype);
132SchemaNumber.prototype.constructor = SchemaNumber;
133SchemaNumber.prototype.OptionsConstructor = SchemaNumberOptions;
134
135/*!
136 * ignore
137 */
138
139SchemaNumber._checkRequired = v => typeof v === 'number' || v instanceof Number;
140
141/**
142 * Override the function the required validator uses to check whether a string
143 * passes the `required` check.
144 *
145 * @param {Function} fn
146 * @return {Function}
147 * @function checkRequired
148 * @static
149 * @api public
150 */
151
152SchemaNumber.checkRequired = SchemaType.checkRequired;
153
154/**
155 * Check if the given value satisfies a required validator.
156 *
157 * @param {Any} value
158 * @param {Document} doc
159 * @return {Boolean}
160 * @api public
161 */
162
163SchemaNumber.prototype.checkRequired = function checkRequired(value, doc) {
164 if (SchemaType._isRef(this, value, doc, true)) {
165 return !!value;
166 }
167
168 // `require('util').inherits()` does **not** copy static properties, and
169 // plugins like mongoose-float use `inherits()` for pre-ES6.
170 const _checkRequired = typeof this.constructor.checkRequired == 'function' ?
171 this.constructor.checkRequired() :
172 SchemaNumber.checkRequired();
173
174 return _checkRequired(value);
175};
176
177/**
178 * Sets a minimum number validator.
179 *
180 * ####Example:
181 *
182 * var s = new Schema({ n: { type: Number, min: 10 })
183 * var M = db.model('M', s)
184 * var m = new M({ n: 9 })
185 * m.save(function (err) {
186 * console.error(err) // validator error
187 * m.n = 10;
188 * m.save() // success
189 * })
190 *
191 * // custom error messages
192 * // We can also use the special {MIN} token which will be replaced with the invalid value
193 * var min = [10, 'The value of path `{PATH}` ({VALUE}) is beneath the limit ({MIN}).'];
194 * var schema = new Schema({ n: { type: Number, min: min })
195 * var M = mongoose.model('Measurement', schema);
196 * var s= new M({ n: 4 });
197 * s.validate(function (err) {
198 * console.log(String(err)) // ValidationError: The value of path `n` (4) is beneath the limit (10).
199 * })
200 *
201 * @param {Number} value minimum number
202 * @param {String} [message] optional custom error message
203 * @return {SchemaType} this
204 * @see Customized Error Messages #error_messages_MongooseError-messages
205 * @api public
206 */
207
208SchemaNumber.prototype.min = function(value, message) {
209 if (this.minValidator) {
210 this.validators = this.validators.filter(function(v) {
211 return v.validator !== this.minValidator;
212 }, this);
213 }
214
215 if (value !== null && value !== undefined) {
216 let msg = message || MongooseError.messages.Number.min;
217 msg = msg.replace(/{MIN}/, value);
218 this.validators.push({
219 validator: this.minValidator = function(v) {
220 return v == null || v >= value;
221 },
222 message: msg,
223 type: 'min',
224 min: value
225 });
226 }
227
228 return this;
229};
230
231/**
232 * Sets a maximum number validator.
233 *
234 * ####Example:
235 *
236 * var s = new Schema({ n: { type: Number, max: 10 })
237 * var M = db.model('M', s)
238 * var m = new M({ n: 11 })
239 * m.save(function (err) {
240 * console.error(err) // validator error
241 * m.n = 10;
242 * m.save() // success
243 * })
244 *
245 * // custom error messages
246 * // We can also use the special {MAX} token which will be replaced with the invalid value
247 * var max = [10, 'The value of path `{PATH}` ({VALUE}) exceeds the limit ({MAX}).'];
248 * var schema = new Schema({ n: { type: Number, max: max })
249 * var M = mongoose.model('Measurement', schema);
250 * var s= new M({ n: 4 });
251 * s.validate(function (err) {
252 * console.log(String(err)) // ValidationError: The value of path `n` (4) exceeds the limit (10).
253 * })
254 *
255 * @param {Number} maximum number
256 * @param {String} [message] optional custom error message
257 * @return {SchemaType} this
258 * @see Customized Error Messages #error_messages_MongooseError-messages
259 * @api public
260 */
261
262SchemaNumber.prototype.max = function(value, message) {
263 if (this.maxValidator) {
264 this.validators = this.validators.filter(function(v) {
265 return v.validator !== this.maxValidator;
266 }, this);
267 }
268
269 if (value !== null && value !== undefined) {
270 let msg = message || MongooseError.messages.Number.max;
271 msg = msg.replace(/{MAX}/, value);
272 this.validators.push({
273 validator: this.maxValidator = function(v) {
274 return v == null || v <= value;
275 },
276 message: msg,
277 type: 'max',
278 max: value
279 });
280 }
281
282 return this;
283};
284
285/**
286 * Sets a enum validator
287 *
288 * ####Example:
289 *
290 * var s = new Schema({ n: { type: Number, enum: [1, 2, 3] });
291 * var M = db.model('M', s);
292 *
293 * var m = new M({ n: 4 });
294 * await m.save(); // throws validation error
295 *
296 * m.n = 3;
297 * await m.save(); // succeeds
298 *
299 * @param {Array} values allowed values
300 * @param {String} [message] optional custom error message
301 * @return {SchemaType} this
302 * @see Customized Error Messages #error_messages_MongooseError-messages
303 * @api public
304 */
305
306SchemaNumber.prototype.enum = function(values, message) {
307 if (this.enumValidator) {
308 this.validators = this.validators.filter(function(v) {
309 return v.validator !== this.enumValidator;
310 }, this);
311 }
312
313 if (!Array.isArray(values)) {
314 values = Array.prototype.slice.call(arguments);
315 message = MongooseError.messages.Number.enum;
316 }
317
318 message = message == null ? MongooseError.messages.Number.enum : message;
319
320 this.enumValidator = v => v == null || values.indexOf(v) !== -1;
321 this.validators.push({
322 validator: this.enumValidator,
323 message: message,
324 type: 'enum',
325 enumValues: values
326 });
327
328 return this;
329};
330
331/**
332 * Casts to number
333 *
334 * @param {Object} value value to cast
335 * @param {Document} doc document that triggers the casting
336 * @param {Boolean} init
337 * @api private
338 */
339
340SchemaNumber.prototype.cast = function(value, doc, init) {
341 if (SchemaType._isRef(this, value, doc, init)) {
342 // wait! we may need to cast this to a document
343
344 if (value === null || value === undefined) {
345 return value;
346 }
347
348 // lazy load
349 Document || (Document = require('./../document'));
350
351 if (value instanceof Document) {
352 value.$__.wasPopulated = true;
353 return value;
354 }
355
356 // setting a populated path
357 if (typeof value === 'number') {
358 return value;
359 } else if (Buffer.isBuffer(value) || !utils.isObject(value)) {
360 throw new CastError('Number', value, this.path, null, this);
361 }
362
363 // Handle the case where user directly sets a populated
364 // path to a plain object; cast to the Model used in
365 // the population query.
366 const path = doc.$__fullPath(this.path);
367 const owner = doc.ownerDocument ? doc.ownerDocument() : doc;
368 const pop = owner.populated(path, true);
369 const ret = new pop.options[populateModelSymbol](value);
370 ret.$__.wasPopulated = true;
371 return ret;
372 }
373
374 const val = value && typeof value._id !== 'undefined' ?
375 value._id : // documents
376 value;
377
378 const castNumber = typeof this.constructor.cast === 'function' ?
379 this.constructor.cast() :
380 SchemaNumber.cast();
381 try {
382 return castNumber(val);
383 } catch (err) {
384 throw new CastError('Number', val, this.path, err, this);
385 }
386};
387
388/*!
389 * ignore
390 */
391
392function handleSingle(val) {
393 return this.cast(val);
394}
395
396function handleArray(val) {
397 const _this = this;
398 if (!Array.isArray(val)) {
399 return [this.cast(val)];
400 }
401 return val.map(function(m) {
402 return _this.cast(m);
403 });
404}
405
406SchemaNumber.prototype.$conditionalHandlers =
407 utils.options(SchemaType.prototype.$conditionalHandlers, {
408 $bitsAllClear: handleBitwiseOperator,
409 $bitsAnyClear: handleBitwiseOperator,
410 $bitsAllSet: handleBitwiseOperator,
411 $bitsAnySet: handleBitwiseOperator,
412 $gt: handleSingle,
413 $gte: handleSingle,
414 $lt: handleSingle,
415 $lte: handleSingle,
416 $mod: handleArray
417 });
418
419/**
420 * Casts contents for queries.
421 *
422 * @param {String} $conditional
423 * @param {any} [value]
424 * @api private
425 */
426
427SchemaNumber.prototype.castForQuery = function($conditional, val) {
428 let handler;
429 if (arguments.length === 2) {
430 handler = this.$conditionalHandlers[$conditional];
431 if (!handler) {
432 throw new CastError('number', val, this.path, null, this);
433 }
434 return handler.call(this, val);
435 }
436 val = this._castForQuery($conditional);
437 return val;
438};
439
440/*!
441 * Module exports.
442 */
443
444module.exports = SchemaNumber;