diff --git a/lib/jsen.js b/lib/jsen.js index 78c5718..68567c9 100644 --- a/lib/jsen.js +++ b/lib/jsen.js @@ -80,54 +80,10 @@ types.date = function (path) { return path + ' instanceof Date'; }; -keywords.type = function (context) { - if (!context.schema.type) { - return; - } - - var specified = Array.isArray(context.schema.type) ? context.schema.type : [context.schema.type], - src = specified.map(function mapType(type) { - return types[type] ? types[type](context.path) : 'true'; - }).join(' || '); - - if (src) { - context.code('if (!(' + src + ')) {'); +keywords.enum = function (context) { + var arr = context.schema['enum']; - context.error('type'); - - context.code('}'); - } -}; - -keywords['enum'] = function (context) { - var arr = context.schema['enum'], - clauses = [], - value, enumType, i; - - if (!Array.isArray(arr) || !arr.length) { - return; - } - - for (i = 0; i < arr.length; i++) { - value = arr[i]; - enumType = typeof value; - - if (value === null || ['boolean', 'number', 'string'].indexOf(enumType) > -1) { - // simple equality check for simple data types - if (enumType === 'string') { - clauses.push(context.path + ' === ' + encodeStr(value)); - } - else { - clauses.push(context.path + ' === ' + value); - } - } - else { - // deep equality check for complex types or regexes - clauses.push('equal(' + context.path + ', ' + JSON.stringify(value) + ')'); - } - } - - context.code('if (!(' + clauses.join(' || ') + ')) {'); + context.code('if (!equalAny(' + context.path + ', ' + JSON.stringify(arr) + ')) {'); context.error('enum'); context.code('}'); }; @@ -556,46 +512,71 @@ keywords.not = function (context) { ('}'); }; +function decorateGenerator(type, keyword) { + keywords[keyword].type = type; + keywords[keyword].keyword = keyword; +} + ['minimum', 'exclusiveMinimum', 'maximum', 'exclusiveMaximum', 'multipleOf'] - .forEach(function (keyword) { keywords[keyword].type = 'number'; }); + .forEach(decorateGenerator.bind(null, 'number')); ['minLength', 'maxLength', 'pattern', 'format'] - .forEach(function (keyword) { keywords[keyword].type = 'string'; }); + .forEach(decorateGenerator.bind(null, 'string')); ['minItems', 'maxItems', 'additionalItems', 'uniqueItems', 'items'] - .forEach(function (keyword) { keywords[keyword].type = 'array'; }); + .forEach(decorateGenerator.bind(null, 'array')); ['maxProperties', 'minProperties', 'required', 'properties', 'patternProperties', 'additionalProperties', 'dependencies'] - .forEach(function (keyword) { keywords[keyword].type = 'object'; }); + .forEach(decorateGenerator.bind(null, 'object')); + +['enum', 'allOf', 'anyOf', 'oneOf', 'not'] + .forEach(decorateGenerator.bind(null, null)); -function getGenerators(schema) { +function groupKeywords(schema) { var keys = Object.keys(schema), - start = [], - perType = {}, - gen, i; + ret = { + enum: schema.enum instanceof Array && schema.enum.length > 0, + type: null, + allType: [], + perType: {} + }, + key, gen, i; + + if (schema.type) { + if (typeof schema.type === 'string') { + ret.type = [schema.type]; + } + else if (schema.type instanceof Array && schema.type.length) { + ret.type = schema.type.slice(0); + } + } for (i = 0; i < keys.length; i++) { - gen = keywords[keys[i]]; + key = keys[i]; + + if (key === 'enum' || key === 'type') { + continue; + } + + gen = keywords[key]; if (!gen) { continue; } if (gen.type) { - if (!perType[gen.type]) { - perType[gen.type] = []; + if (!ret.perType[gen.type]) { + ret.perType[gen.type] = []; } - perType[gen.type].push(gen); + ret.perType[gen.type].push(key); } else { - start.push(gen); + ret.allType.push(key); } } - return start.concat(Object.keys(perType).reduce(function (arr, key) { - return arr.concat(perType[key]); - }, [])); + return ret; } function getPathExpression(path, key) { @@ -684,6 +665,16 @@ function clone(obj) { return cloned; } +function equalAny(obj, options) { + for (var i = 0, len = options.length; i < len; i++) { + if (equal(obj, options[i])) { + return true; + } + } + + return false; +} + function PropertyMarker() { this.objects = []; this.properties = []; @@ -813,7 +804,7 @@ function jsen(schema, options) { errors: [] }, scope = { - equal: equal, + equalAny: equalAny, unique: unique, ucs2length: ucs2length, refs: refs @@ -863,10 +854,11 @@ function jsen(schema, options) { pathExp, index, err, - lastType, format, - gens, - gen, + schemaKeys, + typeKeys, + typeIndex, + validatedType, i; function error(keyword, key, additional) { @@ -932,28 +924,57 @@ function jsen(schema, options) { noFailFast: noFailFast }; - gens = getGenerators(schema); + schemaKeys = groupKeywords(schema); - for (i = 0; i < gens.length; i++) { - gen = gens[i]; + if (schemaKeys.enum) { + keywords.enum(context); - if (gen.type && lastType !== gen.type) { - if (lastType) { - code('}'); - } - - lastType = gen.type; + return; // do not process the schema further + } - code('if (' + types[gen.type](path) + ') {'); - } + typeKeys = Object.keys(schemaKeys.perType); - gen(context); + function generateForKeyword(keyword) { + keywords[keyword](context); } - if (lastType) { + for (i = 0; i < typeKeys.length; i++) { + validatedType = typeKeys[i]; + + code((i ? 'else ' : '') + 'if (' + types[validatedType](path) + ') {'); + + schemaKeys.perType[validatedType].forEach(generateForKeyword); + code('}'); + + if (schemaKeys.type) { + typeIndex = schemaKeys.type.indexOf(validatedType); + + if (typeIndex > -1) { + schemaKeys.type.splice(typeIndex, 1); + } + } } + if (schemaKeys.type) { // we have types in the schema + if (schemaKeys.type.length) { // case 1: we still have some left to check + code((typeKeys.length ? 'else ' : '') + 'if (!(' + schemaKeys.type.map(function (type) { + return types[type] ? types[type](path) : 'true'; + }).join(' || ') + ')) {'); + error('type'); + code('}'); + } + else { + code('else {'); // case 2: we don't have any left to check + error('type'); + code('}'); + } + } + + schemaKeys.allType.forEach(function (keyword) { + keywords[keyword](context); + }); + if (schema.format && options.formats) { format = options.formats[schema.format];