@@ -59,190 +59,196 @@ Validator.prototype.validate = function(obj, schema) {
5959 * @throws {Error } Invalid schema
6060 */
6161Validator . 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