feat: async validation promise resolves with data in case of success, closes #377

master
Evgeny Poberezkin 2016-12-25 20:24:36 +00:00
parent f1028c8411
commit 850b50523c
7 changed files with 31 additions and 21 deletions

View File

@ -419,7 +419,7 @@ If your schema uses asynchronous formats/keywords or refers to some schema that
__Please note__: all asynchronous subschemas that are referenced from the current or other schemas should have `"$async": true` keyword as well, otherwise the schema compilation will fail.
Validation function for an asynchronous custom format/keyword should return a promise that resolves to `true` or `false` (or rejects with `new Ajv.ValidationError(errors)` if you want to return custom errors from the keyword function). Ajv compiles asynchronous schemas to either [es7 async functions](http://tc39.github.io/ecmascript-asyncawait/) (default) that can optionally be transpiled with [nodent](https://github.com/MatAtBread/nodent) or with [regenerator](https://github.com/facebook/regenerator) or to [generator functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*) that can be optionally transpiled with regenerator as well. You can also supply any other transpiler as a function. See [Options](#options).
Validation function for an asynchronous custom format/keyword should return a promise that resolves with `true` or `false` (or rejects with `new Ajv.ValidationError(errors)` if you want to return custom errors from the keyword function). Ajv compiles asynchronous schemas to either [es7 async functions](http://tc39.github.io/ecmascript-asyncawait/) (default) that can optionally be transpiled with [nodent](https://github.com/MatAtBread/nodent) or with [regenerator](https://github.com/facebook/regenerator) or to [generator functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*) that can be optionally transpiled with regenerator as well. You can also supply any other transpiler as a function. See [Options](#options).
The compiled validation function has `$async: true` property (if the schema is asynchronous), so you can differentiate these functions if you are using both syncronous and asynchronous schemas.
@ -431,7 +431,7 @@ Generator functions are currently supported in Chrome, Firefox and node.js.
If you are using Ajv in other browsers or in older versions of node.js you should use one of available transpiling options. All provided async modes use global Promise class. If your platform does not have Promise you should use a polyfill that defines it.
Validation result will be a promise that resolves to `true` or rejects with an exception `Ajv.ValidationError` that has the array of validation errors in `errors` property.
Validation result will be a promise that resolves with validated data or rejects with an exception `Ajv.ValidationError` that has the array of validation errors in `errors` property.
Example:
@ -481,9 +481,8 @@ var schema = {
var validate = ajv.compile(schema);
validate({ userId: 1, postId: 19 }))
.then(function (valid) {
// "valid" is always true here
console.log('Data is valid');
.then(function (data) {
console.log('Data is valid', data); // { userId: 1, postId: 19 }
})
.catch(function (err) {
if (!(err instanceof Ajv.ValidationError)) throw err;

View File

@ -149,8 +149,8 @@ var ERRORS_REGEXP = /[^v\.]errors/g
, REMOVE_ERRORS_ASYNC = /var errors = 0;|var vErrors = null;/g
, RETURN_VALID = 'return errors === 0;'
, RETURN_TRUE = 'validate.errors = null; return true;'
, RETURN_ASYNC = /if \(errors === 0\) return true;\s*else throw new ValidationError\(vErrors\);/
, RETURN_TRUE_ASYNC = 'return true;'
, RETURN_ASYNC = /if \(errors === 0\) return data;\s*else throw new ValidationError\(vErrors\);/
, RETURN_DATA_ASYNC = 'return data;'
, ROOTDATA_REGEXP = /[^A-Za-z_$]rootData[^A-Za-z0-9_$]/g
, REMOVE_ROOTDATA = /if \(rootData === undefined\) rootData = data;/;
@ -159,7 +159,7 @@ function finalCleanUpCode(out, async) {
if (!matches || matches.length !== 2) return out;
out = async
? out.replace(REMOVE_ERRORS_ASYNC, '')
.replace(RETURN_ASYNC, RETURN_TRUE_ASYNC)
.replace(RETURN_ASYNC, RETURN_DATA_ASYNC)
: out.replace(REMOVE_ERRORS, '')
.replace(RETURN_VALID, RETURN_TRUE);

View File

@ -63,12 +63,16 @@
{{? $async }}
{{ if (!it.async) throw new Error('async schema referenced by sync schema'); }}
try { {{? $breakOnError }}var {{=$valid}} ={{?}} {{=it.yieldAwait}} {{=__callValidate}}; }
catch (e) {
{{? $breakOnError }} var {{=$valid}}; {{?}}
try {
{{=it.yieldAwait}} {{=__callValidate}};
{{? $breakOnError }} {{=$valid}} = true; {{?}}
} catch (e) {
if (!(e instanceof ValidationError)) throw e;
if (vErrors === null) vErrors = e.errors;
else vErrors = vErrors.concat(e.errors);
errors = vErrors.length;
{{? $breakOnError }} {{=$valid}} = false; {{?}}
}
{{? $breakOnError }} if ({{=$valid}}) { {{?}}
{{??}}

View File

@ -54,8 +54,12 @@
{{# def.error:'false schema' }}
{{??}}
{{? it.isTop}}
{{? !$async }} validate.errors = null; {{?}}
return true;
{{? $async }}
return data;
{{??}}
validate.errors = null;
return true;
{{?}}
{{??}}
var {{=$valid}} = true;
{{?}}
@ -211,7 +215,7 @@
{{? $top }}
{{? $async }}
if (errors === 0) return true; {{ /* don't edit, used in replace */ }}
if (errors === 0) return data; {{ /* don't edit, used in replace */ }}
else throw new ValidationError(vErrors); {{ /* don't edit, used in replace */ }}
{{??}}
validate.errors = vErrors; {{ /* don't edit, used in replace */ }}

View File

@ -78,7 +78,7 @@
"glob": "^7.0.0",
"if-node-version": "^1.0.0",
"js-beautify": "^1.5.6",
"json-schema-test": "^1.1.1",
"json-schema-test": "^1.3.0",
"karma": "^1.0.0",
"karma-chrome-launcher": "^2.0.0",
"karma-mocha": "^1.1.1",

View File

@ -23,6 +23,7 @@ jsonSchemaTest(instances, {
: './async/{**/,}*.json'
},
async: true,
asyncValid: 'data',
assert: require('./chai').assert,
Promise: Promise,
afterError: after.error,

View File

@ -38,7 +38,7 @@ describe('async schemas, formats and keywords', function() {
var _co = useCo(_ajv);
return Promise.all([
shouldBeValid( _co(validate('abc')) ),
shouldBeValid( _co(validate('abc')), 'abc' ),
shouldBeInvalid( _co(validate('abcd')) ),
shouldBeInvalid( _co(validate(1)) ),
]);
@ -214,9 +214,10 @@ describe('async schemas, formats and keywords', function() {
return repeat(function() { return Promise.all(instances.map(function (_ajv) {
var validate = _ajv.compile(schema);
var _co = useCo(_ajv);
var validData = { word: 'tomorrow' };
return Promise.all([
shouldBeValid( _co(validate({ word: 'tomorrow' })) ),
shouldBeValid( _co(validate(validData)), validData ),
shouldBeInvalid( _co(validate({ word: 'manana' })) ),
shouldBeInvalid( _co(validate({ word: 1 })) ),
shouldThrow( _co(validate({ word: 'today' })), 'unknown word' )
@ -339,17 +340,18 @@ describe('async schemas, formats and keywords', function() {
if (refSchema) try { _ajv.addSchema(refSchema); } catch(e) {}
var validate = _ajv.compile(schema);
var _co = useCo(_ajv);
var data;
return Promise.all([
shouldBeValid( _co(validate({ foo: 'tomorrow' })) ),
shouldBeValid( _co(validate(data = { foo: 'tomorrow' })), data ),
shouldBeInvalid( _co(validate({ foo: 'manana' })) ),
shouldBeInvalid( _co(validate({ foo: 1 })) ),
shouldThrow( _co(validate({ foo: 'today' })), 'unknown word' ),
shouldBeValid( _co(validate({ foo: { foo: 'tomorrow' }})) ),
shouldBeValid( _co(validate(data = { foo: { foo: 'tomorrow' }})), data ),
shouldBeInvalid( _co(validate({ foo: { foo: 'manana' }})) ),
shouldBeInvalid( _co(validate({ foo: { foo: 1 }})) ),
shouldThrow( _co(validate({ foo: { foo: 'today' }})), 'unknown word' ),
shouldBeValid( _co(validate({ foo: { foo: { foo: 'tomorrow' }}})) ),
shouldBeValid( _co(validate(data = { foo: { foo: { foo: 'tomorrow' }}})), data ),
shouldBeInvalid( _co(validate({ foo: { foo: { foo: 'manana' }}})) ),
shouldBeInvalid( _co(validate({ foo: { foo: { foo: 1 }}})) ),
shouldThrow( _co(validate({ foo: { foo: { foo: 'today' }}})), 'unknown word' )
@ -417,9 +419,9 @@ function shouldThrowFunc(message, func) {
}
function shouldBeValid(p) {
function shouldBeValid(p, data) {
return p.then(function (valid) {
valid .should.equal(true);
valid .should.equal(data);
});
}