Skip to content

Commit

Permalink
improved error messages for "required" keyword, closes #18
Browse files Browse the repository at this point in the history
  • Loading branch information
epoberezkin committed Jun 30, 2015
1 parent 5fc0b18 commit 5416eaf
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 74 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,14 @@ There is pre-commit hook that runs compile_dots and tests.

## Changes history


##### 0.6.1

Errors for "required" keyword validation include missing properties

Better references resolution in schemas without IDs


##### 0.5.9

`cache` option and `removeSchema` method
Expand Down
2 changes: 1 addition & 1 deletion lib/dot/definitions.def
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@
not: "'should NOT be valid'",
oneOf: "'should match exactly one schema in oneOf'",
pattern: "'should match pattern \"{{=$schema}}\"'",
required: "'properties {{=$schema.slice(0,7).join(\", \") }}{{? $schema.length > 7}}...{{?}} are required'",
required: "'property {{=$missingProperty}} is required'",
type: "'should be {{? $isArray }}{{= $typeSchema.join(\",\") }}{{??}}{{=$typeSchema}}{{?}}'",
uniqueItems: "'items ## ' + j + ' and ' + i + ' are duplicate'"
} #}}
Expand Down
29 changes: 20 additions & 9 deletions lib/dot/required.jst
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,31 @@
{{~}}
#}}

{{## def.setupLoop:
var schema{{=$lvl}} = validate.schema{{=$schemaPath}};
{{
var $propertyPath = ' + schema' + $lvl + '[i] + '
, $missingProperty = '\' + "\'"' + $propertyPath + '"\'" + \'';
it.errorPath = ($currentErrorPath + ' + "[\'"' + $propertyPath + '"\']"').replace('" + "', '');
}}
#}}


{{ var $currentErrorPath = it.errorPath; }}

{{? $breakOnError }}
var missing{{=$lvl}};
{{? $schema.length <= 100 }}
{{? $schema.length <= 20 }}
if ({{# def.checkRequired }}) {
{{ it.errorPath = $currentErrorPath + ' + missing' + $lvl; }}
{{
var $propertyPath = ' + missing' + $lvl
, $missingProperty = '\'' + $propertyPath + ' + \'';
it.errorPath = $currentErrorPath + $propertyPath;
}}
{{# def.error:'required' }}
} else {
{{??}}
var schema{{=$lvl}} = validate.schema{{=$schemaPath}};
{{ it.errorPath = ($currentErrorPath + ' + "[\'" + schema' + $lvl + '[i] + "\']"').replace('" + "', ''); }}
{{# def.setupLoop }}

for (var i = 0; i < schema{{=$lvl}}.length; i++) {
var {{=$valid}} = data[schema{{=$lvl}}[i]] !== undefined;
Expand All @@ -35,20 +47,19 @@
{{? $schema.length <= 10 }}
{{~ $schema:$property:$i }}
{{
var $prop = it.util.getProperty($property);
var $prop = it.util.getProperty($property)
, $missingProperty = $prop;
it.errorPath = ($currentErrorPath + ' + "' + $prop + '"').replace('" + "', '');
}}
if ({{=$data}}{{=$prop}} === undefined) {
{{# def.error:'required' }}
}
{{~}}
{{??}}
var schema{{=$lvl}} = validate.schema{{=$schemaPath}};
{{ it.errorPath = ($currentErrorPath + ' + "[\'" + schema' + $lvl + '[i] + "\']"').replace('" + "', ''); }}
{{# def.setupLoop }}

for (var i = 0; i < schema{{=$lvl}}.length; i++) {
var {{=$valid}} = data[schema{{=$lvl}}[i]] !== undefined;
if (!{{=$valid}}) {
if (data[schema{{=$lvl}}[i]] === undefined) {
{{# def.error:'required' }}
}
}
Expand Down
71 changes: 23 additions & 48 deletions lib/dotjs/required.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module.exports = function anonymous(it) {
var $currentErrorPath = it.errorPath;
if ($breakOnError) {
out += ' var missing' + ($lvl) + '; ';
if ($schema.length <= 100) {
if ($schema.length <= 20) {
out += ' if ( ';
var arr1 = $schema;
if (arr1) {
Expand All @@ -31,49 +31,37 @@ module.exports = function anonymous(it) {
}
}
out += ') { ';
it.errorPath = $currentErrorPath + ' + missing' + $lvl;
var $propertyPath = ' + missing' + $lvl,
$missingProperty = '\'' + $propertyPath + ' + \'';
it.errorPath = $currentErrorPath + $propertyPath;
if (it.wasTop && $breakOnError) {
out += ' validate.errors = [ { keyword: \'' + ('required') + '\', dataPath: (dataPath || \'\') + ' + (it.errorPath) + ', message: \'properties ' + ($schema.slice(0, 7).join(", "));
if ($schema.length > 7) {
out += '...';
}
out += ' are required\' ';
out += ' validate.errors = [ { keyword: \'' + ('required') + '\', dataPath: (dataPath || \'\') + ' + (it.errorPath) + ', message: \'property ' + ($missingProperty) + ' is required\' ';
if (it.opts.verbose) {
out += ', schema: validate.schema' + ($schemaPath) + ', data: ' + ($data);
}
out += ' }]; return false; ';
} else {
out += ' var err = { keyword: \'' + ('required') + '\', dataPath: (dataPath || \'\') + ' + (it.errorPath) + ', message: \'properties ' + ($schema.slice(0, 7).join(", "));
if ($schema.length > 7) {
out += '...';
}
out += ' are required\' ';
out += ' var err = { keyword: \'' + ('required') + '\', dataPath: (dataPath || \'\') + ' + (it.errorPath) + ', message: \'property ' + ($missingProperty) + ' is required\' ';
if (it.opts.verbose) {
out += ', schema: validate.schema' + ($schemaPath) + ', data: ' + ($data);
}
out += ' }; if (validate.errors === null) validate.errors = [err]; else validate.errors.push(err); errors++; ';
}
out += ' } else { ';
} else {
out += ' var schema' + ($lvl) + ' = validate.schema' + ($schemaPath) + '; ';
it.errorPath = ($currentErrorPath + ' + "[\'" + schema' + $lvl + '[i] + "\']"').replace('" + "', '');
out += ' var schema' + ($lvl) + ' = validate.schema' + ($schemaPath) + '; ';
var $propertyPath = ' + schema' + $lvl + '[i] + ',
$missingProperty = '\' + "\'"' + $propertyPath + '"\'" + \'';
it.errorPath = ($currentErrorPath + ' + "[\'"' + $propertyPath + '"\']"').replace('" + "', '');
out += ' for (var i = 0; i < schema' + ($lvl) + '.length; i++) { var ' + ($valid) + ' = data[schema' + ($lvl) + '[i]] !== undefined; if (!' + ($valid) + ') break; } if (!' + ($valid) + ') { ';
if (it.wasTop && $breakOnError) {
out += ' validate.errors = [ { keyword: \'' + ('required') + '\', dataPath: (dataPath || \'\') + ' + (it.errorPath) + ', message: \'properties ' + ($schema.slice(0, 7).join(", "));
if ($schema.length > 7) {
out += '...';
}
out += ' are required\' ';
out += ' validate.errors = [ { keyword: \'' + ('required') + '\', dataPath: (dataPath || \'\') + ' + (it.errorPath) + ', message: \'property ' + ($missingProperty) + ' is required\' ';
if (it.opts.verbose) {
out += ', schema: validate.schema' + ($schemaPath) + ', data: ' + ($data);
}
out += ' }]; return false; ';
} else {
out += ' var err = { keyword: \'' + ('required') + '\', dataPath: (dataPath || \'\') + ' + (it.errorPath) + ', message: \'properties ' + ($schema.slice(0, 7).join(", "));
if ($schema.length > 7) {
out += '...';
}
out += ' are required\' ';
out += ' var err = { keyword: \'' + ('required') + '\', dataPath: (dataPath || \'\') + ' + (it.errorPath) + ', message: \'property ' + ($missingProperty) + ' is required\' ';
if (it.opts.verbose) {
out += ', schema: validate.schema' + ($schemaPath) + ', data: ' + ($data);
}
Expand All @@ -89,25 +77,18 @@ module.exports = function anonymous(it) {
l2 = arr2.length - 1;
while ($i < l2) {
$property = arr2[$i += 1];
var $prop = it.util.getProperty($property);
var $prop = it.util.getProperty($property),
$missingProperty = $prop;
it.errorPath = ($currentErrorPath + ' + "' + $prop + '"').replace('" + "', '');
out += ' if (' + ($data) + ($prop) + ' === undefined) { ';
if (it.wasTop && $breakOnError) {
out += ' validate.errors = [ { keyword: \'' + ('required') + '\', dataPath: (dataPath || \'\') + ' + (it.errorPath) + ', message: \'properties ' + ($schema.slice(0, 7).join(", "));
if ($schema.length > 7) {
out += '...';
}
out += ' are required\' ';
out += ' validate.errors = [ { keyword: \'' + ('required') + '\', dataPath: (dataPath || \'\') + ' + (it.errorPath) + ', message: \'property ' + ($missingProperty) + ' is required\' ';
if (it.opts.verbose) {
out += ', schema: validate.schema' + ($schemaPath) + ', data: ' + ($data);
}
out += ' }]; return false; ';
} else {
out += ' var err = { keyword: \'' + ('required') + '\', dataPath: (dataPath || \'\') + ' + (it.errorPath) + ', message: \'properties ' + ($schema.slice(0, 7).join(", "));
if ($schema.length > 7) {
out += '...';
}
out += ' are required\' ';
out += ' var err = { keyword: \'' + ('required') + '\', dataPath: (dataPath || \'\') + ' + (it.errorPath) + ', message: \'property ' + ($missingProperty) + ' is required\' ';
if (it.opts.verbose) {
out += ', schema: validate.schema' + ($schemaPath) + ', data: ' + ($data);
}
Expand All @@ -117,25 +98,19 @@ module.exports = function anonymous(it) {
}
}
} else {
out += ' var schema' + ($lvl) + ' = validate.schema' + ($schemaPath) + '; ';
it.errorPath = ($currentErrorPath + ' + "[\'" + schema' + $lvl + '[i] + "\']"').replace('" + "', '');
out += ' for (var i = 0; i < schema' + ($lvl) + '.length; i++) { var ' + ($valid) + ' = data[schema' + ($lvl) + '[i]] !== undefined; if (!' + ($valid) + ') { ';
out += ' var schema' + ($lvl) + ' = validate.schema' + ($schemaPath) + '; ';
var $propertyPath = ' + schema' + $lvl + '[i] + ',
$missingProperty = '\' + "\'"' + $propertyPath + '"\'" + \'';
it.errorPath = ($currentErrorPath + ' + "[\'"' + $propertyPath + '"\']"').replace('" + "', '');
out += ' for (var i = 0; i < schema' + ($lvl) + '.length; i++) { if (data[schema' + ($lvl) + '[i]] === undefined) { ';
if (it.wasTop && $breakOnError) {
out += ' validate.errors = [ { keyword: \'' + ('required') + '\', dataPath: (dataPath || \'\') + ' + (it.errorPath) + ', message: \'properties ' + ($schema.slice(0, 7).join(", "));
if ($schema.length > 7) {
out += '...';
}
out += ' are required\' ';
out += ' validate.errors = [ { keyword: \'' + ('required') + '\', dataPath: (dataPath || \'\') + ' + (it.errorPath) + ', message: \'property ' + ($missingProperty) + ' is required\' ';
if (it.opts.verbose) {
out += ', schema: validate.schema' + ($schemaPath) + ', data: ' + ($data);
}
out += ' }]; return false; ';
} else {
out += ' var err = { keyword: \'' + ('required') + '\', dataPath: (dataPath || \'\') + ' + (it.errorPath) + ', message: \'properties ' + ($schema.slice(0, 7).join(", "));
if ($schema.length > 7) {
out += '...';
}
out += ' are required\' ';
out += ' var err = { keyword: \'' + ('required') + '\', dataPath: (dataPath || \'\') + ' + (it.errorPath) + ', message: \'property ' + ($missingProperty) + ' is required\' ';
if (it.opts.verbose) {
out += ', schema: validate.schema' + ($schemaPath) + ', data: ' + ($data);
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ajv",
"version": "0.6.0",
"version": "0.6.1",
"description": "Another JSON Schema Validator",
"main": "lib/ajv.js",
"scripts": {
Expand Down
32 changes: 17 additions & 15 deletions spec/errors.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,17 +77,17 @@ describe('Validation errors', function () {
var validate = ajv.compile(schema);
shouldBeValid(validate, data);
shouldBeInvalid(validate, invalidData1);
shouldBeError(validate.errors[0], 'required', '.bar');
shouldBeError(validate.errors[0], 'required', '.bar', 'property .bar is required');
shouldBeInvalid(validate, invalidData2);
shouldBeError(validate.errors[0], 'required', '.foo');
shouldBeError(validate.errors[0], 'required', '.foo', 'property .foo is required');

var fullValidate = fullAjv.compile(schema);
shouldBeValid(fullValidate, data);
shouldBeInvalid(fullValidate, invalidData1);
shouldBeError(fullValidate.errors[0], 'required', '.bar');
shouldBeError(fullValidate.errors[0], 'required', '.bar', 'property .bar is required');
shouldBeInvalid(fullValidate, invalidData2, 2);
shouldBeError(fullValidate.errors[0], 'required', '.foo');
shouldBeError(fullValidate.errors[1], 'required', '.baz');
shouldBeError(fullValidate.errors[0], 'required', '.foo', 'property .foo is required');
shouldBeError(fullValidate.errors[1], 'required', '.baz', 'property .baz is required');
});


Expand All @@ -96,29 +96,29 @@ describe('Validation errors', function () {
, data = {}
, invalidData1 = {}
, invalidData2 = {};
for (var i=0; i<200; i++) {
schema.required.push(''+i); // properties from '0' to '199' are required
for (var i=0; i<100; i++) {
schema.required.push(''+i); // properties from '0' to '99' are required
data[i] = invalidData1[i] = invalidData2[i] = i;
}

delete invalidData1[1]; // property '1' will be missing
delete invalidData2[2]; // properties '2' and '198' will be missing
delete invalidData2[198];
delete invalidData2[98];

var validate = ajv.compile(schema);
shouldBeValid(validate, data);
shouldBeInvalid(validate, invalidData1);
shouldBeError(validate.errors[0], 'required', "['1']");
shouldBeError(validate.errors[0], 'required', "['1']", "property '1' is required");
shouldBeInvalid(validate, invalidData2);
shouldBeError(validate.errors[0], 'required', "['2']");
shouldBeError(validate.errors[0], 'required', "['2']", "property '2' is required");

var fullValidate = fullAjv.compile(schema);
shouldBeValid(fullValidate, data);
shouldBeInvalid(fullValidate, invalidData1);
shouldBeError(fullValidate.errors[0], 'required', "['1']");
shouldBeError(fullValidate.errors[0], 'required', "['1']", "property '1' is required");
shouldBeInvalid(fullValidate, invalidData2, 2);
shouldBeError(fullValidate.errors[0], 'required', "['2']");
shouldBeError(fullValidate.errors[1], 'required', "['198']");
shouldBeError(fullValidate.errors[0], 'required', "['2']", "property '2' is required");
shouldBeError(fullValidate.errors[1], 'required', "['98']", "property '98' is required");
});


Expand Down Expand Up @@ -151,9 +151,11 @@ describe('Validation errors', function () {
}


function shouldBeError(error, keyword, dataPath) {
function shouldBeError(error, keyword, dataPath, message) {
error.keyword .should.equal(keyword);
error.message .should.be.a('string');
error.dataPath .should.equal(dataPath);
error.message .should.be.a('string');
if (message !== undefined)
error.message .should.equal(message);
}
});

0 comments on commit 5416eaf

Please sign in to comment.