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 | if(Array.isArray(path2)){
|
297 | path2 = path2[0];
|
298 | }
|
299 | pos = path2.indexOf('*');
|
300 | if (pos === -1) {
|
301 | return path2;
|
302 | }
|
303 | path2 = path2.substr(0, pos) + value + path2.substr(pos + 1);
|
304 | });
|
305 | if(Array.isArray(path)){
|
306 | path[0] = path2;
|
307 | path2 = path;
|
308 | }
|
309 | return path2;
|
310 | },
|
311 |
|
312 | _replaceWildCardsMessages: function (nums) {
|
313 | var customMessages = this.messages.customMessages;
|
314 | var self = this;
|
315 | Object.keys(customMessages).forEach(function (key) {
|
316 | if (nums) {
|
317 | var newKey = self._replaceWildCards(key, nums);
|
318 | customMessages[newKey] = customMessages[key];
|
319 | }
|
320 | });
|
321 |
|
322 | this.messages._setCustom(customMessages);
|
323 | },
|
324 | |
325 |
|
326 |
|
327 |
|
328 |
|
329 |
|
330 | _prepareRulesArray: function (rulesArray) {
|
331 | var rules = [];
|
332 |
|
333 | for (var i = 0, len = rulesArray.length; i < len; i++) {
|
334 | if (typeof rulesArray[i] === 'object') {
|
335 | for (var rule in rulesArray[i]) {
|
336 | rules.push({
|
337 | name: rule,
|
338 | value: rulesArray[i][rule]
|
339 | });
|
340 | }
|
341 | } else {
|
342 | rules.push(rulesArray[i]);
|
343 | }
|
344 | }
|
345 |
|
346 | return rules;
|
347 | },
|
348 |
|
349 | |
350 |
|
351 |
|
352 |
|
353 |
|
354 |
|
355 | _suppliedWithData: function (attribute) {
|
356 | return this.input.hasOwnProperty(attribute);
|
357 | },
|
358 |
|
359 | |
360 |
|
361 |
|
362 |
|
363 |
|
364 |
|
365 | _extractRuleAndRuleValue: function (ruleString) {
|
366 | var rule = {},
|
367 | ruleArray;
|
368 |
|
369 | rule.name = ruleString;
|
370 |
|
371 | if (ruleString.indexOf(':') >= 0) {
|
372 | ruleArray = ruleString.split(':');
|
373 | rule.name = ruleArray[0];
|
374 | rule.value = ruleArray.slice(1).join(":");
|
375 | }
|
376 |
|
377 | return rule;
|
378 | },
|
379 |
|
380 | |
381 |
|
382 |
|
383 |
|
384 |
|
385 |
|
386 |
|
387 | _hasRule: function (attribute, findRules) {
|
388 | var rules = this.rules[attribute] || [];
|
389 | for (var i = 0, len = rules.length; i < len; i++) {
|
390 | if (findRules.indexOf(rules[i].name) > -1) {
|
391 | return true;
|
392 | }
|
393 | }
|
394 | return false;
|
395 | },
|
396 |
|
397 | |
398 |
|
399 |
|
400 |
|
401 |
|
402 |
|
403 | _hasNumericRule: function (attribute) {
|
404 | return this._hasRule(attribute, this.numericRules);
|
405 | },
|
406 |
|
407 | |
408 |
|
409 |
|
410 |
|
411 |
|
412 |
|
413 |
|
414 | _isValidatable: function (rule, value) {
|
415 | if (Rules.isImplicit(rule.name)) {
|
416 | return true;
|
417 | }
|
418 |
|
419 | return this.getRule('required').validate(value);
|
420 | },
|
421 |
|
422 | |
423 |
|
424 |
|
425 |
|
426 |
|
427 |
|
428 |
|
429 | _shouldStopValidating: function (attribute, rulePassed) {
|
430 |
|
431 | var stopOnAttributes = this.stopOnAttributes;
|
432 | if (typeof stopOnAttributes === 'undefined' || stopOnAttributes === false || rulePassed === true) {
|
433 | return false;
|
434 | }
|
435 |
|
436 | if (stopOnAttributes instanceof Array) {
|
437 | return stopOnAttributes.indexOf(attribute) > -1;
|
438 | }
|
439 |
|
440 | return true;
|
441 | },
|
442 |
|
443 | |
444 |
|
445 |
|
446 |
|
447 |
|
448 |
|
449 | setAttributeNames: function (attributes) {
|
450 | this.messages._setAttributeNames(attributes);
|
451 | },
|
452 |
|
453 | |
454 |
|
455 |
|
456 |
|
457 |
|
458 |
|
459 | setAttributeFormatter: function (func) {
|
460 | this.messages._setAttributeFormatter(func);
|
461 | },
|
462 |
|
463 | |
464 |
|
465 |
|
466 |
|
467 |
|
468 |
|
469 | getRule: function (name) {
|
470 | return Rules.make(name, this);
|
471 | },
|
472 |
|
473 | |
474 |
|
475 |
|
476 |
|
477 |
|
478 |
|
479 | stopOnError: function (attributes) {
|
480 | this.stopOnAttributes = attributes;
|
481 | },
|
482 |
|
483 | |
484 |
|
485 |
|
486 |
|
487 |
|
488 |
|
489 | passes: function (passes) {
|
490 | var async = this._checkAsync('passes', passes);
|
491 | if (async) {
|
492 | return this.checkAsync(passes);
|
493 | }
|
494 | return this.check();
|
495 | },
|
496 |
|
497 | |
498 |
|
499 |
|
500 |
|
501 |
|
502 |
|
503 | fails: function (fails) {
|
504 | var async = this._checkAsync('fails', fails);
|
505 | if (async) {
|
506 | return this.checkAsync(function () {}, fails);
|
507 | }
|
508 | return !this.check();
|
509 | },
|
510 |
|
511 | |
512 |
|
513 |
|
514 |
|
515 |
|
516 |
|
517 |
|
518 | _checkAsync: function (funcName, callback) {
|
519 | var hasCallback = typeof callback === 'function';
|
520 | if (this.hasAsync && !hasCallback) {
|
521 | throw funcName + ' expects a callback when async rules are being tested.';
|
522 | }
|
523 |
|
524 | return this.hasAsync || hasCallback;
|
525 | }
|
526 |
|
527 | };
|
528 |
|
529 |
|
530 |
|
531 |
|
532 |
|
533 |
|
534 |
|
535 |
|
536 | Validator.setMessages = function (lang, messages) {
|
537 | Lang._set(lang, messages);
|
538 | return this;
|
539 | };
|
540 |
|
541 |
|
542 |
|
543 |
|
544 |
|
545 |
|
546 |
|
547 | Validator.getMessages = function (lang) {
|
548 | return Lang._get(lang);
|
549 | };
|
550 |
|
551 |
|
552 |
|
553 |
|
554 |
|
555 |
|
556 |
|
557 | Validator.useLang = function (lang) {
|
558 | this.prototype.lang = lang;
|
559 | };
|
560 |
|
561 |
|
562 |
|
563 |
|
564 |
|
565 |
|
566 | Validator.getDefaultLang = function () {
|
567 | return this.prototype.lang;
|
568 | };
|
569 |
|
570 |
|
571 |
|
572 |
|
573 |
|
574 |
|
575 |
|
576 | Validator.setAttributeFormatter = function (func) {
|
577 | this.prototype.attributeFormatter = func;
|
578 | };
|
579 |
|
580 |
|
581 |
|
582 |
|
583 |
|
584 |
|
585 |
|
586 | Validator.stopOnError = function (attributes) {
|
587 | this.prototype.stopOnAttributes = attributes;
|
588 | };
|
589 |
|
590 |
|
591 |
|
592 |
|
593 |
|
594 |
|
595 |
|
596 |
|
597 |
|
598 | Validator.register = function (name, fn, message) {
|
599 | var lang = Validator.getDefaultLang();
|
600 | Rules.register(name, fn);
|
601 | Lang._setRuleMessage(lang, name, message);
|
602 | };
|
603 |
|
604 |
|
605 |
|
606 |
|
607 |
|
608 |
|
609 |
|
610 |
|
611 |
|
612 | Validator.registerImplicit = function (name, fn, message) {
|
613 | var lang = Validator.getDefaultLang();
|
614 | Rules.registerImplicit(name, fn);
|
615 | Lang._setRuleMessage(lang, name, message);
|
616 | };
|
617 |
|
618 |
|
619 |
|
620 |
|
621 |
|
622 |
|
623 |
|
624 |
|
625 |
|
626 | Validator.registerAsync = function (name, fn, message) {
|
627 | var lang = Validator.getDefaultLang();
|
628 | Rules.registerAsync(name, fn);
|
629 | Lang._setRuleMessage(lang, name, message);
|
630 | };
|
631 |
|
632 |
|
633 |
|
634 |
|
635 |
|
636 |
|
637 |
|
638 |
|
639 |
|
640 | Validator.registerAsyncImplicit = function (name, fn, message) {
|
641 | var lang = Validator.getDefaultLang();
|
642 | Rules.registerAsyncImplicit(name, fn);
|
643 | Lang._setRuleMessage(lang, name, message);
|
644 | };
|
645 |
|
646 |
|
647 |
|
648 |
|
649 |
|
650 |
|
651 |
|
652 |
|
653 |
|
654 | Validator.registerMissedRuleValidator = function(fn, message) {
|
655 | Rules.registerMissedRuleValidator(fn, message);
|
656 | };
|
657 |
|
658 | module.exports = Validator;
|