items, uniqueItems, patternProperties, additionalProperties

master
Evgeny Poberezkin 2015-05-29 12:42:55 +01:00
parent 26abbd96d2
commit 67762549d8
9 changed files with 531 additions and 132 deletions

View File

@ -17,6 +17,13 @@ function (data, dataType, dataPath) {
{{ var $schemaKeys = Object.keys(it.schema); }}
{{ /* sort keys so that those that are easier to fail (e.g. type) are validated sooner */ }}
{{
var $checkProperties = !it.schema.properties &&
( it.schema.patternProperties ||
it.schema.hasOwnProperty('additionalProperties') );
if ($checkProperties) $schemaKeys.push('properties');
}}
{{~ $schemaKeys:$key }}
{{ var $rule = it.RULES[$key]; }}
{{? $rule }}
@ -29,6 +36,7 @@ function (data, dataType, dataPath) {
$it.schema = it.schema[$key];
$it.schemaPath = it.schemaPath + '.' + $key;
$it.parentSchema = it.schema;
$it.parentSchemaPath = it.schemaPath;
}}
var result = ({{= $rule.code($it) }})(data, dataType, dataPath);

View File

@ -4,37 +4,14 @@ function (data, dataType, dataPath) {
var errors = [];
{{
var $simpleTypes = [ 'boolean', 'null', 'number', 'string' ]
, $itemsHash = { 'boolean': {}, 'null': {}, 'number': {}, 'string': {} };
var $onlySimpleTypes = it.schema.every(function ($item) {
var $itemType = typeof $item;
var $isSimpleType = $simpleTypes.indexOf($itemType) >= 0;
if ($isSimpleType) $itemsHash[$itemType][$item] = true;
return $isSimpleType;
var $itemsHash = {};
it.schema.forEach(function ($item) {
$itemsHash[it.stableStringify($item)] = true;
});
if (!$onlySimpleTypes) {
var $itemsList = it.schema.map(function ($item) {
return {
type: it.getDataType($item),
valueStr: it.stableStringify($item)
}
});
}
}}
{{? $onlySimpleTypes }}
var itemsHash = {{= JSON.stringify($itemsHash) }};
var valid = itemsHash[dataType] && itemsHash[dataType][data];
{{??}}
var itemsList = {{= JSON.stringify($itemsList) }};
for (var i = 0; i < itemsList.length; i++) {
var item = itemsList[i];
var valid = dataType == item.type && stableStringify(data) == item.valueStr;
if (valid) break;
}
{{?}}
var itemsHash = {{= JSON.stringify($itemsHash) }};
var valid = itemsHash[stableStringify(data)];
return {
valid: valid || false,

View File

@ -10,11 +10,11 @@ var RULES = module.exports = {
anyOf: { code: fs.readFileSync(__dirname + '/anyOf.dot.js') },
oneOf: { code: fs.readFileSync(__dirname + '/oneOf.dot.js') },
not: { code: fs.readFileSync(__dirname + '/not.dot.js') },
maximum: {
maximum: { // includes exclusiveMaximum
code: fs.readFileSync(__dirname + '/maximum.dot.js'),
type: 'number'
},
minimum: {
minimum: { // includes exclusiveMinimum
code: fs.readFileSync(__dirname + '/minimum.dot.js'),
type: 'number'
},
@ -34,14 +34,10 @@ var RULES = module.exports = {
code: fs.readFileSync(__dirname + '/pattern.dot.js'),
type: 'string'
},
// additionalItems: {
// code: '',
// type: 'array'
// },
// items: {
// code: '',
// type: 'array'
// },
items: { // includes additionalItems
code: fs.readFileSync(__dirname + '/items.dot.js'),
type: 'array'
},
maxItems: {
code: fs.readFileSync(__dirname + '/maxItems.dot.js'),
type: 'array'
@ -50,10 +46,10 @@ var RULES = module.exports = {
code: fs.readFileSync(__dirname + '/minItems.dot.js'),
type: 'array'
},
// uniqueItems: {
// code: '',
// type: 'array'
// },
uniqueItems: {
code: fs.readFileSync(__dirname + '/uniqueItems.dot.js'),
type: 'array'
},
maxProperties: {
code: fs.readFileSync(__dirname + '/maxProperties.dot.js'),
type: 'object'
@ -66,18 +62,10 @@ var RULES = module.exports = {
code: fs.readFileSync(__dirname + '/required.dot.js'),
type: 'object'
},
// additionalProperties: {
// code: '',
// type: 'object'
// },
properties: {
properties: { // includes patternProperties and additionalProperties
code: fs.readFileSync(__dirname + '/properties.dot.js'),
type: 'object'
},
// patternProperties: {
// code: '',
// type: 'object'
// },
// dependencies: {
// code: '',
// type: 'object'

View File

@ -0,0 +1,88 @@
{{## def.validateItems:startFrom:
for (var i = {{= startFrom }}; i < data.length; i++) {
var _data = data[i]
, _dataType = getDataType(_data)
, _dataPath = dataPath + '[' + i + ']'
, result = validateItems(_data, _dataType, _dataPath);
if (!result.valid) {
{{? it.opts.allErrors }}
errors.push.apply(errors, result.errors);
{{??}}
return result;
{{?}}
}
}
#}}
function (data, dataType, dataPath) {
'use strict';
var errors = [];
{{? Array.isArray(it.schema) }}
{{ var $additionalItems = it.parentSchema.additionalItems; }}
{{? $additionalItems === false }}
if (data.length > {{= it.schema.length }}) {
errors.push({
keyword: 'additionalItems',
schema: false,
dataPath: dataPath,
message: 'data' + dataPath + ' is not valid, should NOT have more than {{= it.schema.length }} items'
{{? it.opts.verbose }}, data: data{{?}}
});
{{? !it.opts.allErrors }}
return { valid: false, errors: errors };
{{?}}
}
{{?}}
{{~ it.schema:$schema:$index }}
{{? Object.keys($schema).length }}
if (data.length > {{= $index }}) {
{{
var $it = it.copy(it);
$it.schema = $schema;
$it.schemaPath = it.schemaPath + '[' + $index + ']';
}}
var _data = data[{{= $index }}]
, _dataType = getDataType(_data)
, _dataPath = dataPath + '[{{= $index }}]'
, result = ({{= it._validate($it) }})(_data, _dataType, _dataPath);
if (!result.valid) {
{{? it.opts.allErrors }}
errors.push.apply(errors, result.errors);
{{??}}
return result;
{{?}}
}
}
{{?}}
{{~}}
{{? typeof $additionalItems == 'object' && Object.keys($additionalItems).length }}
{{
var $it = it.copy(it);
$it.schema = $additionalItems;
$it.schemaPath = it.parentSchemaPath + '.additionalItems';
}}
if (data.length > {{= it.schema.length }}) {
var validateItems = ({{= it._validate($it) }});
{{# def.validateItems: it.schema.length }}
}
{{?}}
{{?? Object.keys(it.schema).length }}
var validateItems = ({{= it._validate(it) }});
{{# def.validateItems: 0 }}
{{?}}
{{? it.opts.allErrors }}
return { valid: !errors.length, errors: errors };
{{??}}
return { valid: true, errors: [] };
{{?}}
}

View File

@ -3,6 +3,7 @@ function (data, dataType, dataPath) {
var division = data / {{= it.schema }};
var valid = division === parseInt(division);
return {
valid: valid,
errors: valid ? [] : [{

View File

@ -3,29 +3,177 @@ function (data, dataType, dataPath) {
var errors = [];
{{ var $properties = Object.keys(it.schema); }}
{{
var $propertyKeys = Object.keys(it.schema || {})
, $pProperties = it.parentSchema.patternProperties || {}
, $pPropertyKeys = $pProperties && Object.keys($pProperties)
, $aProperties = it.parentSchema.additionalProperties;
}}
{{~ $properties:$property }}
{{
var $it = it.copy(it);
$it.schema = it.schema[$property];
$it.schemaPath = it.schemaPath + '.' + $property;
}}
{{
var $noAdditional = $aProperties === false
, $additionalIsSchema = typeof $aProperties == 'object'
&& Object.keys($aProperties).length
, $checkAdditional = $noAdditional || $additionalIsSchema;
}}
if (data.hasOwnProperty('{{= $property }}')) {
var _data = data['{{= $property }}']
, _dataType = getDataType(_data)
, _dataPath = dataPath + '.{{= $property }}'
, result = ({{= it._validate($it) }})(_data, _dataType, _dataPath);
{{? $checkAdditional }}
var propertiesSchema = self.schema{{= it.schemaPath }} || {};
{{?}}
if (!result.valid) {
{{? it.opts.allErrors }}
errors.push.apply(errors, result.errors);
{{? $noAdditional }}
var propertiesSchemaKeys = Object.keys(propertiesSchema);
var dataKeys = Object.keys(data);
var notValid = dataKeys.length > propertiesSchemaKeys.length;
if (notValid) {
var error = {
keyword: 'properties',
schema: propertiesSchema,
dataPath: dataPath,
message: 'data' + dataPath + ' is not valid, additional properties NOT allowed'
{{? it.opts.verbose }}, data: data{{?}}
};
{{? it.opts.allErrors }}
errors.push(error);
{{??}}
return { valid: false, errors: [error] };
{{?}}
}
{{?}}
{{? $pPropertyKeys.length }}
var pPropertiesSchema = self.schema{{= it.parentSchemaPath + '.patternProperties' }}
, pPropertiesRegexps = {}
, dataKeysPPs; /* map of arrays of applicable pattern properties */
for (var pProperty in pPropertiesSchema)
pPropertiesRegexps[pProperty] = new RegExp(pProperty);
{{?}}
{{? $checkAdditional }}
{{? $pPropertyKeys.length }}
dataKeysPPs = {};
{{?}}
for (var key in data) {
var isAdditional = !propertiesSchema.hasOwnProperty(key);
{{? $pPropertyKeys.length }}
dataKeysPPs[key] = {};
for (var pProperty in pPropertiesSchema) {
var keyMatches = pPropertiesRegexps[pProperty].test(key);
if (keyMatches) {
dataKeysPPs[key][pProperty] = true;
isAdditional = false;
}
}
{{?}}
if (isAdditional) {
{{? $noAdditional }}
var error = {
keyword: 'properties',
schema: propertiesSchema,
dataPath: dataPath,
message: 'data' + dataPath + ' is not valid, property ' + key + ' NOT allowed'
{{? it.opts.verbose }}, data: data{{?}}
};
{{? it.opts.allErrors }}
errors.push(error);
{{??}}
return { valid: false, errors: [error] };
{{?}}
{{??}}
return result;
{{
/* additionalProperties is schema */
var $it = it.copy(it);
$it.schema = $aProperties;
$it.schemaPath = it.parentSchemaPath + '.additionalProperties';
}}
var _data = data[key]
, _dataType = getDataType(_data)
, _dataPath = dataPath + '.' + key
, result = ({{= it._validate($it) }})(_data, _dataType, _dataPath);
if (!result.valid) {
{{? it.opts.allErrors }}
errors.push.apply(errors, result.errors);
{{??}}
return result;
{{?}}
}
{{?}}
}
}
{{?}}
{{~ $propertyKeys:$propertyKey }}
{{ var $schema = it.schema[$propertyKey]; }}
{{? Object.keys($schema).length }}
{{
var $it = it.copy(it);
$it.schema = $schema;
$it.schemaPath = it.schemaPath + '.' + $propertyKey;
}}
if (data.hasOwnProperty('{{= $propertyKey }}')) {
var _data = data['{{= $propertyKey }}']
, _dataType = getDataType(_data)
, _dataPath = dataPath + '.{{= $propertyKey }}'
, result = ({{= it._validate($it) }})(_data, _dataType, _dataPath);
if (!result.valid) {
{{? it.opts.allErrors }}
errors.push.apply(errors, result.errors);
{{??}}
return result;
{{?}}
}
}
{{?}}
{{~}}
{{~ $pPropertyKeys:$propertyKey }}
{{ var $schema = $pProperties[$propertyKey]; }}
{{? Object.keys($schema).length }}
{{
var $it = it.copy(it);
$it.schema = $schema;
$it.schemaPath = it.parentSchemaPath + '.patternProperties.' + $propertyKey;
}}
for (var key in data) {
var keyMatches = {{? $checkAdditional }}
dataKeysPPs[key]['{{= $propertyKey }}']
{{??}}
pPropertiesRegexps['{{= $propertyKey }}'].test(key)
{{?}};
if (keyMatches) {
var _data = data[key]
, _dataType = getDataType(_data)
, _dataPath = dataPath + '.{{= $propertyKey }}'
, result = ({{= it._validate($it) }})(_data, _dataType, _dataPath);
if (!result.valid) {
{{? it.opts.allErrors }}
errors.push.apply(errors, result.errors);
{{??}}
return result;
{{?}}
}
}
}
{{?}}
{{~}}
{{? it.opts.allErrors }}

View File

@ -0,0 +1,38 @@
function (data, dataType, dataPath) {
'use strict';
{{? it.schema && it.opts.uniqueItems !== false }}
if (data.length <= 1) return { valid: true, errors: [] };
var itemsHash = {}, errors = [];
for (var i = 0; i < data.length; i++) {
var itemStr = stableStringify(data[i]);
var isDuplicate = itemsHash[itemStr];
if (isDuplicate) {
var error = {
keyword: 'uniqueItems',
schema: {{= it.schema }},
dataPath: dataPath,
message: 'data' + dataPath + ' is not valid, item #' + i + 'is duplicate'
{{? it.opts.verbose }}, data: data{{?}}
};
{{? it.opts.allErrors }}
errors.push(error);
{{??}}
return { valid: false, errors: [error] };
{{?}}
} else {
itemsHash[itemStr] = true;
}
}
{{? it.opts.allErrors }}
return { valid: !errors.length, errors: errors };
{{??}}
return { valid: true, errors: [] };
{{?}}
{{??}}
return { valid: true, errors: [] };
{{?}}
}

View File

@ -6,10 +6,15 @@ var fs = require('fs')
, TESTS_PATH = 'JSON-Schema-Test-Suite/tests/draft4/';
var ONLY_RULES;
// ONLY_RULES = ['maxItems', 'minItems'];
// ONLY_RULES = ['type', 'not', 'maximum', 'minimum', 'allOf', 'anyOf', 'oneOf', 'enum',
// 'properties', 'required', 'multipleOf', 'maxProperties', 'minProperties',
// 'maxLength', 'minLength', 'pattern', 'maxItems', 'minItems'];
// ONLY_RULES = ['enum'];
ONLY_RULES = [
'type', 'not', 'allOf', 'anyOf', 'oneOf', 'enum',
'maximum', 'minimum', 'multipleOf',
'maxLength', 'minLength', 'pattern',
'properties', 'patternProperties', 'additionalProperties',
'required', 'maxProperties', 'minProperties',
'maxItems', 'minItems', 'items', 'additionalItems', 'uniqueItems'
];
var Jv = require('../lib/jv')
@ -25,18 +30,18 @@ describe.only('JSON-Schema tests', function () {
describe(file.name, function() {
var testSets = require(file.path);
testSets.forEach(function (testSet) {
// if (testSet.description != 'oneOf') return;
describe(testSet.description, function() {
// it(testSet.description, function() {
// if (testSet.description != 'additionalProperties can exist by itself') return;
// describe(testSet.description, function() {
it(testSet.description, function() {
var validate, fullValidate;
before(function() {
// before(function() {
validate = jv.compile(testSet.schema);
fullValidate = fullJv.compile(testSet.schema);
});
// });
testSet.tests.forEach(function (test) {
// if (test.description != 'neither oneOf valid') return;
it(test.description, function() {
// if (test.description != 'a single invalid match is invalid') return;
// it(test.description, function() {
var result = validate(test.data);
// console.log('result', result);
assert.equal(result.valid, test.valid);
@ -48,7 +53,7 @@ describe.only('JSON-Schema tests', function () {
assert.equal(result.valid, test.valid);
if (result.valid) assert(result.errors.length == 0);
else assert(result.errors.length > 0);
});
// });
});
});
});

252
try.js
View File

@ -1,69 +1,215 @@
'use strict';
validate = function(data) {
validate = function(data, instance) {
var self = this;
var dataType = getDataType(data);
var result = (function(data, dataType, dataPath) {
'use strict';
var rule = RULES.not;
var errors = [];
var rule = RULES.properties;
if (!rule.type || rule.type == dataType) {
var result = (function(data, dataType, dataPath) {
var result = (function(data, dataType, dataPath) {
'use strict';
var rule = RULES.type;
if (!rule.type || rule.type == dataType) {
var result = (function(data, dataType, dataPath) {
'use strict';
var errors = [];
var propertiesSchema = self.schema.properties;
var pPropertiesSchema = self.schema.patternProperties,
pPropertiesRegexps = {},
dataKeysPPs;
for (var pProperty in pPropertiesSchema) pPropertiesRegexps[pProperty] = new RegExp(pProperty);
dataKeysPPs = {};
for (var key in data) {
var isAdditional = !propertiesSchema.hasOwnProperty(key);
dataKeysPPs[key] = {};
for (var pProperty in pPropertiesSchema) {
var keyMatches = pPropertiesRegexps[pProperty].test(key);
if (keyMatches) {
dataKeysPPs[key][pProperty] = true;
isAdditional = false;
}
}
if (isAdditional) {
var _data = data[key],
_dataType = getDataType(_data),
_dataPath = dataPath + '.' + key,
result = (function(data, dataType, dataPath) {
'use strict';
var errors = [];
var rule = RULES.type;
if (!rule.type || rule.type == dataType) {
var result = (function(data, dataType, dataPath) {
'use strict';
var valid;
valid = dataType == 'number' && data === parseInt(data);
return {
valid: valid,
errors: valid ? [] : [{
keyword: 'type',
schema: 'integer',
dataPath: dataPath,
message: 'data' + dataPath + ' is not valid. Expected integer',
data: data
}]
};
})(data, dataType, dataPath);
if (!result.valid) {
errors.push.apply(errors, result.errors);
}
}
return {
valid: !errors.length,
errors: errors
};
})(_data, _dataType, _dataPath);
if (!result.valid) {
errors.push.apply(errors, result.errors);
}
}
}
if (data.hasOwnProperty('foo')) {
var _data = data['foo'],
_dataType = getDataType(_data),
_dataPath = dataPath + '.foo',
result = (function(data, dataType, dataPath) {
'use strict';
var valid;
valid = dataType == 'object';
return {
valid: valid,
errors: valid ? [] : [{
keyword: 'type',
schema: "object",
dataPath: dataPath,
message: 'Type of data' + dataPath + ' is not valid. Expected object'
}]
};
})(data, dataType);
if (!result.valid) {
return result;
}
}
var rule = RULES.properties;
if (!rule.type || rule.type == dataType) {
var result = ()(data, dataType);
if (!result.valid) {
return result;
}
}
return {
valid: true,
errors: []
};
})(data, dataType, dataPath);
result.valid = !result.valid;
result.errors = result.valid ? [] : [{
keyword: 'not',
schema: {
"type": "object",
"properties": {
"foo": {
"type": "string"
var errors = [];
var rule = RULES.type;
if (!rule.type || rule.type == dataType) {
var result = (function(data, dataType, dataPath) {
'use strict';
var valid;
valid = dataType == 'array';
return {
valid: valid,
errors: valid ? [] : [{
keyword: 'type',
schema: 'array',
dataPath: dataPath,
message: 'data' + dataPath + ' is not valid. Expected array',
data: data
}]
};
})(data, dataType, dataPath);
if (!result.valid) {
errors.push.apply(errors, result.errors);
}
}
var rule = RULES.maxItems;
if (!rule.type || rule.type == dataType) {
var result = (function(data, dataType, dataPath) {
'use strict';
var valid = data.length <= 3;
return {
valid: valid,
errors: valid ? [] : [{
keyword: 'maxItems',
schema: 3,
dataPath: dataPath,
message: 'data' + dataPath + ' is not valid, should NOT have more than 3 items',
data: data
}]
};
})(data, dataType, dataPath);
if (!result.valid) {
errors.push.apply(errors, result.errors);
}
}
return {
valid: !errors.length,
errors: errors
};
})(_data, _dataType, _dataPath);
if (!result.valid) {
errors.push.apply(errors, result.errors);
}
}
if (data.hasOwnProperty('bar')) {
var _data = data['bar'],
_dataType = getDataType(_data),
_dataPath = dataPath + '.bar',
result = (function(data, dataType, dataPath) {
'use strict';
var errors = [];
var rule = RULES.type;
if (!rule.type || rule.type == dataType) {
var result = (function(data, dataType, dataPath) {
'use strict';
var valid;
valid = dataType == 'array';
return {
valid: valid,
errors: valid ? [] : [{
keyword: 'type',
schema: 'array',
dataPath: dataPath,
message: 'data' + dataPath + ' is not valid. Expected array',
data: data
}]
};
})(data, dataType, dataPath);
if (!result.valid) {
errors.push.apply(errors, result.errors);
}
}
return {
valid: !errors.length,
errors: errors
};
})(_data, _dataType, _dataPath);
if (!result.valid) {
errors.push.apply(errors, result.errors);
}
}
for (var key in data) {
var keyMatches = dataKeysPPs[key]['f.o'];
if (keyMatches) {
var _data = data['f.o'],
_dataType = getDataType(_data),
_dataPath = dataPath + '.f.o',
result = (function(data, dataType, dataPath) {
'use strict';
var errors = [];
var rule = RULES.minItems;
if (!rule.type || rule.type == dataType) {
var result = (function(data, dataType, dataPath) {
'use strict';
var valid = data.length >= 2;
return {
valid: valid,
errors: valid ? [] : [{
keyword: 'minItems',
schema: 2,
dataPath: dataPath,
message: 'data' + dataPath + ' is not valid, should NOT have less than 2 items',
data: data
}]
};
})(data, dataType, dataPath);
if (!result.valid) {
errors.push.apply(errors, result.errors);
}
}
return {
valid: !errors.length,
errors: errors
};
})(_data, _dataType, _dataPath);
if (!result.valid) {
errors.push.apply(errors, result.errors);
}
},
dataPath: dataPath,
message: 'data' + dataPath + ' is valid according to schema, should be NOT valid'
}];
return result;
})(data, dataType);
}
}
return {
valid: !errors.length,
errors: errors
};
})(data, dataType, dataPath);
if (!result.valid) {
return result;
errors.push.apply(errors, result.errors);
}
}
return {
valid: true,
errors: []
valid: !errors.length,
errors: errors
};
})(data, dataType, '');
return result;
}
}