Running "mochacov:coverage" (mochacov) task Coverage

Coverage

94%
636
599
37

/Users/nrako/sandbox/formal/lib/error.js

100%
11
11
0
LineHitsSource
1
21var util = require('util');
3/**
4 * FormalError constructor
5 *
6 * @param {String} msg Error message
7 * @inherits Error https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error
8 */
9
101function FormalError (msg) {
1128 Error.call(this);
1228 Error.captureStackTrace(this, FormalError);
1328 this.message = msg;
1428 this.name = 'FormalError';
15}
16
17/*!
18 * Inherits from Error.
19 */
20
211util.inherits(FormalError, Error);
22
23/*!
24 * Module exports.
25 */
26
271module.exports = exports = FormalError;
28
29/*!
30 * Expose subclasses
31 */
32
331FormalError.CastError = require('./errors/cast');
34//FormalError.DocumentError = require('./errors/document');
351FormalError.ValidationError = require('./errors/validation');
361FormalError.ValidatorError = require('./errors/validator');
37//FormalError.VersionError =require('./errors/version')
38//FormalError.OverwriteModelError = require('./errors/overwriteModel')
39//FormalError.MissingSchemaError = require('./errors/missingSchema')
40//FormalError.DivergentArrayError = require('./errors/divergentArray')
41

/Users/nrako/sandbox/formal/lib/errors/cast.js

100%
10
10
0
LineHitsSource
1/*!
2 * Module dependencies.
3 */
41var util = require('util'),
5 FormalError = require('../error');
6
7/**
8 * Casting Error constructor.
9 *
10 * @param {String} type
11 * @param {String} value
12 * @inherits FormalError
13 * @api private
14 */
15
161function CastError (type, value, path) {
173 FormalError.call(this, 'Cast to ' + type + ' failed for value "' + value + '" at path "' + path + '"');
182 Error.captureStackTrace(this, CastError);
192 this.name = 'CastError';
202 this.type = type;
212 this.value = value;
222 this.path = path;
23}
24
25/*!
26 * Inherits from FormalError.
27 */
28
291util.inherits(CastError, FormalError);
30
31/*!
32 * exports
33 */
34
351module.exports = CastError;
36

/Users/nrako/sandbox/formal/lib/errors/validation.js

100%
15
15
0
LineHitsSource
1
2/*!
3 * Module requirements
4 */
5
61var FormalError = require('../error'),
7 util = require('util');
8
9/**
10 * Document Validation Error
11 *
12 * @api private
13 * @param {Document} instance
14 * @inherits FormalError
15 */
16
171function ValidationError (instance) {
187 FormalError.call(this, 'Validation failed');
197 Error.captureStackTrace(this, ValidationError);
207 this.name = 'ValidationError';
217 this.errors = instance.errors = {};
22}
23
24/*!
25 * Inherits from FormalError.
26 */
27
281util.inherits(ValidationError, FormalError);
29
30/**
31 * Console.log helper
32 */
33
341ValidationError.prototype.toString = function () {
351 var ret = this.name + ': ';
361 var msgs = [];
37
381 Object.keys(this.errors).forEach(function (key) {
392 if (this == this.errors[key]) return;
402 msgs.push(String(this.errors[key]));
41 }, this);
42
431 return ret + msgs.join(', ');
44};
45
46/*!
47 * Module exports
48 */
49
501module.exports = exports = ValidationError;
51

/Users/nrako/sandbox/formal/lib/errors/validator.js

100%
15
15
0
LineHitsSource
1/*!
2 * Module dependencies.
3 */
4
51var FormalError = require('../error'),
6 util = require('util');
7
8/**
9 * Schema validator error
10 *
11 * @param {String} path
12 * @param {String} msg
13 * @param {String|Number|any} val
14 * @inherits FormalError
15 * @api private
16 */
17
181function ValidatorError (path, type, val) {
1919 var msg = type ? '"' + type + '" ' : '';
20
2119 var message = 'Validator ' + msg + 'failed for path ' + path;
2238 if (2 < arguments.length) message += ' with value `' + String(val) + '`';
23
2419 FormalError.call(this, message);
2519 Error.captureStackTrace(this, ValidatorError);
2619 this.name = 'ValidatorError';
2719 this.path = path;
2819 this.type = type;
2919 this.value = val;
30}
31
32/*!
33 * Inherits from FormalError
34 */
35
361util.inherits(ValidatorError, FormalError);
37
38/*!
39 * toString helper
40 */
41
421ValidatorError.prototype.toString = function () {
432 return this.message;
44};
45
46
47/*!
48 * exports
49 */
50
511module.exports = ValidatorError;
52

/Users/nrako/sandbox/formal/lib/field/array.js

93%
33
31
2
LineHitsSource
1/*!
2 * Module dependencies.
3 */
4
51var Field = require('./field'),
6 util = require('util'),
7 CastError = Field.CastError,
8 Types = {
9 Boolean: require('./boolean'),
10 Date: require('./date'),
11 Number: require('./number'),
12 String: require('./string')
13 };
14
15/**
16 * Array Field constructor
17 *
18 * @param {String} key
19 * @param {Field} cast
20 * @param {Object} options
21 * @inherits Field
22 * @api private
23 */
24
251function FieldArray (key, cast, options) {
2610 if (cast) {
2710 var castOptions = {};
28
29 // support { type: 'String' }
3010 var name = 'string' == typeof cast ? cast : cast.name;
31
3210 var Caster = name in Types ? Types[name] : cast;
33
3410 this.casterConstructor = Caster;
3510 this.caster = new Caster(null, castOptions);
36 }
37
3810 Field.call(this, key, options);
39
4010 var self = this,
41 defaultArr, fn;
42
4310 if (this.defaultValue) {
441 defaultArr = this.defaultValue;
451 fn = 'function' == typeof defaultArr;
46 }
47
4810 this.default(function(){
4910 var arr = fn ? defaultArr() : defaultArr || [];
5010 return arr;
51 });
52}
53
54/*!
55 * Inherits from Field.
56 */
57
581util.inherits(FieldArray, Field);
59
60/**
61 * Check required
62 *
63 * @param {Array} value
64 * @api private
65 */
66
671FieldArray.prototype.checkRequired = function (value) {
681 return !!(value && value.length);
69};
70
71/**
72 * Overrides the getters application for the population special-case
73 *
74 * @param {Object} value
75 * @param {Object} scope
76 * @api private
77 */
78
791FieldArray.prototype.applyGetters = function (value, scope) {
809 if (this.caster.options && this.caster.options.ref) {
81 // means the object id was populated
820 return value;
83 }
84
859 return Field.prototype.applyGetters.call(this, value, scope);
86};
87
88/**
89 * Casts contents
90 *
91 * @param {Object} value
92 * @param {Form} form that triggers the casting
93 * @param {Boolean} init whether this is an initialization cast
94 * @api private
95 */
96
971FieldArray.prototype.cast = function (value, form, init) {
9818 if (Array.isArray(value)) {
99
10017 if (this.caster) {
10117 try {
10217 for (var i = 0, l = value.length; i < l; i++) {
10317 value[i] = this.caster.cast(value[i], form, init);
104 }
105 } catch (e) {
106 // rethrow
1070 throw new CastError(e.type, value, this.path);
108 }
109 }
110
11117 return value;
112 } else {
1131 return this.cast([value], form, init);
114 }
115};
116
117/*!
118 * Module exports.
119 */
120
1211module.exports = FieldArray;
122

/Users/nrako/sandbox/formal/lib/field/boolean.js

100%
18
18
0
LineHitsSource
1/*!
2 * Module dependencies.
3 */
4
51var util = require('util'),
6 Field = require('./field');
7
8/**
9 * Boolean Field constructor.
10 *
11 * @param {String} path
12 * @param {Object} options
13 * @inherits Field
14 * @api private
15 */
16
171function FieldBoolean (path, options) {
1812 Field.call(this, path, options);
19}
20
21/*!
22 * Inherits from Field.
23 */
241util.inherits(FieldBoolean, Field);
25
26/**
27 * Required validator
28 *
29 * @api private
30 */
31
321FieldBoolean.prototype.checkRequired = function (value) {
331 return value === true || value === false;
34};
35
36/**
37 * Casts to boolean
38 *
39 * @param {Object} value
40 * @api private
41 */
42
431FieldBoolean.prototype.cast = function (value) {
4410 if (null === value) return value;
4511 if ('0' === value) return false;
469 if ('true' === value) return true;
4710 if ('false' === value) return false;
488 return !! value;
49};
50
511FieldBoolean.prototype.export = function() {
521 FieldBoolean.super_.prototype.export.call(this);
53
541 var attr = this.options.attributes || (this.options.attributes = {});
55
561 attr.type = 'checkbox';
57
581 return this.options;
59};
60
61/*!
62 * Module exports.
63 */
64
651module.exports = FieldBoolean;
66

/Users/nrako/sandbox/formal/lib/field/date.js

100%
25
25
0
LineHitsSource
1/*!
2 * Module requirements.
3 */
4
51var Field = require('./field'),
6 util = require('util'),
7 CastError = Field.CastError;
8
9/**
10 * Date Field constructor.
11 *
12 * @param {String} key
13 * @param {Object} options
14 * @inherits Field
15 * @api private
16 */
17
181function FieldDate (key, options) {
1910 Field.call(this, key, options);
20}
21
22/*!
23 * Inherits from Field.
24 */
25
261util.inherits(FieldDate, Field);
27
28/**
29 * Required validator for date
30 *
31 * @api private
32 */
33
341FieldDate.prototype.checkRequired = function (value) {
352 return value instanceof Date;
36};
37
38/**
39 * Casts to date
40 *
41 * @param {Object} value to cast
42 * @api private
43 */
44
451FieldDate.prototype.cast = function (value) {
469 if (value === null || value === '')
471 return null;
48
498 if (value instanceof Date)
505 return value;
51
523 var date;
53
54 // support for timestamps
553 if (value instanceof Number || 'number' == typeof value || String(value) == Number(value))
561 date = new Date(Number(value));
57
58 // support for date strings
592 else if (value.toString)
602 date = new Date(value.toString());
61
623 if (date.toString() != 'Invalid Date')
632 return date;
64
651 throw new CastError('date', value, this.path);
66};
67
681FieldDate.prototype.export = function() {
691 FieldDate.super_.prototype.export.call(this);
70
711 var attr = this.options.attributes || (this.options.attributes = {});
72
731 attr.type = 'date';
74
751 return this.options;
76};
77
78
79/*!
80 * Module exports.
81 */
82
831module.exports = FieldDate;
84

/Users/nrako/sandbox/formal/lib/field/field.js

99%
106
105
1
LineHitsSource
1/*!
2 * Module dependencies.
3 */
4
51var CastError = require('../error').CastError,
6 ValidatorError = require('../error').ValidatorError,
7 util = require('util'),
8 _ = require('lodash'),
9 EventEmitter = require('events').EventEmitter;
10
11/**
12 * Field constructor
13 *
14 * @param {String} path
15 * @param {Object} [options]
16 * @param {String} [instance]
17 * @api public
18 */
19
201function Field (path, options, instance) {
2195 this.path = path;
2295 this.instance = instance;
2395 this.validators = [];
2495 this.setters = [];
2595 this.getters = [];
2695 this.defaultValue = null;
27
2895 this.options = options;
29
3095 for (var opt in options)
31122 if (this[opt] && 'function' == typeof this[opt]) {
32 // { unique: true, index: true }
33 // if ('index' == opt && this._index) continue;
34
3535 var opts = Array.isArray(options[opt]) ? options[opt] : [options[opt]];
36
3735 this[opt].apply(this, opts);
38 }
39
4095 EventEmitter.call(this);
41}
42
431util.inherits(Field, EventEmitter);
44
45/**
46 * Sets a default value for this Field.
47 *
48 * ####Example:
49 *
50 * var field = new Field({ n: { type: Number, default: 10 })
51 * var M = db.model('M', field)
52 * var m = new M;
53 * console.log(m.n) // 10
54 *
55 * Defaults can be either `functions` which return the value to use as the default or the literal value itself. Either way, the value will be cast based on its field type before being set during document creation.
56 *
57 * ####Example:
58 *
59 * // values are cast:
60 * var field = new Field({ aNumber: Number, default: "4.815162342" })
61 * var M = db.model('M', field)
62 * var m = new M;
63 * console.log(m.aNumber) // 4.815162342
64 *
65 * // default unique objects for Mixed types:
66 * var field = new Field({ mixed: Field.Types.Mixed });
67 * field.path('mixed').default(function () {
68 * return {};
69 * });
70 *
71 * // if we don't use a function to return object literals for Mixed defaults,
72 * // each document will receive a reference to the same object literal creating
73 * // a "shared" object instance:
74 * var field = new Field({ mixed: Field.Types.Mixed });
75 * field.path('mixed').default({});
76 * var M = db.model('M', field);
77 * var m1 = new M;
78 * m1.mixed.added = 1;
79 * console.log(m1.mixed); // { added: 1 }
80 * var m2 = new M;
81 * console.log(m2.mixed); // { added: 1 }
82 *
83 * @param {Function|any} val the default value
84 * @return {defaultValue}
85 * @api public
86 */
87
881Field.prototype.default = function (val) {
8914 if (1 === arguments.length) {
9011 this.defaultValue = typeof val === 'function' ? val : this.cast(val);
9111 return this;
923 } else if (arguments.length > 1) {
932 this.defaultValue = _.toArray(arguments);
94 }
953 if (arguments.length)
962 this.emit('default', this.defaultValue);
973 return this.defaultValue;
98};
99
100/**
101 * Declares an unique index.
102 *
103 * ####Example:
104 *
105 * var s = new Field({ name: { type: String, unique: true })
106 * Field.path('name').index({ unique: true });
107 *
108 * _NOTE: violating the constraint returns an `E11000` error from MongoDB when saving, not a Mongoose validation error._
109 *
110 * @param {Boolean} bool
111 * @return {Field} this
112 * @api public
113
114Field.prototype.unique = function (bool) {
115 //this._index.unique = bool;
116 return this;
117};
118 */
119
120/**
121 * Adds a setter to this fieldtype.
122 *
123 * ####Example:
124 *
125 * function capitalize (val) {
126 * if ('string' != typeof val) val = '';
127 * return val.charAt(0).toUpperCase() + val.substring(1);
128 * }
129 *
130 * // defining within the field
131 * var s = new Field({ name: { type: String, set: capitalize }})
132 *
133 * // or by retreiving its Field
134 * var s = new Field({ name: String })
135 * s.path('name').set(capitalize)
136 *
137 * Setters allow you to transform the data before it gets to the raw mongodb document and is set as a value on an actual key.
138 *
139 * Suppose you are implementing user registration for a website. Users provide an email and password, which gets saved to mongodb. The email is a string that you will want to normalize to lower case, in order to avoid one email having more than one account -- e.g., otherwise, avenue@q.com can be registered for 2 accounts via avenue@q.com and AvEnUe@Q.CoM.
140 *
141 * You can set up email lower case normalization easily via a Mongoose setter.
142 *
143 * function toLower (v) {
144 * return v.toLowerCase();
145 * }
146 *
147 * var UserField = new Field({
148 * email: { type: String, set: toLower }
149 * })
150 *
151 * var User = db.model('User', UserField)
152 *
153 * var user = new User({email: 'AVENUE@Q.COM'})
154 * console.log(user.email); // 'avenue@q.com'
155 *
156 * // or
157 * var user = new User
158 * user.email = 'Avenue@Q.com'
159 * console.log(user.email) // 'avenue@q.com'
160 *
161 * As you can see above, setters allow you to transform the data before it gets to the raw mongodb document and is set as a value on an actual key.
162 *
163 * _NOTE: we could have also just used the built-in `lowercase: true` Field option instead of defining our own function._
164 *
165 * new Field({ email: { type: String, lowercase: true }})
166 *
167 * Setters are also passed a second argument, the fieldtype on which the setter was defined. This allows for tailored behavior based on options passed in the field.
168 *
169 * function inspector (val, fieldtype) {
170 * if (fieldtype.options.required) {
171 * return fieldtype.path + ' is required';
172 * } else {
173 * return val;
174 * }
175 * }
176 *
177 * var VirusField = new Field({
178 * name: { type: String, required: true, set: inspector },
179 * taxonomy: { type: String, set: inspector }
180 * })
181 *
182 * var Virus = db.model('Virus', VirusField);
183 * var v = new Virus({ name: 'Parvoviridae', taxonomy: 'Parvovirinae' });
184 *
185 * console.log(v.name); // name is required
186 * console.log(v.taxonomy); // Parvovirinae
187 *
188 * @param {Function} fn
189 * @return {Field} this
190 * @api public
191 */
192
1931Field.prototype.set = function (fn) {
19410 if ('function' != typeof fn)
1951 throw new TypeError('A setter must be a function.');
1969 this.setters.push(fn);
1979 return this;
198};
199
200/**
201 * Adds a getter to this fieldtype.
202 *
203 * ####Example:
204 *
205 * function dob (val) {
206 * if (!val) return val;
207 * return (val.getMonth() + 1) + "/" + val.getDate() + "/" + val.getFullYear();
208 * }
209 *
210 * // defining within the field
211 * var s = new Field({ born: { type: Date, get: dob })
212 *
213 * // or by retreiving its Field
214 * var s = new Field({ born: Date })
215 * s.path('born').get(dob)
216 *
217 * Getters allow you to transform the representation of the data as it travels from the raw mongodb document to the value that you see.
218 *
219 * 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:
220 *
221 * function obfuscate (cc) {
222 * return '****-****-****-' + cc.slice(cc.length-4, cc.length);
223 * }
224 *
225 * var AccountField = new Field({
226 * creditCardNumber: { type: String, get: obfuscate }
227 * });
228 *
229 * var Account = db.model('Account', AccountField);
230 *
231 * Account.findById(id, function (err, found) {
232 * console.log(found.creditCardNumber); // '****-****-****-1234'
233 * });
234 *
235 * Getters are also passed a second argument, the fieldtype on which the getter was defined. This allows for tailored behavior based on options passed in the field.
236 *
237 * function inspector (val, fieldtype) {
238 * if (fieldtype.options.required) {
239 * return fieldtype.path + ' is required';
240 * } else {
241 * return fieldtype.path + ' is not';
242 * }
243 * }
244 *
245 * var VirusField = new Field({
246 * name: { type: String, required: true, get: inspector },
247 * taxonomy: { type: String, get: inspector }
248 * })
249 *
250 * var Virus = db.model('Virus', VirusField);
251 *
252 * Virus.findById(id, function (err, virus) {
253 * console.log(virus.name); // name is required
254 * console.log(virus.taxonomy); // taxonomy is not
255 * })
256 *
257 * @param {Function} fn
258 * @return {Field} this
259 * @api public
260 */
261
2621Field.prototype.get = function (fn) {
2634 if ('function' != typeof fn)
2641 throw new TypeError('A getter must be a function.');
2653 this.getters.push(fn);
2663 return this;
267};
268
269/**
270 * Adds validator(s) for this document path.
271 *
272 * Validators always receive the value to validate as their first argument and must return `Boolean`. Returning false is interpreted as validation failure.
273 *
274 * ####Examples:
275 *
276 * function validator (val) {
277 * return val == 'something';
278 * }
279 *
280 * new Field({ name: { type: String, validate: validator }});
281 *
282 * // with a custom error message
283 *
284 * var custom = [validator, 'validation failed']
285 * new Field({ name: { type: String, validate: custom }});
286 *
287 * var many = [
288 * { validator: validator, msg: 'uh oh' }
289 * , { validator: fn, msg: 'failed' }
290 * ]
291 * new Field({ name: { type: String, validate: many }});
292 *
293 * // or utilizing Field methods directly:
294 *
295 * var field = new Field({ name: 'string' });
296 * field.path('name').validate(validator, 'validation failed');
297 *
298 * ####Asynchronous validation:
299 *
300 * Passing a validator function that receives two arguments tells mongoose that the validator is an asynchronous validator. The second argument is an callback function that must be passed either `true` or `false` when validation is complete.
301 *
302 * field.path('name').validate(function (value, respond) {
303 * doStuff(value, function () {
304 * ...
305 * respond(false); // validation failed
306 * })
307* }, 'my error type');
308*
309 * You might use asynchronous validators to retreive other documents from the database to validate against or to meet other I/O bound validation needs.
310 *
311 * Validation occurs `pre('save')` or whenever you manually execute [document#validate](#document_Document-validate).
312 *
313 * 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.
314 *
315 * var conn = mongoose.createConnection(..);
316 * conn.on('error', handleError);
317 *
318 * var Product = conn.model('Product', yourField);
319 * var dvd = new Product(..);
320 * dvd.save(); // emits error on the `conn` above
321 *
322 * If you desire handling these errors at the Model level, attach an `error` listener to your Model and the event will instead be emitted there.
323 *
324 * // registering an error listener on the Model lets us handle errors more locally
325 * Product.on('error', handleError);
326 *
327 * @param {RegExp|Function|Object} obj validator
328 * @param {String} [error] optional error message
329 * @api public
330 */
331
3321Field.prototype.validate = function (obj, error) {
3336 if ('function' == typeof obj || obj && 'RegExp' === obj.constructor.name) {
3344 this.validators.push([obj, error]);
3354 return this;
336 }
337
3382 var i = arguments.length,
339 arg;
340
3412 while (i--) {
3422 arg = arguments[i];
3432 if (!(arg && 'Object' == arg.constructor.name)) {
3441 var msg = 'Invalid validator. Received (' + typeof arg + ') ' +
345 arg +
346 '. See http://mongoosejs.com/docs/api.html#schematype_SchemaType-validate';
347
3481 throw new Error(msg);
349 }
3501 this.validate(arg.validator, arg.msg);
351 }
352
3531 return this;
354};
355
356/**
357 * Adds a required validator to this fieldtype.
358 *
359 * ####Example:
360 *
361 * var s = new Field({ born: { type: Date, required: true })
362 * // or
363 * form.path('name').required(true);
364 *
365 *
366 * @param {Boolean} required enable/disable the validator
367 * @return {Field} this
368 * @api public
369 */
370
3711Field.prototype.required = function (required) {
37210 var self = this;
373
37410 function __checkRequired (v) {
3759 return self.checkRequired(v, this);
376 }
377
37810 if (false === required) {
3792 delete this.options.required;
3802 this.validators = this.validators.filter(function (v) {
3812 return v[0].name !== '__checkRequired';
382 });
383 } else {
3848 this.options.required = true;
3858 this.validators.push([__checkRequired, 'required']);
386 }
387
38810 return this;
389};
390
391/**
392 * Gets the default value
393 *
394 * @param {Object} scope the scope which callback are executed
395 * @param {Boolean} init
396 * @api private
397 */
398
3991Field.prototype.getDefault = function (scope, init) {
40085 var ret = 'function' === typeof this.defaultValue ?
401 this.defaultValue.call(scope) :
402 this.defaultValue;
403
40485 if (null !== ret && undefined !== ret) {
40511 return this.cast(ret, scope, init);
406 } else {
40774 return ret;
408 }
409};
410
411/**
412 * Applies setters
413 *
414 * @param {Object} value
415 * @param {Object} scope
416 * @param {Boolean} init
417 * @api private
418 */
419
4201Field.prototype.applySetters = function (value, scope, init, priorVal) {
42162 var v = value,
422 setters = this.setters,
423 len = setters.length;
424
42562 if (!len) {
42656 if (null === v || undefined === v) return v;
42756 return this.cast(v, scope, init, priorVal);
428 }
429
4306 while (len--) {
4317 v = setters[len].call(scope, v, this);
432 }
433
4346 if (null === v || undefined === v) return v;
435
436 // do not cast until all setters are applied
4376 v = this.cast(v, scope, init, priorVal);
438
4396 return v;
440};
441
442/**
443 * Applies getters to a value
444 *
445 * @param {Object} value
446 * @param {Object} scope
447 * @api private
448 */
449
4501Field.prototype.applyGetters = function (value, scope) {
45161 var v = value,
452 getters = this.getters,
453 len = getters.length;
454
45561 if (!len) {
45657 return v;
457 }
458
4594 while (len--) {
4604 v = getters[len].call(scope, v, this);
461 }
462
4634 return v;
464};
465
466/**
467 * Performs a validation of `value` using the validators declared for this Field.
468 *
469 * @param {any} value
470 * @param {Function} callback
471 * @param {Object} scope
472 * @api private
473 */
474
4751Field.prototype.doValidate = function (value, fn, scope) {
47644 var err = false,
477 path = this.path,
478 count = this.validators.length;
479
48058 if (!count) return fn(null);
481
48230 function validate (ok, msg, val) {
48331 if (err) return;
48431 if (ok === undefined || ok) {
48523 if (!--count) fn(null);
486 } else {
48719 fn(err = new ValidatorError(path, msg, val));
488 }
489 }
490
49130 this.validators.forEach(function (v) {
49231 var validator = v[0],
493 message = v[1];
494
49531 if (validator instanceof RegExp) {
4963 validate(validator.test(value), message, value);
49728 } else if ('function' === typeof validator) {
49828 if (2 === validator.length) {
4992 validator.call(scope, value, function (ok) {
5002 validate(ok, message, value);
501 });
502 } else {
50326 validate(validator.call(scope, value), message, value);
504 }
505 }
506 });
507};
508
509/**
510 * Return field options
511 *
512 * @return {Object} options
513 * @api public
514 */
5151Field.prototype.export = function () {
51615 var attr = this.options.attributes || (this.options.attributes = {});
517
51815 if (!this.options.required && attr.required)
5190 delete attr.required;
520
52115 if (this.options.required)
5224 attr.required = null; // <input name="toto" required/>
523
52415 return this.options;
525};
526
527/*!
528 * Module exports.
529 */
530
5311module.exports = exports = Field;
532
5331exports.CastError = CastError;
534
5351exports.ValidatorError = ValidatorError;
536

/Users/nrako/sandbox/formal/lib/field/number.js

96%
51
49
2
LineHitsSource
1/* jshint -W053 */
2/*!
3 * Module requirements.
4 */
5
61var Field = require('./field'),
7 util = require('util'),
8 CastError = Field.CastError,
9 Document;
10
11/**
12 * Number Field constructor.
13 *
14 * @param {String} key
15 * @param {Object} options
16 * @inherits Field
17 * @api private
18 */
19
201function FieldNumber (key, options) {
2120 Field.call(this, key, options, 'Number');
22}
23
24/*!
25 * Inherits from Field.
26 */
27
281util.inherits(FieldNumber, Field);
29
30/**
31 * Required validator for number
32 *
33 * @api private
34 */
35
361FieldNumber.prototype.checkRequired = function checkRequired (value, doc) {
371 return typeof value == 'number' || value instanceof Number;
38};
39
40/**
41 * Sets a minimum number validator.
42 *
43 * ####Example:
44 *
45 * var form = new Form({
46 * age: {
47 * type: Number,
48 * min: 18
49 * }
50 * });
51 *
52 * form.set('age', 17);
53 *
54 * form.validate(console.log);
55 *
56 * @param {Number} value minimum number
57 * @param {String} message
58 * @api public
59 */
60
611FieldNumber.prototype.min = function (value, message) {
625 if (this.minValidator) {
631 delete this.options.min;
641 this.validators = this.validators.filter(function(v){
651 return v[1] != 'min';
66 });
67 }
685 if (value != null) {
694 this.validators.push([this.minValidator = function(v){
705 return v === null || v >= value;
71 }, 'min']);
724 this.options.min = value;
73 }
745 return this;
75};
76
77/**
78 * Sets a maximum number validator.
79 *
80 * ####Example:
81 *
82 * var form = new Form({
83 * price: Number
84 * });
85 *
86 * form.path('price').max(100000000, 'Price is too high, well... really?');
87 *
88 * form.validate(console.log);
89 *
90 * @param {Number} maximum number
91 * @param {String} message
92 * @api public
93 */
94
951FieldNumber.prototype.max = function (value, message) {
964 if (this.maxValidator) {
971 this.maxValue = null;
981 this.validators = this.validators.filter(function(v){
991 return v[1] != 'max';
100 });
101 }
1024 if (value != null) {
1034 this.validators.push([this.maxValidator = function(v){
1044 return v === null || v <= value;
105 }, 'max']);
1064 this.maxValue = value;
107 }
1084 return this;
109};
110
111/**
112 * Casts to number
113 *
114 * @param {Object} value value to cast
115 * @param {Document} doc document that triggers the casting
116 * @param {Boolean} init
117 * @api private
118 */
119
1201FieldNumber.prototype.cast = function (value, doc, init) {
12119 var val = value;
122
12319 if (!isNaN(val)){
12419 if (null === val) return val;
12519 if ('' === val) return null;
12621 if ('string' == typeof val) val = Number(val);
12719 if (val instanceof Number) return val;
12836 if ('number' == typeof val) return val;
1292 if (val.toString && !Array.isArray(val) &&
130 val.toString() == Number(val)) {
1311 return new Number(val);
132 }
133 }
134
1351 throw new CastError('number', value, this.path);
136};
137
138/**
139 * Export
140 * @return {[type]} [description]
141 */
1421FieldNumber.prototype.export = function() {
1433 FieldNumber.super_.prototype.export.call(this);
144
1453 var attr = this.options.attributes || (this.options.attributes = {});
146
1473 attr.type = 'number';
148
1493 if (!this.options.min && attr.min)
1500 delete attr.min;
1513 if (this.options.min)
1522 attr.min = this.options.min;
153
1543 if (!this.options.max && attr.max)
1550 delete attr.max;
156
1573 if (this.options.max)
1582 attr.max = this.options.max;
159
1603 return this.options;
161};
162
163/*!
164 * Module exports.
165 */
166
1671module.exports = FieldNumber;
168

/Users/nrako/sandbox/formal/lib/field/string.js

94%
59
56
3
LineHitsSource
1/* jshint bitwise: false */
2/*!
3 * Module dependencies.
4 */
5
61var Field = require('./field'),
7 util = require('util'),
8 CastError = Field.CastError,
9 Document;
10
11/**
12 * String Field constructor.
13 *
14 * @param {String} key
15 * @param {Object} options
16 * @inherits Field
17 * @api private
18 */
19
201function FieldString (key, options) {
2143 this.enumValues = [];
2243 this.regExp = null;
2343 Field.call(this, key, options, 'String');
24}
25
26/*!
27 * Inherits from Field.
28 */
29
301util.inherits(FieldString, Field);
31
32/**
33 * Adds enumeration values and a coinciding validator.
34 *
35 * ####Example:
36 *
37 * var states = 'opening open closing closed'.split(' ')
38 * var f = new Form({ state: { type: String, enum: states })
39 * app.post('/url', f, function (req, res) {
40 * if (!req.form.isValid())
41 * console.error(req.form.errors);
42 * });
43 *
44 * @param {String} [args...] enumeration values
45 * @api public
46 */
47
481FieldString.prototype.enum = function () {
494 var len = arguments.length;
504 if (!len || undefined === arguments[0] || false === arguments[0]) {
511 if (this.enumValidator){
521 this.enumValidator = false;
531 this.validators = this.validators.filter(function(v){
541 return v[1] != 'enum';
55 });
56 }
571 return;
58 }
59
603 for (var i = 0; i < len; i++) {
618 if (undefined !== arguments[i]) {
628 this.enumValues.push(this.cast(arguments[i]));
63 }
64 }
65
663 if (!this.enumValidator) {
673 var values = this.enumValues;
683 this.enumValidator = function(v){
694 return undefined === v || ~values.indexOf(v);
70 };
713 this.validators.push([this.enumValidator, 'enum']);
72 }
73};
74
75/**
76 * Adds a lowercase setter.
77 *
78 * ####Example:
79 *
80 * var s = new FieldString({ email: { type: String, lowercase: true }})
81 * var M = db.model('M', s);
82 * var m = new M({ email: 'SomeEmail@example.COM' });
83 * console.log(m.email) // someemail@example.com
84 *
85 * @api public
86 */
87
881FieldString.prototype.lowercase = function () {
891 return this.set(function (v, self) {
901 if ('string' != typeof v) v = self.cast(v);
912 if (v) return v.toLowerCase();
920 return v;
93 });
94};
95
96/**
97 * Adds an uppercase setter.
98 *
99 * ####Example:
100 *
101 * var s = new FieldString({ caps: { type: String, uppercase: true }})
102 * var M = db.model('M', s);
103 * var m = new M({ caps: 'an example' });
104 * console.log(m.caps) // AN EXAMPLE
105 *
106 * @api public
107 */
108
1091FieldString.prototype.uppercase = function () {
1102 return this.set(function (v, self) {
1111 if ('string' != typeof v) v = self.cast(v);
1122 if (v) return v.toUpperCase();
1130 return v;
114 });
115};
116
117/**
118 * Adds a trim setter.
119 *
120 * The string value will be trimmed when set.
121 *
122 * ####Example:
123 *
124 * var s = new FieldString({ name: { type: String, trim: true }})
125 * var M = db.model('M', s)
126 * var string = ' some name '
127 * console.log(string.length) // 11
128 * var m = new M({ name: string })
129 * console.log(m.name.length) // 9
130 *
131 * @api public
132 */
133
1341FieldString.prototype.trim = function () {
1354 return this.set(function (v, self) {
1363 if ('string' != typeof v) v = self.cast(v);
1375 if (v) return v.trim();
1381 return v;
139 });
140};
141
142/**
143 * Sets a regexp validator.
144 *
145 * Any value that does not pass `regExp`.test(val) will fail validation.
146 *
147 * ####Example:
148 *
149 * var s = new FieldString({ name: { type: String, match: /^a/ }})
150 * var M = db.model('M', s)
151 * var m = new M({ name: 'invalid' })
152 * m.validate(function (err) {
153 * console.error(err) // validation error
154 * m.name = 'apples'
155 * m.validate(function (err) {
156 * assert.ok(err) // success
157 * })
158 * })
159 *
160 * @param {RegExp} regExp regular expression to test against
161 * @api public
162 */
163
1641FieldString.prototype.match = function match (regExp) {
1651 this.validators.push([function(v){
1662 return null != v && '' !== v ? regExp.test(v) : true;
167 }, 'regexp']);
168};
169
170/**
171 * Check required
172 *
173 * @param {String|null|undefined} value
174 * @api private
175 */
176
1771FieldString.prototype.checkRequired = function checkRequired (value, doc) {
1784 return (value instanceof String || typeof value == 'string') && value.length;
179};
180
181/**
182 * Casts to String
183 *
184 * @api private
185 */
186
1871FieldString.prototype.cast = function (value, doc, init) {
18847 if (value === null) {
1891 return value;
190 }
191
19246 if ('undefined' !== typeof value) {
19346 if (value.toString) {
19445 return value.toString();
195 }
196 }
197
198
1991 throw new CastError('string', value, this.path);
200};
201
2021FieldString.prototype.export = function() {
2039 FieldString.super_.prototype.export.call(this);
204
2059 var attr = this.options.attributes || (this.options.attributes = {});
206
2079 if (this.enumValues.length) {
2081 if (this.enumValues.length === 2 && this.isRequired)
2090 attr.type = 'radio';
210 else
2111 attr.type = 'checkbox';
212 } else {
2138 attr.type = 'text';
214 }
215
2169 return this.options;
217};
218
219/*!
220 * Module exports.
221 */
222
2231module.exports = FieldString;
224

/Users/nrako/sandbox/formal/lib/field/types.js

100%
6
6
0
LineHitsSource
1
2/*!
3 * Module exports.
4 */
5
61exports.String = require('./string');
7
81exports.Number = require('./number');
9
101exports.Boolean = require('./boolean');
11
12//exports.FormArray = require('./formarray');
13
141exports.Array = require('./array');
15
16//exports.Buffer = require('./buffer');
17
181exports.Date = require('./date');
19
201exports.Virtual = require('./virtualtype');
21
22// alias
23
24//exports.Bool = exports.Boolean;
25

/Users/nrako/sandbox/formal/lib/field/virtualtype.js

100%
23
23
0
LineHitsSource
1
2/**
3 * VirtualType constructor
4 *
5 * This is what Formal uses to define virtual attributes via `Form.prototype.virtual`.
6 *
7 * ####Example:
8 *
9 * var fullname = form.virtual('fullname').get(function() {
10 * return this.name.first + ' ' this.name.family;
11 * });
12 *
13 * @parma {Object} options
14 * @api public
15 */
16
171function VirtualType (options, name, scope) {
182 this.path = name;
192 this.scope = scope;
202 this.getters = [];
212 this.setters = [];
222 this.options = options || {};
23}
24
25/**
26 * Defines a getter.
27 *
28 * ####Example:
29 *
30 * var virtual = form.virtual('fullname');
31 * virtual.get(function () {
32 * return this.name.first + ' ' + this.name.last;
33 * });
34 *
35 * @param {Function} fn
36 * @return {VirtualType} this
37 * @api public
38 */
39
401VirtualType.prototype.get = function (fn) {
411 this.getters.push(fn.bind(this.scope));
421 return this;
43};
44
45/**
46 * Defines a setter.
47 *
48 * ####Example:
49 *
50 * var virtual = form.virtual('fullname');
51 * virtual.set(function (v) {
52 * var parts = v.split(' ');
53 * this.name.first = parts[0];
54 * this.name.last = parts[1];
55 * });
56 *
57 * @param {Function} fn
58 * @return {VirtualType} this
59 * @api public
60 */
61
621VirtualType.prototype.set = function (fn) {
631 this.setters.push(fn.bind(this.scope));
641 return this;
65};
66
67/**
68 * Applies getters to `value` using optional `scope`.
69 *
70 * @param {Object} value
71 * @param {Object} scope
72 * @return {any} the value after applying all getters
73 * @api public
74 */
75
761VirtualType.prototype.applyGetters = function (value, scope) {
771 var v = value;
781 for (var l = this.getters.length - 1; l >= 0; l--) {
791 v = this.getters[l].call(scope, v, this);
80 }
811 return v;
82};
83
84/**
85 * Applies setters to `value` using optional `scope`.
86 *
87 * @param {Object} value
88 * @param {Object} scope
89 * @return {any} the value after applying all setters
90 * @api public
91 */
92
931VirtualType.prototype.applySetters = function (value, scope) {
941 var v = value;
951 for (var l = this.setters.length - 1; l >= 0; l--) {
961 v = this.setters[l].call(scope, v, this);
97 }
981 return v;
99};
100
101/*!
102 * exports
103 */
104
1051module.exports = VirtualType;
106

/Users/nrako/sandbox/formal/lib/form.js

89%
264
235
29
LineHitsSource
1/*!
2 * Module dependencies.
3 */
4
51var EventEmitter = require('events').EventEmitter,
6 ValidatorError = require('./field/field').ValidatorError,
7 ValidationError = require('./errors/validation'),
8 mpath = require('mpath'),
9 util = require('util'),
10 _ = require('lodash'),
11 Types;
12
13/**
14 * Form constructor.
15 *
16 * ####Example:
17 *
18 * var form = new Form({
19 * email: {
20 * type: String,
21 * attributes: {type='email'}
22 * },
23 * name: {
24 * first: String,
25 * family: String
26 * }
27 * }, options);
28 *
29 * ####Options:
30 *
31 * {
32 * dataSources: ['body', 'query', 'params'],
33 * autoTrim: false, // automatically add trim options to fields
34 * autoLocals: true, // form.middleware adds form.export + validation results to res.locals
35 * errors: {
36 * required: 'This is a required field',
37 * min: 'Value must be greater than or equal to <%= data.min %>',
38 * max: 'Value must be less than or equal to <%= data.max %>',
39 * }
40 * }
41 *
42 * ####Note:
43 *
44 * Provides an alternative factory returning a route-middleware for express and connect
45 *
46 * app.post('/url',
47 * // sames as (new Form({...})).middleware()
48 * form({
49 * fieldA: String
50 * }),
51 * function (req, res) {
52 * console.log(req.form); // instanceof Form
53 * console.log(res.locals.form.fieldA); // {value: '...', error: '...', data: {...}}
54 * }
55 * );
56 *
57 * @param {Object} definition
58 * @param {Object} options
59 * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter
60 * @api public
61 */
62
631function Form (obj, options) {
6431 if (!(this instanceof Form))
651 return (new Form(obj, options)).middleware();
66
6730 this.data = {};
6830 this.paths = {};
6930 this.subpaths = {};
7030 this.virtuals = {};
7130 this.nested = {};
7230 this.tree = {};
7330 this._requiredpaths = undefined;
74
7530 this.options = this.defaultOptions(options);
76
77 // build paths
7830 if (obj) {
7930 this.field(obj);
80 }
81
8230 EventEmitter.call(this);
83}
84
85/*!
86 * Inherit from EventEmitter.
87 */
88
891util.inherits(Form, EventEmitter);
90
91/**
92 * Form as flat paths
93 *
94 * ####Example:
95 * {
96 * 'something' : Field,
97 * , 'nested.key' : Field,
98 * }
99 *
100 * @api private
101 * @property paths
102 */
103
1041Form.prototype.paths = {};
105
106/**
107 * Form as a tree
108 *
109 * ####Example:
110 * {
111 * 'nested' : {
112 * 'key' : String
113 * }
114 * }
115 *
116 * @api private
117 * @property tree
118 */
119
1201Form.prototype.tree = {};
121
122
123/**
124 * Returns default options for this form, merged with `options`.
125 *
126 * @param {Object} options
127 * @return {Object}
128 * @api private
129 */
130
1311Form.prototype.defaultOptions = function (options) {
13230 options = _.merge({
133 dataSources: ['body', 'query', 'params'],
134 autoTrim: false,
135 autoLocals: true,
136 passThrough: false,
137 errors: {
138 required: 'This is a required field',
139 min: 'Value must be greater than or equal to <%= data.min %>',
140 max: 'Value must be less than or equal to <%= data.max %>',
141 }
142 }, options);
143
14430 return options;
145};
146
147/**
148 * Adds key path / field type pairs to this form.
149 *
150 * ####Example:
151 *
152 * var ToyForm = new Form;
153 * ToyForm.add({ name: 'string', color: 'string', price: 'number' });
154 * // alias
155 * ToyForm.field(...)
156 *
157 * @param {Object} obj
158 * @param {String} prefix
159 * @api public
160 */
161
1621Form.prototype.field = Form.prototype.add = function add (obj, prefix) {
16340 prefix = prefix || '';
16440 for (var i in obj) {
16593 if (null === obj[i]) {
1661 throw new TypeError('Invalid value for field path `'+ prefix + i +'`');
167 }
168
16992 if (obj[i].constructor.name == 'Object' && (!obj[i].type || obj[i].type.type)) {
1706 if (Object.keys(obj[i]).length) {
171 // nested object { last: { name: String }}
1726 this.nested[prefix + i] = true;
1736 this.add(obj[i], prefix + i + '.');
174 } else {
1750 this.path(prefix + i, obj[i]); // mixed type
176 }
177 } else {
17886 this.path(prefix + i, obj[i]);
179 }
180 }
181};
182
183/**
184 * Gets/sets form paths.
185 *
186 * Sets a path (if arity 2)
187 * Gets a path (if arity 1)
188 *
189 * ####Example
190 *
191 * form.path('name') // returns a Field
192 * form.path('name', Number) // changes the fieldType of `name` to Number
193 *
194 * @param {String} path
195 * @param {Object} constructor
196 * @api public
197 */
198
1991Form.prototype.path = function (path, obj) {
200298 var self = this,
201 field;
202
203298 if (obj === undefined) {
204417 if (this.paths[path]) return this.paths[path];
20510 if (this.subpaths[path]) return this.subpaths[path];
206
207 // subpaths?
2084 return (/\.\d+\.?.*$/).test(path) ? getPositionalPath(this, path) : undefined;
209 }
210
211 // update the tree
21286 var subpaths = path.split(/\./),
213 last = subpaths.pop(),
214 branch = this.tree;
215
21686 subpaths.forEach(function(sub, i) {
21716 if (!branch[sub]) branch[sub] = {};
21810 if (typeof branch[sub] != 'object' ) {
2190 var msg = 'Cannot set nested path `' +
220 path + '`. ' + 'Parent path `' +
221 subpaths.slice(0, i).concat([sub]).join('.') +
222 '` already set to type ' +
223 branch[sub].name + '.';
2240 throw new Error(msg);
225 }
22610 branch = branch[sub];
227 });
228
22986 branch[last] = _.clone(obj, true);
230
23186 field = this.interpretAsType(path, obj);
232
23385 this.paths[path] = field;
234
235 // set default value
23685 this.setValue(path, field.getDefault());
237
238 // when default() method is used on field
23985 field.on('default', function (def) {
2401 var val = self.getValue(path);
2411 if (!val || Array.isArray(val) && val.length === 0)
2421 self.setValue(path, def);
243 });
244
24585 return this;
246};
247
248/**
249 * Converts type arguments into Formal Types.
250 *
251 * @param {String} path
252 * @param {Object} obj constructor
253 * @api private
254 */
255
2561Form.prototype.interpretAsType = function (path, obj) {
25786 if (obj.constructor.name != 'Object')
25855 obj = { type: obj };
259
26086 if (this.options.autoTrim)
2612 obj.trim = true;
262
263 // Get the type making sure to allow keys named "type"
264 // and default to mixed if not specified.
265 // { type: { type: String, default: 'freshcut' } }
26686 var type = obj.type && !obj.type.type ? obj.type : {};
267
26886 if (Array.isArray(type) || Array == type || 'array' == type) {
269 // if it was specified through { type } look for `cast`
27010 var cast = (Array == type || 'array' == type) ? obj.cast : type[0];
271
27210 if (cast instanceof Form) {
2730 return new Types.FormArray(path, cast, obj);
274 }
275
27610 if ('string' == typeof cast) {
2770 cast = Types[cast.charAt(0).toUpperCase() + cast.substring(1)];
27810 } else if (cast && (!cast.type || cast.type.type) &&
279 'Object' == cast.constructor.name &&
280 Object.keys(cast).length) {
281
2820 return new Types.FormArray(path, new Form(cast), obj);
283 }
284
28510 return new Types.Array(path, cast, obj);
286 }
287
28876 var name = 'string' == typeof type ? type : type.name;
289
29076 if (name) {
29176 name = name.charAt(0).toUpperCase() + name.substring(1);
292 }
293
29476 if (undefined === Types[name]) {
2951 throw new TypeError('Undefined type at `' + path +
296 '`\n Did you try nesting Forms? ' +
297 'You can only nest using refs or arrays.');
298 }
299
30075 return new Types[name](path, obj);
301};
302
303
304/**
305 * Returns an Array of path strings that are required by this form.
306 *
307 * @api public
308 * @return {Array}
309 */
310
3111Form.prototype.requiredPaths = function requiredPaths () {
3121 if (this._requiredpaths) return this._requiredpaths;
313
3141 var ret = [];
315
3161 _.each(this.paths, function(field) {
3178 if (field.options.required) ret.push(field.path);
318 });
319
3201 this._requiredpaths = ret;
3211 return this._requiredpaths;
322};
323
324/**
325 * Returns the pathType of `path` for this form.
326 *
327 * Given a path, returns whether it is a real, virtual, nested, or ad-hoc/undefined path.
328 *
329 * @param {String} path
330 * @return {String}
331 * @api public
332 */
333
3341Form.prototype.pathType = function (path) {
335265 if (path in this.paths) return 'real';
33611 if (path in this.virtuals) return 'virtual';
3379 if (path in this.nested) return 'nested';
3387 if (path in this.subpaths) return 'real';
339
3403 if (/\.\d+\.|\.\d+$/.test(path) && getPositionalPath(this, path)) {
3411 return 'real';
342 } else {
3432 return 'adhocOrUndefined';
344 }
345};
346
347/*!
348 * ignore
349 */
350
3511function getPositionalPath (self, path) {
3522 var subpaths = path.split(/\.(\d+)\.|\.(\d+)$/).filter(Boolean);
3532 if (subpaths.length < 2) {
3540 return self.paths[subpaths[0]];
355 }
356
3572 var val = self.path(subpaths[0]);
3582 if (!val) return val;
359
3602 var last = subpaths.length - 1,
361 subpath, i = 1;
362
3632 for (; i < subpaths.length; ++i) {
3642 subpath = subpaths[i];
365
3662 if (i === last && val && !val.form && !/\D/.test(subpath)) {
3672 if (val instanceof Types.Array) {
368 // StringField, NumberField, etc
3692 val = val.caster;
370 } else {
3710 val = undefined;
372 }
3732 break;
374 }
375
376 // ignore if its just a position segment: path.0.subpath
3770 if (!/\D/.test(subpath)) continue;
378
3790 if (!(val && val.form)) {
3800 val = undefined;
3810 break;
382 }
383
3840 val = val.form.path(subpath);
385 }
386
3872 return self.subpaths[path] = val;
388}
389
390/**!
391 * ignore
392 * Registers a plugin for this form.
393 *
394 * @param {Function} plugin callback
395 * @param {Object} opts
396 * @see plugins
397 * @api public
398 */
399
4001Form.prototype.plugin = function (fn, opts) {
4010 fn(this, opts);
4020 return this;
403};
404
405/**
406 * Sets/gets a form option.
407 *
408 * Sets an option (if arity 2)
409 * Gets an option (if arity 1)
410 *
411 * @param {String} key option name
412 * @param {Object} [value] if not passed, the current option value is returned
413 * @api public
414 */
415
4161Form.prototype.option = function (key, value) {
4174 if (1 === arguments.length) {
4182 return this.options[key];
419 }
420
4212 if (this.options[key] && typeof this.options[key] === 'object')
4221 this.options[key] = _.merge(this.options[key], value);
423 else
4241 this.options[key] = value;
425
4262 return this;
427};
428
429/**
430 * Creates a virtual type with the given name.
431 *
432 * @param {String} name
433 * @param {Object} [options]
434 * @return {VirtualType}
435 */
436
4371Form.prototype.virtual = function(name, options) {
4382 var self = this;
4392 var virtuals = this.virtuals;
4402 var parts = name.split('.');
4412 return virtuals[name] = parts.reduce(
442 function(mem, part, i) {
4434 if (!mem[part]) mem[part] = (i === parts.length - 1) ? new Types.Virtual(options, name, self.data) : {};
4442 return mem[part];
445 },
446 this.tree
447 );
448};
449
450/**
451 * Returns the virtual type with the given `name`.
452 *
453 * @param {String} name
454 * @return {VirtualType}
455 */
456
4571Form.prototype.virtualpath = function (name) {
4583 return this.virtuals[name];
459};
460
461/**
462 * Sets the value of a path, or many paths.
463 *
464 * ####Example:
465 *
466 * // path, value
467 * form.set(path, value);
468 *
469 * // object
470 * form.set({
471 * path : value,
472 * path2 : {
473 * path : value
474 * }
475 * });
476 *
477 * // only-the-fly cast to number
478 * form.set(path, value, Number)
479 *
480 * // only-the-fly cast to string
481 * form.set(path, value, String)
482 *
483 * @param {String|Object} path path or object of key/vals to set
484 * @param {Any} val the value to set
485 * @param {Schema|String|Number|Buffer|etc..} [type] optionally specify a type for "on-the-fly" attributes
486 * @param {Object} [options] optionally specify options that modify the behavior of the set
487 * @api public
488 */
489
4901Form.prototype.set = function (path, val, type, options) {
491102 if (type && 'Object' == type.constructor.name) {
4920 options = type;
4930 type = undefined;
494 }
495
496102 var merge = options && options.merge,
497 constructing = true === type;
498
499102 if (typeof path !== 'string') {
50035 if (null === path || undefined === path) {
5010 var v = path;
5020 path = val;
5030 val = v;
504
505 } else {
50635 var prefix = val ? val + '.' : '';
507
50835 if (path instanceof Form)
5090 path = path.data;
510
51135 var keys = Object.keys(path),
512 i = keys.length,
513 pathtype, key;
514
51535 while (i--) {
51670 key = keys[i];
51770 pathtype = this.pathType(prefix + key);
51870 if (path[key] && 'Object' === path[key].constructor.name && 'virtual' !== pathtype) {
5193 this.set(path[key], prefix + key, constructing);
52067 } else if (undefined !== path[key] && 'real' === pathtype || 'virtual' === pathtype) {
52165 this.set(prefix + key, path[key], constructing);
522 }
523 }
524
52535 return this;
526 }
527 }
528
529 // form = new Form({ path: { nest: 'string' }})
530 // form.set('path', obj);
53167 var pathType = this.pathType(path);
53267 if ('nested' == pathType && val && 'Object' == val.constructor.name) {
5330 if (!merge) this.setValue(path, null);
5340 this.set(val, path, constructing);
5350 return this;
536 }
537
53867 var field;
53967 var parts = path.split('.');
540
54167 if ('virtual' == pathType) {
5421 field = this.virtualpath(path);
5431 field.applySetters(val, this);
5441 return this;
545 } else {
54666 field = this.path(path);
547 }
548
54966 if (!field || null === val || undefined === val) {
5504 _set(this.data, parts, val);
5514 return this;
552 }
553
55462 var shouldSet = true;
55562 try {
55662 val = field.applySetters(val, this, false, this.getValue(path));
557 } catch (err) {
5582 shouldSet = false;
559 // casting error put on silence
560 //this.invalidate(path, err, val);
561 }
562
56362 if (shouldSet) {
56460 _set(this.data, parts, val);
565 }
566
56762 return this;
568};
569
570/**
571 * Returns the value of a path.
572 *
573 * ####Example
574 *
575 * // path
576 * form.get('author.age') // 47
577 *
578 * @param {String} path
579 * @api public
580 */
581
5821Form.prototype.get = function get (path) {
58363 var field = this.path(path) || this.virtualpath(path),
584 pieces = path.split('.'),
585 obj = this.data;
586
58763 for (var i = 0, l = pieces.length; i < l; i++) {
58872 obj = undefined === obj || null === obj ? undefined : obj[pieces[i]];
589 }
590
591
59263 if (field) {
59362 obj = field.applyGetters(obj, this);
594 }
595
59663 return obj;
597};
598
599/**
600 * Executes registered validation field rules for this form.
601 *
602 * ####Example:
603 *
604 * form.validate(function (err) {
605 * if (err) handleError(err);
606 * else // validation passed
607 * });
608 *
609 * @param {Function} cb called after validation completes, passing an error if one occurred
610 * @api public
611 */
612
6131Form.prototype.validate = function (cb) {
61412 var self = this;
615
61612 var paths = Object.keys(this.paths);
617
61812 if (0 === paths.length) {
6190 complete();
6200 return this;
621 }
622
62312 var validating = {}, total = 0;
624
62512 paths.forEach(validatePath);
62612 return this;
627
6280 function validatePath (path) {
62944 if (validating[path]) return;
630
63144 validating[path] = true;
63244 total++;
633
63444 process.nextTick(function() {
63544 var p = self.path(path);
63644 if (!p) return --total || complete();
637
63844 var val = self.getValue(path);
63944 p.doValidate(val, function(err) {
64044 if (err) {
64119 self.invalidate(path, err, undefined, true); // embedded docs
642 }
64344 if (!--total)
64412 complete();
645 }, self);
646 });
647 }
648
6490 function complete () {
65012 var err = self.validationError;
65112 self.validationError = undefined;
65212 self.emit('validate', self);
65312 cb(err);
654 }
655};
656
657/**
658 * Gets a raw value from a path (no getters)
659 *
660 * @param {String} path
661 * @api public
662 */
663
6641Form.prototype.getValue = function (path) {
665108 return mpath.get(path, this.data);
666};
667
668/**
669 * Sets a raw value for a path (no casting, setters, transformations)
670 *
671 * @param {String} path
672 * @param {Object} value
673 * @api public
674 */
6751Form.prototype.setValue = function (path, val) {
67686 mpath.set(path, val, this.data);
67786 return this;
678};
679
680/**
681 * Marks a path as invalid, causing validation to fail.
682 *
683 * @param {String} path the field to invalidate
684 * @param {String|Error} err the error which states the reason `path` was invalid
685 * @param {Object|String|Number|any} value optional invalid value
686 * @api public
687 */
688
6891Form.prototype.invalidate = function (path, err, val) {
69019 if (!this.validationError) {
6917 this.validationError = new ValidationError(this);
692 }
693
69419 if (!err || 'string' === typeof err) {
695 // sniffing arguments:
696 // need to handle case where user does not pass value
697 // so our error message is cleaner
6980 err = 2 < arguments.length ? new ValidatorError(path, err, val) : new ValidatorError(path, err);
699 }
700
70119 this.validationError.errors[path] = err;
702};
703
704/**
705 * Return a simple tree object containing data and errors for each field
706 *
707 * @param {Ojbect} err ValidationError object
708 * @return {Object} Tree data
709 * @api public
710 */
7111Form.prototype.export = function (err) {
7123 var form = this,
713 errorsTpl = this.options.errors,
714 result = {};
715
7163 Object.keys(this.paths).forEach(function (path, index) {
71715 var field = {
718 value: form.get(path),
719 data: form.path(path).export()
720 };
721
72215 var fielderror = err ? mpath.get(path, err.errors) : null;
723
72415 if (fielderror) {
7257 if (errorsTpl[fielderror.type]) {
7266 field.error = _.template(errorsTpl[fielderror.type], field);
727 } else
7281 field.error = fielderror.message;
729 }
730
73115 _set(result, path.split('.'), field);
732 });
733
7343 return result;
735};
736
737/**
738 * Return the data tree with getters applied
739 *
740 * @return {Object} data tree object
741 * @public true
742 */
7431Form.prototype.getData = function() {
7441 var self = this,
745 values = {};
746
7471 Object.keys(this.paths).forEach(function (path, index) {
7482 _set(values, path.split('.'), self.get(path));
749 });
750
7511 return values;
752};
753
754/**
755 * Return a route-middleware for connect and express
756 *
757 * ####Example usage:
758 *
759 * var form = new Form({
760 * email: String
761 * });
762 *
763 * app.post('/url',
764 * form.middleware(),
765 * function(req, res) {
766 * console.log(req.form); // instanceof Form
767 * console.log(res.locals.form.fieldA); // {value: '...', error: '...', data: {...}}
768 * }
769 * );
770 *
771 * #####Alternative usage:
772 *
773 * app.post('/url',
774 * form({
775 * email: String
776 * }),
777 * function(req, res) {
778 * console.log(req.form); // instanceof Form
779 * console.log(res.locals.form.fieldA); // {value: '...', error: '...', data: {...}}
780 * }
781 * );
782 *
783 * #### Notes
784 *
785 * Adds the form instance to the request.
786 *
787 * #####Example usage with mongoose
788 * // in app.post callback
789 * modelinstance.set(req.form.data);
790 * model.save(function() {
791 * console.log('VoilĂ !')
792 * });
793 *
794 * It also adds the result of form.export with the the validation result to res.locals
795 * which is very useful for rendering
796 *
797 * @return {Function} route-middleware function
798 * @api public
799 */
8001Form.prototype.middleware = function () {
8013 var form = this;
802
8033 return function (req, res, next) {
8043 var mergedSource = {};
8053 form.options.dataSources.forEach(function(source) {
8069 if (req[source])
8075 _.merge(mergedSource, req[source]);
808 });
809
8103 form.set(mergedSource);
811
8123 form.validate(function (err) {
8133 if (form.options.autoLocals) {
8142 res.locals.form = form.export(err);
8152 res.locals.isValid = !!err;
816 }
8173 req.form = form;
8183 next();
819 });
820 };
821
822};
823
824/*!
825 * Module exports.
826 */
827
8281module.exports = exports = Form;
829
830// require down here because of reference issues
831
832/**!
833 * ignore
834 * The various built-in Field Types which mimic Mongoose Schema Type.
835 *
836 * ####Types:
837 *
838 * - [String](#field-string-js)
839 * - [Number](#field-number-js)
840 * - [Boolean](#field-boolean-js) | Bool
841 * - [Array](#field-array-js)
842 * - [Buffer](#field-buffer-js) // TODO?
843 * - [Date](#field-date-js)
844 * - [ObjectId](#field-objectid-js) | Oid // TODO
845 * - [Mixed](#field-mixed-js) // mixed type removed
846 *
847 * @api private
848 */
849
8501Form.FieldTypes = require('./field/types');
851
852/*!
853 * ignore
854 */
855
8561Types = Form.FieldTypes;
8571var ObjectId = exports.ObjectId = Types.ObjectId;
858
859
860/**
861 * Set a value to obj from parts of a path
862 *
863 * @api private
864 * @memberOf Form
865 */
866
8671var _set = function (obj, parts, val) {
868
86981 var i = 0,
870 l = parts.length;
871
87281 for (; i < l; i++) {
87388 var next = i + 1,
874 last = next === l;
875
87688 if (last) {
87781 obj[parts[i]] = val;
87881 return;
879 }
880
8817 if (obj[parts[i]] && 'Object' === obj[parts[i]].constructor.name) {
8821 obj = obj[parts[i]];
8836 } else if (obj[parts[i]] && Array.isArray(obj[parts[i]])) {
8842 obj = obj[parts[i]];
885 } else {
8864 obj = obj[parts[i]] = {};
887 }
888 }
889};
890
891
Done, without errors.