You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					624 lines
				
				18 KiB
			
		
		
			
		
	
	
					624 lines
				
				18 KiB
			| 
											3 years ago
										 | var genobj = require('generate-object-property') | ||
|  | var genfun = require('generate-function') | ||
|  | var jsonpointer = require('jsonpointer') | ||
|  | var xtend = require('xtend') | ||
|  | var formats = require('./formats') | ||
|  | 
 | ||
|  | var get = function(obj, additionalSchemas, ptr) { | ||
|  | 
 | ||
|  |   var visit = function(sub) { | ||
|  |     if (sub && sub.id === ptr) return sub | ||
|  |     if (typeof sub !== 'object' || !sub) return null | ||
|  |     return Object.keys(sub).reduce(function(res, k) { | ||
|  |       return res || visit(sub[k]) | ||
|  |     }, null) | ||
|  |   } | ||
|  | 
 | ||
|  |   var res = visit(obj) | ||
|  |   if (res) return res | ||
|  | 
 | ||
|  |   ptr = ptr.replace(/^#/, '') | ||
|  |   ptr = ptr.replace(/\/$/, '') | ||
|  | 
 | ||
|  |   try { | ||
|  |     return jsonpointer.get(obj, decodeURI(ptr)) | ||
|  |   } catch (err) { | ||
|  |     var end = ptr.indexOf('#') | ||
|  |     var other | ||
|  |     // external reference
 | ||
|  |     if (end !== 0) { | ||
|  |       // fragment doesn't exist.
 | ||
|  |       if (end === -1) { | ||
|  |         other = additionalSchemas[ptr] | ||
|  |       } else { | ||
|  |         var ext = ptr.slice(0, end) | ||
|  |         other = additionalSchemas[ext] | ||
|  |         var fragment = ptr.slice(end).replace(/^#/, '') | ||
|  |         try { | ||
|  |           return jsonpointer.get(other, fragment) | ||
|  |         } catch (err) {} | ||
|  |       } | ||
|  |     } else { | ||
|  |       other = additionalSchemas[ptr] | ||
|  |     } | ||
|  |     return other || null | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | var types = {} | ||
|  | 
 | ||
|  | types.any = function() { | ||
|  |   return 'true' | ||
|  | } | ||
|  | 
 | ||
|  | types.null = function(name) { | ||
|  |   return name+' === null' | ||
|  | } | ||
|  | 
 | ||
|  | types.boolean = function(name) { | ||
|  |   return 'typeof '+name+' === "boolean"' | ||
|  | } | ||
|  | 
 | ||
|  | types.array = function(name) { | ||
|  |   return 'Array.isArray('+name+')' | ||
|  | } | ||
|  | 
 | ||
|  | types.object = function(name) { | ||
|  |   return 'typeof '+name+' === "object" && '+name+' && !Array.isArray('+name+')' | ||
|  | } | ||
|  | 
 | ||
|  | types.number = function(name) { | ||
|  |   return 'typeof '+name+' === "number" && isFinite('+name+')' | ||
|  | } | ||
|  | 
 | ||
|  | types.integer = function(name) { | ||
|  |   return 'typeof '+name+' === "number" && (Math.floor('+name+') === '+name+' || '+name+' > 9007199254740992 || '+name+' < -9007199254740992)' | ||
|  | } | ||
|  | 
 | ||
|  | types.string = function(name) { | ||
|  |   return 'typeof '+name+' === "string"' | ||
|  | } | ||
|  | 
 | ||
|  | var unique = function(array, len) { | ||
|  |   len = Math.min(len === -1 ? array.length : len, array.length) | ||
|  |   var list = [] | ||
|  |   for (var i = 0; i < len; i++) { | ||
|  |     list.push(typeof array[i] === 'object' ? JSON.stringify(array[i]) : array[i]) | ||
|  |   } | ||
|  |   for (var i = 1; i < list.length; i++) { | ||
|  |     if (list.indexOf(list[i]) !== i) return false | ||
|  |   } | ||
|  |   return true | ||
|  | } | ||
|  | 
 | ||
|  | var isMultipleOf = function(name, multipleOf) { | ||
|  |   var res; | ||
|  |   var factor = ((multipleOf | 0) !== multipleOf) ? Math.pow(10, multipleOf.toString().split('.').pop().length) : 1 | ||
|  |   if (factor > 1) { | ||
|  |     var factorName = ((name | 0) !== name) ? Math.pow(10, name.toString().split('.').pop().length) : 1 | ||
|  |     if (factorName > factor) res = true | ||
|  |     else res = Math.round(factor * name) % (factor * multipleOf) | ||
|  |   } | ||
|  |   else res = name % multipleOf; | ||
|  |   return !res; | ||
|  | } | ||
|  | 
 | ||
|  | var testLimitedRegex = function (r, s, maxLength) { | ||
|  |   if (maxLength > -1 && s.length > maxLength) return true | ||
|  |   return r.test(s) | ||
|  | } | ||
|  | 
 | ||
|  | var compile = function(schema, cache, root, reporter, opts) { | ||
|  |   var fmts = opts ? xtend(formats, opts.formats) : formats | ||
|  |   var scope = {unique:unique, formats:fmts, isMultipleOf:isMultipleOf, testLimitedRegex:testLimitedRegex} | ||
|  |   var verbose = opts ? !!opts.verbose : false; | ||
|  |   var greedy = opts && opts.greedy !== undefined ? | ||
|  |     opts.greedy : false; | ||
|  | 
 | ||
|  |   var syms = {} | ||
|  |   var allocated = [] | ||
|  |   var gensym = function(name) { | ||
|  |     var res = name+(syms[name] = (syms[name] || 0)+1) | ||
|  |     allocated.push(res) | ||
|  |     return res | ||
|  |   } | ||
|  | 
 | ||
|  |   var formatName = function(field) { | ||
|  |     var s = JSON.stringify(field) | ||
|  |     try { | ||
|  |       var pattern = /\[([^\[\]"]+)\]/ | ||
|  |       while (pattern.test(s)) s = s.replace(pattern, replacer) | ||
|  |       return s | ||
|  |     } catch (_) { | ||
|  |       return JSON.stringify(field) | ||
|  |     } | ||
|  | 
 | ||
|  |     function replacer (match, v) { | ||
|  |       if (allocated.indexOf(v) === -1) throw new Error('Unreplaceable') | ||
|  |       return '." + ' + v + ' + "' | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   var reversePatterns = {} | ||
|  |   var patterns = function(p) { | ||
|  |     if (reversePatterns[p]) return reversePatterns[p] | ||
|  |     var n = gensym('pattern') | ||
|  |     scope[n] = new RegExp(p) | ||
|  |     reversePatterns[p] = n | ||
|  |     return n | ||
|  |   } | ||
|  | 
 | ||
|  |   var vars = ['i','j','k','l','m','n','o','p','q','r','s','t','u','v','x','y','z'] | ||
|  |   var genloop = function() { | ||
|  |     var v = vars.shift() | ||
|  |     vars.push(v+v[0]) | ||
|  |     allocated.push(v) | ||
|  |     return v | ||
|  |   } | ||
|  | 
 | ||
|  |   var visit = function(name, node, reporter, filter, schemaPath) { | ||
|  |     var properties = node.properties | ||
|  |     var type = node.type | ||
|  |     var tuple = false | ||
|  | 
 | ||
|  |     if (Array.isArray(node.items)) { // tuple type
 | ||
|  |       properties = {} | ||
|  |       node.items.forEach(function(item, i) { | ||
|  |         properties[i] = item | ||
|  |       }) | ||
|  |       type = 'array' | ||
|  |       tuple = true | ||
|  |     } | ||
|  | 
 | ||
|  |     var indent = 0 | ||
|  |     var error = function(msg, prop, value) { | ||
|  |       validate('errors++') | ||
|  |       if (reporter === true) { | ||
|  |         validate('if (validate.errors === null) validate.errors = []') | ||
|  |         if (verbose) { | ||
|  |           validate( | ||
|  |             'validate.errors.push({field:%s,message:%s,value:%s,type:%s,schemaPath:%s})', | ||
|  |             formatName(prop || name), | ||
|  |             JSON.stringify(msg), | ||
|  |             value || name, | ||
|  |             JSON.stringify(type), | ||
|  |             JSON.stringify(schemaPath) | ||
|  |           ) | ||
|  |         } else { | ||
|  |           validate('validate.errors.push({field:%s,message:%s})', formatName(prop || name), JSON.stringify(msg)) | ||
|  |         } | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     if (node.required === true) { | ||
|  |       indent++ | ||
|  |       validate('if (%s === undefined) {', name) | ||
|  |       error('is required') | ||
|  |       validate('} else {') | ||
|  |     } else { | ||
|  |       indent++ | ||
|  |       validate('if (%s !== undefined) {', name) | ||
|  |     } | ||
|  | 
 | ||
|  |     var valid = [].concat(type) | ||
|  |       .map(function(t) { | ||
|  |         if (t && !types.hasOwnProperty(t)) { | ||
|  |           throw new Error('Unknown type: ' + t) | ||
|  |         } | ||
|  | 
 | ||
|  |         return types[t || 'any'](name) | ||
|  |       }) | ||
|  |       .join(' || ') || 'true' | ||
|  | 
 | ||
|  |     if (valid !== 'true') { | ||
|  |       indent++ | ||
|  |       validate('if (!(%s)) {', valid) | ||
|  |       error('is the wrong type') | ||
|  |       validate('} else {') | ||
|  |     } | ||
|  | 
 | ||
|  |     if (tuple) { | ||
|  |       if (node.additionalItems === false) { | ||
|  |         validate('if (%s.length > %d) {', name, node.items.length) | ||
|  |         error('has additional items') | ||
|  |         validate('}') | ||
|  |       } else if (node.additionalItems) { | ||
|  |         var i = genloop() | ||
|  |         validate('for (var %s = %d; %s < %s.length; %s++) {', i, node.items.length, i, name, i) | ||
|  |         visit(name+'['+i+']', node.additionalItems, reporter, filter, schemaPath.concat('additionalItems')) | ||
|  |         validate('}') | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     if (node.format && fmts[node.format]) { | ||
|  |       if (type !== 'string' && formats[node.format]) validate('if (%s) {', types.string(name)) | ||
|  |       var n = gensym('format') | ||
|  |       scope[n] = fmts[node.format] | ||
|  | 
 | ||
|  |       if (typeof scope[n] === 'function') validate('if (!%s(%s)) {', n, name) | ||
|  |       else validate('if (!testLimitedRegex(%s, %s, %d)) {', n, name, typeof node.maxLength === 'undefined' ? -1 : node.maxLength) | ||
|  |       error('must be '+node.format+' format') | ||
|  |       validate('}') | ||
|  |       if (type !== 'string' && formats[node.format]) validate('}') | ||
|  |     } | ||
|  | 
 | ||
|  |     if (Array.isArray(node.required)) { | ||
|  |       var n = gensym('missing') | ||
|  |       validate('var %s = 0', n) | ||
|  |       var checkRequired = function (req) { | ||
|  |         var prop = genobj(name, req); | ||
|  |         validate('if (%s === undefined) {', prop) | ||
|  |         error('is required', prop) | ||
|  |         validate('%s++', n) | ||
|  |         validate('}') | ||
|  |       } | ||
|  |       validate('if ((%s)) {', type !== 'object' ? types.object(name) : 'true') | ||
|  |       node.required.map(checkRequired) | ||
|  |       validate('}'); | ||
|  |       if (!greedy) { | ||
|  |         validate('if (%s === 0) {', n) | ||
|  |         indent++ | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     if (node.uniqueItems) { | ||
|  |       if (type !== 'array') validate('if (%s) {', types.array(name)) | ||
|  |       validate('if (!(unique(%s, %d))) {', name, node.maxItems || -1) | ||
|  |       error('must be unique') | ||
|  |       validate('}') | ||
|  |       if (type !== 'array') validate('}') | ||
|  |     } | ||
|  | 
 | ||
|  |     if (node.enum) { | ||
|  |       var complex = node.enum.some(function(e) { | ||
|  |         return typeof e === 'object' | ||
|  |       }) | ||
|  | 
 | ||
|  |       var compare = complex ? | ||
|  |         function(e) { | ||
|  |           return 'JSON.stringify('+name+')'+' !== JSON.stringify('+JSON.stringify(e)+')' | ||
|  |         } : | ||
|  |         function(e) { | ||
|  |           return name+' !== '+JSON.stringify(e) | ||
|  |         } | ||
|  | 
 | ||
|  |       validate('if (%s) {', node.enum.map(compare).join(' && ') || 'false') | ||
|  |       error('must be an enum value') | ||
|  |       validate('}') | ||
|  |     } | ||
|  | 
 | ||
|  |     if (node.dependencies) { | ||
|  |       if (type !== 'object') validate('if (%s) {', types.object(name)) | ||
|  | 
 | ||
|  |       Object.keys(node.dependencies).forEach(function(key) { | ||
|  |         var deps = node.dependencies[key] | ||
|  |         if (typeof deps === 'string') deps = [deps] | ||
|  | 
 | ||
|  |         var exists = function(k) { | ||
|  |           return genobj(name, k) + ' !== undefined' | ||
|  |         } | ||
|  | 
 | ||
|  |         if (Array.isArray(deps)) { | ||
|  |           validate('if (%s !== undefined && !(%s)) {', genobj(name, key), deps.map(exists).join(' && ') || 'true') | ||
|  |           error('dependencies not set') | ||
|  |           validate('}') | ||
|  |         } | ||
|  |         if (typeof deps === 'object') { | ||
|  |           validate('if (%s !== undefined) {', genobj(name, key)) | ||
|  |           visit(name, deps, reporter, filter, schemaPath.concat(['dependencies', key])) | ||
|  |           validate('}') | ||
|  |         } | ||
|  |       }) | ||
|  | 
 | ||
|  |       if (type !== 'object') validate('}') | ||
|  |     } | ||
|  | 
 | ||
|  |     if (node.additionalProperties || node.additionalProperties === false) { | ||
|  |       if (type !== 'object') validate('if (%s) {', types.object(name)) | ||
|  | 
 | ||
|  |       var i = genloop() | ||
|  |       var keys = gensym('keys') | ||
|  | 
 | ||
|  |       var toCompare = function(p) { | ||
|  |         return keys+'['+i+'] !== '+JSON.stringify(p) | ||
|  |       } | ||
|  | 
 | ||
|  |       var toTest = function(p) { | ||
|  |         return '!'+patterns(p)+'.test('+keys+'['+i+'])' | ||
|  |       } | ||
|  | 
 | ||
|  |       var additionalProp = Object.keys(properties || {}).map(toCompare) | ||
|  |         .concat(Object.keys(node.patternProperties || {}).map(toTest)) | ||
|  |         .join(' && ') || 'true' | ||
|  | 
 | ||
|  |       validate('var %s = Object.keys(%s)', keys, name) | ||
|  |         ('for (var %s = 0; %s < %s.length; %s++) {', i, i, keys, i) | ||
|  |           ('if (%s) {', additionalProp) | ||
|  | 
 | ||
|  |       if (node.additionalProperties === false) { | ||
|  |         if (filter) validate('delete %s', name+'['+keys+'['+i+']]') | ||
|  |         error('has additional properties', null, JSON.stringify(name+'.') + ' + ' + keys + '['+i+']') | ||
|  |       } else { | ||
|  |         visit(name+'['+keys+'['+i+']]', node.additionalProperties, reporter, filter, schemaPath.concat(['additionalProperties'])) | ||
|  |       } | ||
|  | 
 | ||
|  |       validate | ||
|  |           ('}') | ||
|  |         ('}') | ||
|  | 
 | ||
|  |       if (type !== 'object') validate('}') | ||
|  |     } | ||
|  | 
 | ||
|  |     if (node.$ref) { | ||
|  |       var sub = get(root, opts && opts.schemas || {}, node.$ref) | ||
|  |       if (sub) { | ||
|  |         var fn = cache[node.$ref] | ||
|  |         if (!fn) { | ||
|  |           cache[node.$ref] = function proxy(data) { | ||
|  |             return fn(data) | ||
|  |           } | ||
|  |           fn = compile(sub, cache, root, false, opts) | ||
|  |         } | ||
|  |         var n = gensym('ref') | ||
|  |         scope[n] = fn | ||
|  |         validate('if (!(%s(%s))) {', n, name) | ||
|  |         error('referenced schema does not match') | ||
|  |         validate('}') | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     if (node.not) { | ||
|  |       var prev = gensym('prev') | ||
|  |       validate('var %s = errors', prev) | ||
|  |       visit(name, node.not, false, filter, schemaPath.concat('not')) | ||
|  |       validate('if (%s === errors) {', prev) | ||
|  |       error('negative schema matches') | ||
|  |       validate('} else {') | ||
|  |         ('errors = %s', prev) | ||
|  |       ('}') | ||
|  |     } | ||
|  | 
 | ||
|  |     if (node.items && !tuple) { | ||
|  |       if (type !== 'array') validate('if (%s) {', types.array(name)) | ||
|  | 
 | ||
|  |       var i = genloop() | ||
|  |       validate('for (var %s = 0; %s < %s.length; %s++) {', i, i, name, i) | ||
|  |       visit(name+'['+i+']', node.items, reporter, filter, schemaPath.concat('items')) | ||
|  |       validate('}') | ||
|  | 
 | ||
|  |       if (type !== 'array') validate('}') | ||
|  |     } | ||
|  | 
 | ||
|  |     if (node.patternProperties) { | ||
|  |       if (type !== 'object') validate('if (%s) {', types.object(name)) | ||
|  |       var keys = gensym('keys') | ||
|  |       var i = genloop() | ||
|  |       validate | ||
|  |         ('var %s = Object.keys(%s)', keys, name) | ||
|  |         ('for (var %s = 0; %s < %s.length; %s++) {', i, i, keys, i) | ||
|  | 
 | ||
|  |       Object.keys(node.patternProperties).forEach(function(key) { | ||
|  |         var p = patterns(key) | ||
|  |         validate('if (%s.test(%s)) {', p, keys+'['+i+']') | ||
|  |         visit(name+'['+keys+'['+i+']]', node.patternProperties[key], reporter, filter, schemaPath.concat(['patternProperties', key])) | ||
|  |         validate('}') | ||
|  |       }) | ||
|  | 
 | ||
|  |       validate('}') | ||
|  |       if (type !== 'object') validate('}') | ||
|  |     } | ||
|  | 
 | ||
|  |     if (node.pattern) { | ||
|  |       var p = patterns(node.pattern) | ||
|  |       if (type !== 'string') validate('if (%s) {', types.string(name)) | ||
|  |       validate('if (!(testLimitedRegex(%s, %s, %d))) {', p, name, typeof node.maxLength === 'undefined' ? -1 : node.maxLength) | ||
|  |       error('pattern mismatch') | ||
|  |       validate('}') | ||
|  |       if (type !== 'string') validate('}') | ||
|  |     } | ||
|  | 
 | ||
|  |     if (node.allOf) { | ||
|  |       node.allOf.forEach(function(sch, key) { | ||
|  |         visit(name, sch, reporter, filter, schemaPath.concat(['allOf', key])) | ||
|  |       }) | ||
|  |     } | ||
|  | 
 | ||
|  |     if (node.anyOf && node.anyOf.length) { | ||
|  |       var prev = gensym('prev') | ||
|  | 
 | ||
|  |       node.anyOf.forEach(function(sch, i) { | ||
|  |         if (i === 0) { | ||
|  |           validate('var %s = errors', prev) | ||
|  |         } else { | ||
|  |           validate('if (errors !== %s) {', prev) | ||
|  |             ('errors = %s', prev) | ||
|  |         } | ||
|  |         visit(name, sch, false, false, schemaPath) | ||
|  |       }) | ||
|  |       node.anyOf.forEach(function(sch, i) { | ||
|  |         if (i) validate('}') | ||
|  |       }) | ||
|  |       validate('if (%s !== errors) {', prev) | ||
|  |       error('no schemas match') | ||
|  |       validate('}') | ||
|  |     } | ||
|  | 
 | ||
|  |     if (node.oneOf && node.oneOf.length) { | ||
|  |       var prev = gensym('prev') | ||
|  |       var passes = gensym('passes') | ||
|  | 
 | ||
|  |       validate | ||
|  |         ('var %s = errors', prev) | ||
|  |         ('var %s = 0', passes) | ||
|  | 
 | ||
|  |       node.oneOf.forEach(function(sch, i) { | ||
|  |         visit(name, sch, false, false, schemaPath) | ||
|  |         validate('if (%s === errors) {', prev) | ||
|  |           ('%s++', passes) | ||
|  |         ('} else {') | ||
|  |           ('errors = %s', prev) | ||
|  |         ('}') | ||
|  |       }) | ||
|  | 
 | ||
|  |       validate('if (%s !== 1) {', passes) | ||
|  |       error('no (or more than one) schemas match') | ||
|  |       validate('}') | ||
|  |     } | ||
|  | 
 | ||
|  |     if (node.multipleOf !== undefined) { | ||
|  |       if (type !== 'number' && type !== 'integer') validate('if (%s) {', types.number(name)) | ||
|  | 
 | ||
|  |       validate('if (!isMultipleOf(%s, %d)) {', name, node.multipleOf) | ||
|  | 
 | ||
|  |       error('has a remainder') | ||
|  |       validate('}') | ||
|  | 
 | ||
|  |       if (type !== 'number' && type !== 'integer') validate('}') | ||
|  |     } | ||
|  | 
 | ||
|  |     if (node.maxProperties !== undefined) { | ||
|  |       if (type !== 'object') validate('if (%s) {', types.object(name)) | ||
|  | 
 | ||
|  |       validate('if (Object.keys(%s).length > %d) {', name, node.maxProperties) | ||
|  |       error('has more properties than allowed') | ||
|  |       validate('}') | ||
|  | 
 | ||
|  |       if (type !== 'object') validate('}') | ||
|  |     } | ||
|  | 
 | ||
|  |     if (node.minProperties !== undefined) { | ||
|  |       if (type !== 'object') validate('if (%s) {', types.object(name)) | ||
|  | 
 | ||
|  |       validate('if (Object.keys(%s).length < %d) {', name, node.minProperties) | ||
|  |       error('has less properties than allowed') | ||
|  |       validate('}') | ||
|  | 
 | ||
|  |       if (type !== 'object') validate('}') | ||
|  |     } | ||
|  | 
 | ||
|  |     if (node.maxItems !== undefined) { | ||
|  |       if (type !== 'array') validate('if (%s) {', types.array(name)) | ||
|  | 
 | ||
|  |       validate('if (%s.length > %d) {', name, node.maxItems) | ||
|  |       error('has more items than allowed') | ||
|  |       validate('}') | ||
|  | 
 | ||
|  |       if (type !== 'array') validate('}') | ||
|  |     } | ||
|  | 
 | ||
|  |     if (node.minItems !== undefined) { | ||
|  |       if (type !== 'array') validate('if (%s) {', types.array(name)) | ||
|  | 
 | ||
|  |       validate('if (%s.length < %d) {', name, node.minItems) | ||
|  |       error('has less items than allowed') | ||
|  |       validate('}') | ||
|  | 
 | ||
|  |       if (type !== 'array') validate('}') | ||
|  |     } | ||
|  | 
 | ||
|  |     if (node.maxLength !== undefined) { | ||
|  |       if (type !== 'string') validate('if (%s) {', types.string(name)) | ||
|  | 
 | ||
|  |       validate('if (%s.length > %d) {', name, node.maxLength) | ||
|  |       error('has longer length than allowed') | ||
|  |       validate('}') | ||
|  | 
 | ||
|  |       if (type !== 'string') validate('}') | ||
|  |     } | ||
|  | 
 | ||
|  |     if (node.minLength !== undefined) { | ||
|  |       if (type !== 'string') validate('if (%s) {', types.string(name)) | ||
|  | 
 | ||
|  |       validate('if (%s.length < %d) {', name, node.minLength) | ||
|  |       error('has less length than allowed') | ||
|  |       validate('}') | ||
|  | 
 | ||
|  |       if (type !== 'string') validate('}') | ||
|  |     } | ||
|  | 
 | ||
|  |     if (node.minimum !== undefined) { | ||
|  |       if (type !== 'number' && type !== 'integer') validate('if (%s) {', types.number(name)) | ||
|  | 
 | ||
|  |       validate('if (%s %s %d) {', name, node.exclusiveMinimum ? '<=' : '<', node.minimum) | ||
|  |       error('is less than minimum') | ||
|  |       validate('}') | ||
|  | 
 | ||
|  |       if (type !== 'number' && type !== 'integer') validate('}') | ||
|  |     } | ||
|  | 
 | ||
|  |     if (node.maximum !== undefined) { | ||
|  |       if (type !== 'number' && type !== 'integer') validate('if (%s) {', types.number(name)) | ||
|  | 
 | ||
|  |       validate('if (%s %s %d) {', name, node.exclusiveMaximum ? '>=' : '>', node.maximum) | ||
|  |       error('is more than maximum') | ||
|  |       validate('}') | ||
|  | 
 | ||
|  |       if (type !== 'number' && type !== 'integer') validate('}') | ||
|  |     } | ||
|  | 
 | ||
|  |     if (properties) { | ||
|  |       Object.keys(properties).forEach(function(p) { | ||
|  |         if (Array.isArray(type) && type.indexOf('null') !== -1) validate('if (%s !== null) {', name) | ||
|  | 
 | ||
|  |         visit( | ||
|  |           genobj(name, p), | ||
|  |           properties[p], | ||
|  |           reporter, | ||
|  |           filter, | ||
|  |           schemaPath.concat(tuple ? p : ['properties', p]) | ||
|  |         ) | ||
|  | 
 | ||
|  |         if (Array.isArray(type) && type.indexOf('null') !== -1) validate('}') | ||
|  |       }) | ||
|  |     } | ||
|  | 
 | ||
|  |     while (indent--) validate('}') | ||
|  |   } | ||
|  | 
 | ||
|  |   var validate = genfun | ||
|  |     ('function validate(data) {') | ||
|  |       // Since undefined is not a valid JSON value, we coerce to null and other checks will catch this
 | ||
|  |       ('if (data === undefined) data = null') | ||
|  |       ('validate.errors = null') | ||
|  |       ('var errors = 0') | ||
|  | 
 | ||
|  |   visit('data', schema, reporter, opts && opts.filter, []) | ||
|  | 
 | ||
|  |   validate | ||
|  |       ('return errors === 0') | ||
|  |     ('}') | ||
|  | 
 | ||
|  |   validate = validate.toFunction(scope) | ||
|  |   validate.errors = null | ||
|  | 
 | ||
|  |   if (Object.defineProperty) { | ||
|  |     Object.defineProperty(validate, 'error', { | ||
|  |       get: function() { | ||
|  |         if (!validate.errors) return '' | ||
|  |         return validate.errors.map(function(err) { | ||
|  |           return err.field + ' ' + err.message; | ||
|  |         }).join('\n') | ||
|  |       } | ||
|  |     }) | ||
|  |   } | ||
|  | 
 | ||
|  |   validate.toJSON = function() { | ||
|  |     return schema | ||
|  |   } | ||
|  | 
 | ||
|  |   return validate | ||
|  | } | ||
|  | 
 | ||
|  | module.exports = function(schema, opts) { | ||
|  |   if (typeof schema === 'string') schema = JSON.parse(schema) | ||
|  |   return compile(schema, {}, schema, true, opts) | ||
|  | } | ||
|  | 
 | ||
|  | module.exports.filter = function(schema, opts) { | ||
|  |   var validate = module.exports(schema, xtend(opts, {filter: true})) | ||
|  |   return function(sch) { | ||
|  |     validate(sch) | ||
|  |     return sch | ||
|  |   } | ||
|  | } |