Skip to content

Commit ba23707

Browse files
authored
Merge pull request #31 from andersnm/compile
Reorganized
2 parents e37c10b + 55ef64e commit ba23707

File tree

2 files changed

+296
-170
lines changed

2 files changed

+296
-170
lines changed

lib/validator.js

Lines changed: 157 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -59,190 +59,196 @@ Validator.prototype.validate = function(obj, schema) {
5959
* @throws {Error} Invalid schema
6060
*/
6161
Validator.prototype.compile = function(schema) {
62+
const self = this;
6263
if (Array.isArray(schema)) {
6364
// Multiple schemas
6465
if (schema.length == 0) {
6566
throw new Error("If the schema is an Array, must contain at least one element!");
6667
}
6768

68-
const rules = flatten(schema.map(r => this._processRule(r, null, false)));
69-
return this._checkWrapper(rules, true);
69+
var rules = this.compileSchemaType(schema);
70+
return function(value) {
71+
return self.checkSchemaType(value, rules, undefined, null);
72+
}
73+
}
7074

71-
} else if (schema != null && typeof schema === "object") {
72-
const rules = flatten(Object.keys(schema).map(name => this._processRule(schema[name], name, false)));
73-
return this._checkWrapper(rules);
75+
var rule = this.compileSchemaObject(schema);
76+
return function(value) {
77+
return self.checkSchemaObject(value, rule, undefined, null);
78+
}
79+
};
7480

81+
Validator.prototype.compileSchemaObject = function(schemaObject) {
82+
if (schemaObject === null || typeof schemaObject !== "object" || Array.isArray(schemaObject)) {
83+
throw new Error("Invalid schema!");
7584
}
7685

77-
throw new Error("Invalid schema!");
86+
var compiledObject = Object.keys(schemaObject).map(name => {
87+
const compiledType = this.compileSchemaType(schemaObject[name]);
88+
return {name: name, compiledType: compiledType};
89+
});
90+
91+
// Uncomment this line to use compiled object validator:
92+
// return compiledObject;
93+
94+
const sourceCode = [];
95+
sourceCode.push(`let res;`);
96+
sourceCode.push(`let propertyPath;`);
97+
sourceCode.push(`const errors = [];`);
98+
for (let i = 0; i < compiledObject.length; i++) {
99+
const property = compiledObject[i];
100+
const name = property.name;
101+
sourceCode.push(`propertyPath = (path !== undefined ? path + ".${name}" : "${name}");`);
102+
if (Array.isArray(property.compiledType)) {
103+
sourceCode.push(`res = this.checkSchemaType(value.${name}, compiledObject[${i}].compiledType, propertyPath, value);`);
104+
} else {
105+
sourceCode.push(`res = this.checkSchemaRule(value.${name}, compiledObject[${i}].compiledType, propertyPath, value);`);
106+
}
107+
sourceCode.push(`if (res !== true) {`);
108+
sourceCode.push(`\tthis.handleResult(errors, propertyPath, res);`);
109+
sourceCode.push(`}`);
110+
}
111+
112+
sourceCode.push(`return errors.length === 0 ? true : errors;`);
113+
114+
var compiledObjectFunction = new Function("value", "compiledObject", "path", "parent", sourceCode.join("\n"));
115+
116+
var self = this;
117+
return function(value, _unused, path, parent) {
118+
return compiledObjectFunction.call(self, value, compiledObject, path, parent);
119+
};
78120
};
79121

80-
/**
81-
* Process a rule item & return checker functions
82-
*
83-
* @param {Object} rule
84-
* @param {String} name
85-
* @param {Boolean} iterate
86-
*/
87-
Validator.prototype._processRule = function(rule, name, iterate) {
88-
const checks = [];
89-
90-
if (Array.isArray(rule)) {
91-
// Compile the multiple schemas
92-
checks.push({
93-
fn: this.compile(rule),
94-
type: "_multi",
95-
name: name,
96-
schema: rule,
97-
iterate: iterate
98-
});
99-
100-
return checks;
122+
Validator.prototype.compileSchemaType = function(schemaType) {
123+
if (Array.isArray(schemaType)) {
124+
// Multiple rules, flatten to array of compiled SchemaRule
125+
const rules = flatten(schemaType.map(r => this.compileSchemaType(r)));
126+
if (rules.length == 1) {
127+
return rules[0];
128+
}
129+
130+
return rules;
101131
}
102132

103-
if (typeof rule === "string") {
104-
rule = {
105-
type: rule
133+
return this.compileSchemaRule(schemaType);
134+
};
135+
136+
Validator.prototype.compileSchemaRule = function(schemaRule) {
137+
if (typeof schemaRule === "string") {
138+
schemaRule = {
139+
type: schemaRule
106140
};
107141
}
108142

109-
if (!this.rules[rule.type]) {
110-
throw new Error("Invalid '" + rule.type + "' type in validator schema!");
143+
const ruleFunction = this.rules[schemaRule.type];
144+
if (!ruleFunction) {
145+
throw new Error("Invalid '" + schemaRule.type + "' type in validator schema!");
111146
}
112147

148+
let dataParameter = null;
149+
let dataFunction = null;
113150

114-
/**
115-
* !IMPORTANT!: For the functioning of multiRule cases it is important that
116-
* pushing of object and array special rules is done in directly after this
117-
* simple rule.
118-
* If you which to push other checks, do it before the simple ones or after
119-
* the array special case.
120-
* See the comments in _checkWrapper for further explanation.
121-
*/
122-
checks.push({
123-
fn: this.rules[rule.type],
124-
type: rule.type,
125-
name: name,
126-
schema: rule,
127-
iterate: iterate
128-
});
151+
if (schemaRule.type === "object" && schemaRule.props) {
152+
dataParameter = this.compileSchemaObject(schemaRule.props);
153+
dataFunction = this.checkSchemaObject;
154+
} else if (schemaRule.type === "array" && schemaRule.items) {
155+
dataParameter = this.compileSchemaType(schemaRule.items);
156+
dataFunction = this.checkSchemaArray;
157+
}
158+
159+
return {
160+
schemaRule: schemaRule,
161+
ruleFunction: ruleFunction,
162+
dataFunction: dataFunction,
163+
dataParameter: dataParameter
164+
};
165+
};
129166

130-
// Nested schema
131-
if (rule.type === "object" && rule.props) {
132-
// Compile the child schema
133-
checks.push({
134-
fn: this.compile(rule.props),
135-
type: rule.type,
136-
name: name,
137-
schema: rule,
138-
iterate: iterate,
139-
secondPart: true //first part is the "primitive" typeof check above
140-
});
167+
Validator.prototype.checkSchemaObject = function(value, compiledObject, path, parent) {
168+
if (compiledObject instanceof Function) {
169+
return compiledObject(value, undefined, path, parent);
141170
}
142171

143-
// Array schema
144-
if (rule.type === "array" && rule.items) {
145-
// Compile the array schema
146-
checks.push({
147-
fn: this._checkWrapper(this._processRule(rule.items, null, false)),
148-
type: rule.type,
149-
name: name,
150-
schema: rule,
151-
iterate: true,
152-
secondPart: true //first part is the "primitive" typeof check above
153-
});
172+
const errors = [];
173+
const checksLength = compiledObject.length;
174+
for (let i = 0; i < checksLength; i++) {
175+
const check = compiledObject[i];
176+
const propertyPath = (path !== undefined ? path + "." : "") + check.name;
177+
const res = this.checkSchemaType(value[check.name], check.compiledType, propertyPath, value);
178+
179+
if (res !== true) {
180+
this.handleResult(errors, propertyPath, res);
181+
}
154182
}
155183

156-
return checks;
184+
return errors.length === 0 ? true : errors;
157185
};
158186

159-
/**
160-
* Create a wrapper function for compiled schema.
161-
*
162-
* @param {Array} rules
163-
* @param {Boolean} isMultipleRules
164-
*/
165-
Validator.prototype._checkWrapper = function(rules, isMultipleRules) {
166-
const self = this;
187+
Validator.prototype.checkSchemaType = function(value, compiledType, path, parent) {
188+
if (Array.isArray(compiledType)) {
189+
const errors = [];
190+
const checksLength = compiledType.length;
191+
for (let i = 0; i < checksLength; i++) {
192+
// Always compiled to list of rules
193+
const res = this.checkSchemaRule(value, compiledType[i], path, parent);
167194

168-
// Compiled validator function
169-
return function(obj, _schema, pathStack) {
170-
let errors = [];
171-
const count = rules.length;
172-
for (let i = 0; i < count; i++) {
173-
const check = rules[i];
174-
const schema = check.schema;
175-
176-
let value;
177-
let stack;
178-
if (check.name) {
179-
value = obj[check.name];
180-
stack = (pathStack ? pathStack + "." : "") + check.name;
195+
if (res !== true) {
196+
this.handleResult(errors, path, res);
181197
} else {
182-
value = obj;
183-
stack = pathStack ? pathStack : "";
198+
// Jump out after first success and clear previous errors
199+
return true;
184200
}
201+
}
185202

186-
// Check required fields
187-
if ((value === undefined || value === null)) {
188-
if (check.type === "forbidden")
189-
continue;
190-
191-
if (schema.optional === true)
192-
continue;
193-
194-
if (!Array.isArray(schema)) {
195-
self.handleResult(errors, stack, self.makeError("required"));
196-
continue;
197-
}
198-
199-
} // else {
200-
// Call the checker function
201-
if (check.iterate) {
202-
let errorInCurrentArray = false;
203-
const l = value.length;
204-
for (let i = 0; i < l; i++) {
205-
let _stack = stack + "[" + i + "]";
206-
let res = check.fn.call(self, value[i], schema, _stack, obj);
207-
if (res !== true) {
208-
errorInCurrentArray = true;
209-
self.handleResult(errors, _stack, res);
210-
}
211-
}
212-
/**
213-
* If this is second part of a multiRule array check and the array
214-
* is valid, then the rule is valid.
215-
*/
216-
if (!errorInCurrentArray && isMultipleRules && check.secondPart) {
217-
return true;
218-
}
219-
} else {
220-
let res = check.fn.call(self, value, schema, stack, obj);
221-
222-
if (isMultipleRules) {
223-
if (res === true) {
224-
/**
225-
* Object and array checks are divided into two internal checks. In case of a multiRule
226-
* we have to make sure to check both parts. Thus we we continue to also do the second
227-
* check if their is one.
228-
*/
229-
const nextRule = rules[i + 1];
230-
if (nextRule && nextRule.secondPart){
231-
continue;
232-
}
233-
// Jump out after first success and clear previous errors
234-
return true;
235-
}
236-
}
237-
238-
if (res !== true)
239-
self.handleResult(errors, stack, res);
240-
}
241-
//}
203+
return errors;
204+
}
205+
206+
return this.checkSchemaRule(value, compiledType, path, parent);
207+
};
208+
209+
Validator.prototype.checkSchemaArray = function(value, compiledArray, path, parent) {
210+
const errors = [];
211+
const valueLength = value.length;
212+
213+
for (let i = 0; i < valueLength; i++) {
214+
const itemPath = (path !== undefined ? path : "") + "[" + i + "]";
215+
const res = this.checkSchemaType(value[i], compiledArray, itemPath, value, parent);
216+
217+
if (res !== true) {
218+
this.handleResult(errors, itemPath, res);
242219
}
220+
}
243221

244-
return errors.length === 0 ? true : errors;
245-
};
222+
return errors.length === 0 ? true : errors;
223+
};
224+
225+
Validator.prototype.checkSchemaRule = function(value, compiledRule, path, parent) {
226+
const schemaRule = compiledRule.schemaRule;
227+
228+
if (value === undefined || value === null) {
229+
if (schemaRule.type === "forbidden")
230+
return true;
231+
232+
if (schemaRule.optional === true)
233+
return true;
234+
235+
const errors = [];
236+
this.handleResult(errors, path, this.makeError("required"));
237+
return errors;
238+
}
239+
240+
const res = compiledRule.ruleFunction.call(this, value, schemaRule, path, parent);
241+
if (res !== true) {
242+
const errors = [];
243+
this.handleResult(errors, path, res);
244+
return errors;
245+
}
246+
247+
if (compiledRule.dataFunction !== null) {
248+
return compiledRule.dataFunction.call(this, value, compiledRule.dataParameter, path, parent);
249+
}
250+
251+
return true;
246252
};
247253

248254
/**

0 commit comments

Comments
 (0)