Skip to content

Commit 7ce54aa

Browse files
committed
More reorganize for performance, compile unrolled object validator
1 parent a2bdb77 commit 7ce54aa

File tree

1 file changed

+133
-76
lines changed

1 file changed

+133
-76
lines changed

lib/validator.js

Lines changed: 133 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -59,65 +59,75 @@ 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-
return this.compileSchemaType(schema);
69+
var rules = this.compileSchemaType(schema);
70+
return function(value) {
71+
return self.checkSchemaType(value, rules, undefined, null);
72+
}
6973
}
7074

71-
return this.compileSchemaObject(schema);
75+
var rule = this.compileSchemaObject(schema);
76+
return function(value) {
77+
return self.checkSchemaObject(value, rule, undefined, null);
78+
}
7279
};
7380

7481
Validator.prototype.compileSchemaObject = function(schemaObject) {
7582
if (schemaObject === null || typeof schemaObject !== "object" || Array.isArray(schemaObject)) {
7683
throw new Error("Invalid schema!");
7784
}
78-
79-
const self = this;
80-
const checks = Object.keys(schemaObject).map(name => ({name: name, fn: this.compileSchemaType(schemaObject[name])}));
8185

82-
return function(value, _, path, parent) {
83-
const errors = [];
84-
const checksLength = checks.length;
85-
for (let i = 0; i < checksLength; i++) {
86-
const check = checks[i];
87-
const propertyPath = (path !== undefined ? path + "." : "") + check.name;
88-
const res = check.fn(value[check.name], undefined, propertyPath, value);
86+
var compiledObject = Object.keys(schemaObject).map(name => {
87+
const compiledType = this.compileSchemaType(schemaObject[name]);
88+
return {name: name, compiledType: compiledType, isArray: Array.isArray(compiledType)};
89+
});
8990

90-
if (res !== true) {
91-
self.handleResult(errors, propertyPath, res);
92-
}
91+
// Uncomment this line to use uncompiled 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);`);
93106
}
107+
sourceCode.push(`if (res !== true) {`);
108+
sourceCode.push(`\tthis.handleResult(errors, propertyPath, res);`);
109+
sourceCode.push(`}`);
110+
}
94111

95-
return errors.length === 0 ? true : errors;
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);
96119
};
97120
};
98121

99122
Validator.prototype.compileSchemaType = function(schemaType) {
100123
if (Array.isArray(schemaType)) {
101-
// Multiple rules
102-
const self = this;
103-
const checks = schemaType.map(r => this.compileSchemaType(r));
104-
105-
return function(value, _, path, parent) {
106-
const errors = [];
107-
const checksLength = checks.length;
108-
for (let i = 0; i < checksLength; i++) {
109-
const res = checks[i](value, undefined, path, parent);
110-
111-
if (res !== true) {
112-
self.handleResult(errors, path, res);
113-
} else {
114-
// Jump out after first success and clear previous errors
115-
return true;
116-
}
117-
}
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+
}
118129

119-
return errors;
120-
};
130+
return rules;
121131
}
122132

123133
return this.compileSchemaRule(schemaType);
@@ -130,69 +140,116 @@ Validator.prototype.compileSchemaRule = function(schemaRule) {
130140
};
131141
}
132142

133-
const checkRule = this.rules[schemaRule.type];
134-
if (!checkRule) {
143+
const ruleFunction = this.rules[schemaRule.type];
144+
if (!ruleFunction) {
135145
throw new Error("Invalid '" + schemaRule.type + "' type in validator schema!");
136146
}
137147

138-
const self = this;
139-
let checkContents = null;
148+
let dataParameter = null;
149+
let dataFunction = null;
150+
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+
}
140158

141-
if (schemaRule.type === "object") {
142-
if (schemaRule.props) {
143-
checkContents = this.compileSchemaObject(schemaRule.props);
144-
}
145-
} else if (schemaRule.type === "array") {
146-
if (schemaRule.items) {
147-
checkContents = this.compileSchemaArray(schemaRule.items);
159+
return {
160+
schemaRule: schemaRule,
161+
ruleFunction: ruleFunction,
162+
dataFunction: dataFunction,
163+
dataParameter: dataParameter
164+
};
165+
};
166+
167+
Validator.prototype.checkSchemaObject = function(value, compiledObject, path, parent) {
168+
169+
if (compiledObject instanceof Function) {
170+
return compiledObject(value, undefined, path, parent);
171+
}
172+
173+
const errors = [];
174+
const checksLength = compiledObject.length;
175+
for (let i = 0; i < checksLength; i++) {
176+
const check = compiledObject[i];
177+
const propertyPath = (path !== undefined ? path + "." : "") + check.name;
178+
const res = this.checkSchemaType(value[check.name], check.compiledType, propertyPath, value);
179+
180+
if (res !== true) {
181+
this.handleResult(errors, propertyPath, res);
148182
}
149183
}
150184

151-
return function(value, _, path, parent) {
185+
return errors.length === 0 ? true : errors;
186+
}
187+
188+
Validator.prototype.checkSchemaType = function(value, compiledType, path, parent) {
189+
if (Array.isArray(compiledType)) {
152190
const errors = [];
153-
if (value === undefined || value === null) {
154-
if (schemaRule.type === "forbidden")
155-
return true;
191+
const checksLength = compiledType.length;
192+
for (let i = 0; i < checksLength; i++) {
193+
// Always compiled to list of rules
194+
const res = this.checkSchemaRule(value, compiledType[i], path, parent);
156195

157-
if (schemaRule.optional === true)
196+
if (res !== true) {
197+
this.handleResult(errors, path, res);
198+
} else {
199+
// Jump out after first success and clear previous errors
158200
return true;
159-
160-
self.handleResult(errors, path, self.makeError("required"));
161-
return errors;
201+
}
162202
}
163203

164-
const res = checkRule.call(self, value, schemaRule, path, parent);
204+
return errors;
205+
}
206+
207+
return this.checkSchemaRule(value, compiledType, path, parent);
208+
}
209+
210+
Validator.prototype.checkSchemaArray = function(value, compiledArray, path, parent) {
211+
const errors = [];
212+
const valueLength = value.length;
213+
214+
for (let i = 0; i < valueLength; i++) {
215+
const itemPath = (path !== undefined ? path : "") + "[" + i + "]";
216+
const res = this.checkSchemaType(value[i], compiledArray, itemPath, value);
217+
165218
if (res !== true) {
166-
self.handleResult(errors, path, res);
167-
return errors;
219+
this.handleResult(errors, itemPath, res);
168220
}
221+
}
169222

170-
if (checkContents !== null) {
171-
return checkContents(value, undefined, path, parent);
172-
}
223+
return errors.length === 0 ? true : errors;
224+
}
173225

174-
return true;
175-
}
176-
};
226+
Validator.prototype.checkSchemaRule = function(value, compiledRule, path, parent) {
227+
const schemaRule = compiledRule.schemaRule;
177228

178-
Validator.prototype.compileSchemaArray = function(schemaType) {
179-
const self = this;
180-
const checkArrayItem = this.compileSchemaType(schemaType);
229+
if (value === undefined || value === null) {
230+
if (schemaRule.type === "forbidden")
231+
return true;
232+
233+
if (schemaRule.optional === true)
234+
return true;
181235

182-
return function (value, _, path, parent) {
183236
const errors = [];
184-
const valueLength = value.length;
185-
for (let i = 0; i < valueLength; i++) {
186-
const arrayItemPath = (path !== undefined ? path : "") + "[" + i + "]";
187-
const res = checkArrayItem(value[i], undefined, arrayItemPath, value);
237+
this.handleResult(errors, path, this.makeError("required"));
238+
return errors;
239+
}
188240

189-
if (res !== true) {
190-
self.handleResult(errors, arrayItemPath, res);
191-
}
192-
}
241+
const res = compiledRule.ruleFunction.call(this, value, schemaRule, path, parent);
242+
if (res !== true) {
243+
const errors = [];
244+
this.handleResult(errors, path, res);
245+
return errors;
246+
}
193247

194-
return errors.length === 0 ? true : errors;
248+
if (compiledRule.dataFunction !== null) {
249+
return compiledRule.dataFunction.call(this, value, compiledRule.dataParameter, path, parent);
195250
}
251+
252+
return true;
196253
};
197254

198255
/**

0 commit comments

Comments
 (0)