1 | var Rules = require('./rules');
|
2 | var Lang = require('./lang');
|
3 | var Errors = require('./errors');
|
4 | var Attributes = require('./attributes');
|
5 | var AsyncResolvers = require('./async');
|
6 |
|
7 | var Validator = function (input, rules, customMessages) {
|
8 | var lang = Validator.getDefaultLang();
|
9 | this.input = input || {};
|
10 |
|
11 | this.messages = Lang._make(lang);
|
12 | this.messages._setCustom(customMessages);
|
13 | this.setAttributeFormatter(Validator.prototype.attributeFormatter);
|
14 |
|
15 | this.errors = new Errors();
|
16 | this.errorCount = 0;
|
17 |
|
18 | this.hasAsync = false;
|
19 | this.rules = this._parseRules(rules);
|
20 | };
|
21 |
|
22 | Validator.prototype = {
|
23 |
|
24 | constructor: Validator,
|
25 |
|
26 | /**
|
27 | * Default language
|
28 | *
|
29 | * @type {string}
|
30 | */
|
31 | lang: 'en',
|
32 |
|
33 | |
34 |
|
35 |
|
36 |
|
37 |
|
38 | numericRules: ['integer', 'numeric'],
|
39 |
|
40 | |
41 |
|
42 |
|
43 |
|
44 |
|
45 | attributeFormatter: Attributes.formatter,
|
46 |
|
47 | |
48 |
|
49 |
|
50 |
|
51 |
|
52 | check: function () {
|
53 | var self = this;
|
54 |
|
55 | for (var attribute in this.rules) {
|
56 | var attributeRules = this.rules[attribute];
|
57 | var inputValue = this._objectPath(this.input, attribute);
|
58 |
|
59 | if (this._hasRule(attribute, ['sometimes']) && !this._suppliedWithData(attribute)) {
|
60 | continue;
|
61 | }
|
62 |
|
63 | for (var i = 0, len = attributeRules.length, rule, ruleOptions, rulePassed; i < len; i++) {
|
64 | ruleOptions = attributeRules[i];
|
65 | rule = this.getRule(ruleOptions.name);
|
66 |
|
67 | if (!this._isValidatable(rule, inputValue)) {
|
68 | continue;
|
69 | }
|
70 |
|
71 | rulePassed = rule.validate(inputValue, ruleOptions.value, attribute);
|
72 | if (!rulePassed) {
|
73 | this._addFailure(rule);
|
74 | }
|
75 |
|
76 | if (this._shouldStopValidating(attribute, rulePassed)) {
|
77 | break;
|
78 | }
|
79 | }
|
80 | }
|
81 |
|
82 | return this.errorCount === 0;
|
83 | },
|
84 |
|
85 | |
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 | checkAsync: function (passes, fails) {
|
93 | var _this = this;
|
94 | passes = passes || function () {};
|
95 | fails = fails || function () {};
|
96 |
|
97 | var failsOne = function (rule, message) {
|
98 | _this._addFailure(rule, message);
|
99 | };
|
100 |
|
101 | var resolvedAll = function (allPassed) {
|
102 | if (allPassed) {
|
103 | passes();
|
104 | } else {
|
105 | fails();
|
106 | }
|
107 | };
|
108 |
|
109 | var asyncResolvers = new AsyncResolvers(failsOne, resolvedAll);
|
110 |
|
111 | var validateRule = function (inputValue, ruleOptions, attribute, rule) {
|
112 | return function () {
|
113 | var resolverIndex = asyncResolvers.add(rule);
|
114 | rule.validate(inputValue, ruleOptions.value, attribute, function () {
|
115 | asyncResolvers.resolve(resolverIndex);
|
116 | });
|
117 | };
|
118 | };
|
119 |
|
120 | for (var attribute in this.rules) {
|
121 | var attributeRules = this.rules[attribute];
|
122 | var inputValue = this._objectPath(this.input, attribute);
|
123 |
|
124 | if (this._hasRule(attribute, ['sometimes']) && !this._suppliedWithData(attribute)) {
|
125 | continue;
|
126 | }
|
127 |
|
128 | for (var i = 0, len = attributeRules.length, rule, ruleOptions; i < len; i++) {
|
129 | ruleOptions = attributeRules[i];
|
130 |
|
131 | rule = this.getRule(ruleOptions.name);
|
132 |
|
133 | if (!this._isValidatable(rule, inputValue)) {
|
134 | continue;
|
135 | }
|
136 |
|
137 | validateRule(inputValue, ruleOptions, attribute, rule)();
|
138 | }
|
139 | }
|
140 |
|
141 | asyncResolvers.enableFiring();
|
142 | asyncResolvers.fire();
|
143 | },
|
144 |
|
145 | |
146 |
|
147 |
|
148 |
|
149 |
|
150 | _addFailure: function (rule) {
|
151 | var msg = this.messages.render(rule);
|
152 | this.errors.add(rule.attribute, msg);
|
153 | this.errorCount++;
|
154 | },
|
155 |
|
156 | |
157 |
|
158 |
|
159 |
|
160 |
|
161 |
|
162 | _flattenObject: function (obj) {
|
163 | var flattened = {};
|
164 |
|
165 | function recurse(current, property) {
|
166 | if (!property && Object.getOwnPropertyNames(current).length === 0) {
|
167 | return;
|
168 | }
|
169 | if (Object(current) !== current || Array.isArray(current)) {
|
170 | flattened[property] = current;
|
171 | } else {
|
172 | var isEmpty = true;
|
173 | for (var p in current) {
|
174 | isEmpty = false;
|
175 | recurse(current[p], property ? property + "." + p : p);
|
176 | }
|
177 | if (isEmpty) {
|
178 | flattened[property] = {};
|
179 | }
|
180 | }
|
181 | }
|
182 | if (obj) {
|
183 | recurse(obj);
|
184 | }
|
185 | return flattened;
|
186 | },
|
187 |
|
188 | |
189 |
|
190 |
|
191 |
|
192 |
|
193 |
|
194 |
|
195 | _objectPath: function (obj, path) {
|
196 | if (Object.prototype.hasOwnProperty.call(obj, path)) {
|
197 | return obj[path];
|
198 | }
|
199 |
|
200 | var keys = path.replace(/\[(\w+)\]/g, ".$1").replace(/^\./, "").split(".");
|
201 | var copy = {};
|
202 | for (var attr in obj) {
|
203 | if (Object.prototype.hasOwnProperty.call(obj, attr)) {
|
204 | copy[attr] = obj[attr];
|
205 | }
|
206 | }
|
207 |
|
208 | for (var i = 0, l = keys.length; i < l; i++) {
|
209 | if (Object.hasOwnProperty.call(copy, keys[i])) {
|
210 | copy = copy[keys[i]];
|
211 | } else {
|
212 | return;
|
213 | }
|
214 | }
|
215 | return copy;
|
216 | },
|
217 |
|
218 | |
219 |
|
220 |
|
221 |
|
222 |
|
223 |
|
224 | _parseRules: function (rules) {
|
225 |
|
226 | var parsedRules = {};
|
227 | rules = this._flattenObject(rules);
|
228 |
|
229 | for (var attribute in rules) {
|
230 |
|
231 | var rulesArray = rules[attribute];
|
232 |
|
233 | this._parseRulesCheck(attribute, rulesArray, parsedRules);
|
234 | }
|
235 | return parsedRules;
|
236 |
|
237 |
|
238 | },
|
239 |
|
240 | _parseRulesCheck: function (attribute, rulesArray, parsedRules, wildCardValues) {
|
241 | if (attribute.indexOf('*') > -1) {
|
242 | this._parsedRulesRecurse(attribute, rulesArray, parsedRules, wildCardValues);
|
243 | } else {
|
244 | this._parseRulesDefault(attribute, rulesArray, parsedRules, wildCardValues);
|
245 | }
|
246 | },
|
247 |
|
248 | _parsedRulesRecurse: function (attribute, rulesArray, parsedRules, wildCardValues) {
|
249 | var parentPath = attribute.substr(0, attribute.indexOf('*') - 1);
|
250 | var propertyValue = this._objectPath(this.input, parentPath);
|
251 |
|
252 | if (propertyValue) {
|
253 | for (var propertyNumber = 0; propertyNumber < propertyValue.length; propertyNumber++) {
|
254 | var workingValues = wildCardValues ? wildCardValues.slice() : [];
|
255 | workingValues.push(propertyNumber);
|
256 | this._parseRulesCheck(attribute.replace('*', propertyNumber), rulesArray, parsedRules, workingValues);
|
257 | }
|
258 | }
|
259 | },
|
260 |
|
261 | _parseRulesDefault: function (attribute, rulesArray, parsedRules, wildCardValues) {
|
262 | var attributeRules = [];
|
263 |
|
264 | if (rulesArray instanceof Array) {
|
265 | rulesArray = this._prepareRulesArray(rulesArray);
|
266 | }
|
267 |
|
268 | if (typeof rulesArray === 'string') {
|
269 | rulesArray = rulesArray.split('|');
|
270 | }
|
271 |
|
272 | for (var i = 0, len = rulesArray.length, rule; i < len; i++) {
|
273 | rule = typeof rulesArray[i] === 'string' ? this._extractRuleAndRuleValue(rulesArray[i]) : rulesArray[i];
|
274 | if (rule.value) {
|
275 | rule.value = this._replaceWildCards(rule.value, wildCardValues);
|
276 | this._replaceWildCardsMessages(wildCardValues);
|
277 | }
|
278 |
|
279 | if (Rules.isAsync(rule.name)) {
|
280 | this.hasAsync = true;
|
281 | }
|
282 | attributeRules.push(rule);
|
283 | }
|
284 |
|
285 | parsedRules[attribute] = attributeRules;
|
286 | },
|
287 |
|
288 | _replaceWildCards: function (path, nums) {
|
289 |
|
290 | if (!nums) {
|
291 | return path;
|
292 | }
|
293 |
|
294 | var path2 = path;
|
295 | nums.forEach(function (value) {
|
296 | var pos = path2.indexOf('*');
|
297 | if (pos === -1) {
|
298 | return path2;
|
299 | }
|
300 | path2 = path2.substr(0, pos) + value + path2.substr(pos + 1);
|
301 | });
|
302 | return path2;
|
303 | },
|
304 |
|
305 | _replaceWildCardsMessages: function (nums) {
|
306 | var customMessages = this.messages.customMessages;
|
307 | var self = this;
|
308 | Object.keys(customMessages).forEach(function (key) {
|
309 | if (nums) {
|
310 | var newKey = self._replaceWildCards(key, nums);
|
311 | customMessages[newKey] = customMessages[key];
|
312 | }
|
313 | });
|
314 |
|
315 | this.messages._setCustom(customMessages);
|
316 | },
|
317 | |
318 |
|
319 |
|
320 |
|
321 |
|
322 |
|
323 | _prepareRulesArray: function (rulesArray) {
|
324 | var rules = [];
|
325 |
|
326 | for (var i = 0, len = rulesArray.length; i < len; i++) {
|
327 | if (typeof rulesArray[i] === 'object') {
|
328 | for (var rule in rulesArray[i]) {
|
329 | rules.push({
|
330 | name: rule,
|
331 | value: rulesArray[i][rule]
|
332 | });
|
333 | }
|
334 | } else {
|
335 | rules.push(rulesArray[i]);
|
336 | }
|
337 | }
|
338 |
|
339 | return rules;
|
340 | },
|
341 |
|
342 | |
343 |
|
344 |
|
345 |
|
346 |
|
347 |
|
348 | _suppliedWithData: function (attribute) {
|
349 | return this.input.hasOwnProperty(attribute);
|
350 | },
|
351 |
|
352 | |
353 |
|
354 |
|
355 |
|
356 |
|
357 |
|
358 | _extractRuleAndRuleValue: function (ruleString) {
|
359 | var rule = {},
|
360 | ruleArray;
|
361 |
|
362 | rule.name = ruleString;
|
363 |
|
364 | if (ruleString.indexOf(':') >= 0) {
|
365 | ruleArray = ruleString.split(':');
|
366 | rule.name = ruleArray[0];
|
367 | rule.value = ruleArray.slice(1).join(":");
|
368 | }
|
369 |
|
370 | return rule;
|
371 | },
|
372 |
|
373 | |
374 |
|
375 |
|
376 |
|
377 |
|
378 |
|
379 |
|
380 | _hasRule: function (attribute, findRules) {
|
381 | var rules = this.rules[attribute] || [];
|
382 | for (var i = 0, len = rules.length; i < len; i++) {
|
383 | if (findRules.indexOf(rules[i].name) > -1) {
|
384 | return true;
|
385 | }
|
386 | }
|
387 | return false;
|
388 | },
|
389 |
|
390 | |
391 |
|
392 |
|
393 |
|
394 |
|
395 |
|
396 | _hasNumericRule: function (attribute) {
|
397 | return this._hasRule(attribute, this.numericRules);
|
398 | },
|
399 |
|
400 | |
401 |
|
402 |
|
403 |
|
404 |
|
405 |
|
406 |
|
407 | _isValidatable: function (rule, value) {
|
408 | if (Rules.isImplicit(rule.name)) {
|
409 | return true;
|
410 | }
|
411 |
|
412 | return this.getRule('required').validate(value);
|
413 | },
|
414 |
|
415 | |
416 |
|
417 |
|
418 |
|
419 |
|
420 |
|
421 |
|
422 | _shouldStopValidating: function (attribute, rulePassed) {
|
423 |
|
424 | var stopOnAttributes = this.stopOnAttributes;
|
425 | if (typeof stopOnAttributes === 'undefined' || stopOnAttributes === false || rulePassed === true) {
|
426 | return false;
|
427 | }
|
428 |
|
429 | if (stopOnAttributes instanceof Array) {
|
430 | return stopOnAttributes.indexOf(attribute) > -1;
|
431 | }
|
432 |
|
433 | return true;
|
434 | },
|
435 |
|
436 | |
437 |
|
438 |
|
439 |
|
440 |
|
441 |
|
442 | setAttributeNames: function (attributes) {
|
443 | this.messages._setAttributeNames(attributes);
|
444 | },
|
445 |
|
446 | |
447 |
|
448 |
|
449 |
|
450 |
|
451 |
|
452 | setAttributeFormatter: function (func) {
|
453 | this.messages._setAttributeFormatter(func);
|
454 | },
|
455 |
|
456 | |
457 |
|
458 |
|
459 |
|
460 |
|
461 |
|
462 | getRule: function (name) {
|
463 | return Rules.make(name, this);
|
464 | },
|
465 |
|
466 | |
467 |
|
468 |
|
469 |
|
470 |
|
471 |
|
472 | stopOnError: function (attributes) {
|
473 | this.stopOnAttributes = attributes;
|
474 | },
|
475 |
|
476 | |
477 |
|
478 |
|
479 |
|
480 |
|
481 |
|
482 | passes: function (passes) {
|
483 | var async = this._checkAsync('passes', passes);
|
484 | if (async) {
|
485 | return this.checkAsync(passes);
|
486 | }
|
487 | return this.check();
|
488 | },
|
489 |
|
490 | |
491 |
|
492 |
|
493 |
|
494 |
|
495 |
|
496 | fails: function (fails) {
|
497 | var async = this._checkAsync('fails', fails);
|
498 | if (async) {
|
499 | return this.checkAsync(function () {}, fails);
|
500 | }
|
501 | return !this.check();
|
502 | },
|
503 |
|
504 | |
505 |
|
506 |
|
507 |
|
508 |
|
509 |
|
510 |
|
511 | _checkAsync: function (funcName, callback) {
|
512 | var hasCallback = typeof callback === 'function';
|
513 | if (this.hasAsync && !hasCallback) {
|
514 | throw funcName + ' expects a callback when async rules are being tested.';
|
515 | }
|
516 |
|
517 | return this.hasAsync || hasCallback;
|
518 | }
|
519 |
|
520 | };
|
521 |
|
522 |
|
523 |
|
524 |
|
525 |
|
526 |
|
527 |
|
528 |
|
529 | Validator.setMessages = function (lang, messages) {
|
530 | Lang._set(lang, messages);
|
531 | return this;
|
532 | };
|
533 |
|
534 |
|
535 |
|
536 |
|
537 |
|
538 |
|
539 |
|
540 | Validator.getMessages = function (lang) {
|
541 | return Lang._get(lang);
|
542 | };
|
543 |
|
544 |
|
545 |
|
546 |
|
547 |
|
548 |
|
549 |
|
550 | Validator.useLang = function (lang) {
|
551 | this.prototype.lang = lang;
|
552 | };
|
553 |
|
554 |
|
555 |
|
556 |
|
557 |
|
558 |
|
559 | Validator.getDefaultLang = function () {
|
560 | return this.prototype.lang;
|
561 | };
|
562 |
|
563 |
|
564 |
|
565 |
|
566 |
|
567 |
|
568 |
|
569 | Validator.setAttributeFormatter = function (func) {
|
570 | this.prototype.attributeFormatter = func;
|
571 | };
|
572 |
|
573 |
|
574 |
|
575 |
|
576 |
|
577 |
|
578 |
|
579 | Validator.stopOnError = function (attributes) {
|
580 | this.prototype.stopOnAttributes = attributes;
|
581 | };
|
582 |
|
583 |
|
584 |
|
585 |
|
586 |
|
587 |
|
588 |
|
589 |
|
590 |
|
591 | Validator.register = function (name, fn, message) {
|
592 | var lang = Validator.getDefaultLang();
|
593 | Rules.register(name, fn);
|
594 | Lang._setRuleMessage(lang, name, message);
|
595 | };
|
596 |
|
597 |
|
598 |
|
599 |
|
600 |
|
601 |
|
602 |
|
603 |
|
604 |
|
605 | Validator.registerImplicit = function (name, fn, message) {
|
606 | var lang = Validator.getDefaultLang();
|
607 | Rules.registerImplicit(name, fn);
|
608 | Lang._setRuleMessage(lang, name, message);
|
609 | };
|
610 |
|
611 |
|
612 |
|
613 |
|
614 |
|
615 |
|
616 |
|
617 |
|
618 |
|
619 | Validator.registerAsync = function (name, fn, message) {
|
620 | var lang = Validator.getDefaultLang();
|
621 | Rules.registerAsync(name, fn);
|
622 | Lang._setRuleMessage(lang, name, message);
|
623 | };
|
624 |
|
625 |
|
626 |
|
627 |
|
628 |
|
629 |
|
630 |
|
631 |
|
632 |
|
633 | Validator.registerAsyncImplicit = function (name, fn, message) {
|
634 | var lang = Validator.getDefaultLang();
|
635 | Rules.registerAsyncImplicit(name, fn);
|
636 | Lang._setRuleMessage(lang, name, message);
|
637 | };
|
638 |
|
639 |
|
640 |
|
641 |
|
642 |
|
643 |
|
644 |
|
645 |
|
646 |
|
647 | Validator.registerMissedRuleValidator = function(fn, message) {
|
648 | Rules.registerMissedRuleValidator(fn, message);
|
649 | };
|
650 |
|
651 | module.exports = Validator;
|