-
-
Notifications
You must be signed in to change notification settings - Fork 127
/
Copy pathstandard-validator.ts
155 lines (138 loc) · 5.24 KB
/
standard-validator.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import { Expression, LookupFunctions } from 'aurelia-binding';
import { ViewResources } from 'aurelia-templating';
import { Validator } from '../validator';
import { ValidateResult } from '../validate-result';
import { Rule } from './rule';
import { Rules } from './rules';
import { ValidationMessageProvider } from './validation-messages';
/**
* Validates.
* Responsible for validating objects and properties.
*/
export class StandardValidator extends Validator {
public static inject = [ValidationMessageProvider, ViewResources];
private messageProvider: ValidationMessageProvider;
private lookupFunctions: LookupFunctions;
private getDisplayName: (propertyName: string) => string;
constructor(messageProvider: ValidationMessageProvider, resources: ViewResources) {
super();
this.messageProvider = messageProvider;
this.lookupFunctions = (resources as any).lookupFunctions;
this.getDisplayName = messageProvider.getDisplayName.bind(messageProvider);
}
/**
* Validates the specified property.
* @param object The object to validate.
* @param propertyName The name of the property to validate.
* @param rules Optional. If unspecified, the rules will be looked up using the metadata
* for the object created by ValidationRules....on(class/object)
*/
public validateProperty(object: any, propertyName: string, rules?: any): Promise<ValidateResult[]> {
return this.validate(object, propertyName, rules || null);
}
/**
* Validates all rules for specified object and it's properties.
* @param object The object to validate.
* @param rules Optional. If unspecified, the rules will be looked up using the metadata
* for the object created by ValidationRules....on(class/object)
*/
public validateObject(object: any, rules?: any): Promise<ValidateResult[]> {
return this.validate(object, null, rules || null);
}
/**
* Determines whether a rule exists in a set of rules.
* @param rules The rules to search.
* @parem rule The rule to find.
*/
public ruleExists(rules: Rule<any, any>[][], rule: Rule<any, any>): boolean {
let i = rules.length;
while (i--) {
if (rules[i].indexOf(rule) !== -1) {
return true;
}
}
return false;
}
private getMessage(rule: Rule<any, any>, object: any, value: any): string {
const expression: Expression = rule.message || this.messageProvider.getMessage(rule.messageKey);
// tslint:disable-next-line:prefer-const
let { name: propertyName, displayName } = rule.property;
if (propertyName !== null) {
displayName = this.messageProvider.getDisplayName(propertyName, displayName);
}
const overrideContext: any = {
$displayName: displayName,
$propertyName: propertyName,
$value: value,
$object: object,
$config: rule.config,
// returns the name of a given property, given just the property name (irrespective of the property's displayName)
// split on capital letters, first letter ensured to be capitalized
$getDisplayName: this.getDisplayName
};
return expression.evaluate(
{ bindingContext: object, overrideContext },
this.lookupFunctions);
}
private validateRuleSequence(
object: any,
propertyName: string | null,
ruleSequence: Rule<any, any>[][],
sequence: number,
results: ValidateResult[]
): Promise<ValidateResult[]> {
// are we validating all properties or a single property?
const validateAllProperties = propertyName === null || propertyName === undefined;
const rules = ruleSequence[sequence];
let allValid = true;
// validate each rule.
const promises: Promise<boolean>[] = [];
for (let i = 0; i < rules.length; i++) {
const rule = rules[i];
// is the rule related to the property we're validating.
if (!validateAllProperties && rule.property.name !== propertyName) {
continue;
}
// is this a conditional rule? is the condition met?
if (rule.when && !rule.when(object)) {
continue;
}
// validate.
const value = rule.property.name === null ? object : object[rule.property.name];
let promiseOrBoolean = rule.condition(value, object);
if (!(promiseOrBoolean instanceof Promise)) {
promiseOrBoolean = Promise.resolve(promiseOrBoolean);
}
promises.push(promiseOrBoolean.then(valid => {
const message = valid ? null : this.getMessage(rule, object, value);
results.push(new ValidateResult(rule, object, rule.property.name, valid, message));
allValid = allValid && valid;
return valid;
}));
}
return Promise.all(promises)
.then(() => {
sequence++;
if (allValid && sequence < ruleSequence.length) {
return this.validateRuleSequence(object, propertyName, ruleSequence, sequence, results);
}
return results;
});
}
private validate(
object: any,
propertyName: string | null,
rules: Rule<any, any>[][] | null
): Promise<ValidateResult[]> {
// rules specified?
if (!rules) {
// no. attempt to locate the rules.
rules = Rules.get(object);
}
// any rules?
if (!rules) {
return Promise.resolve([]);
}
return this.validateRuleSequence(object, propertyName, rules, 0, []);
}
}