diff --git a/README.md b/README.md index fec0884..fd26a1e 100644 --- a/README.md +++ b/README.md @@ -201,11 +201,16 @@ Defaults: ```javascript { - keepErrors: false + keepErrors: false, + singleError: false } ``` - _keepErrors_: keep original errors. Default is to remove matched errors (they will still be available in `params.errors` property of generated error). If an error was matched and included in the error generated by `errorMessage` keyword it will have property `emUsed: true`. +- _singleError_: create one error for all keywords used in `errorMessage` keyword (error messages defined for properties and items are not merged because they have different dataPaths). Multiple error messages are concatenated. Option values: + - `false` (default): create multiple errors, one for each message + - `true`: create single error, messages are concatenated using `"; "` + - non-empty string: this string is used as a separator to concatenate messages ## License diff --git a/SPEC.md b/SPEC.md index a949d82..9a41a22 100644 --- a/SPEC.md +++ b/SPEC.md @@ -93,10 +93,8 @@ Example: All errors that "errorMessage" keyword processes determined by keyword value, as described above. -Each appearance of "errorMessage" keyword can generate either multiple or a single error message, depending on the option "multiple" (true/false, default is false?). // DONE only multiple errors, TODO: single error +Each appearance of "errorMessage" keyword can generate either multiple or a single error message, depending on the option "multiple" (true/false, default is false?). // DONE -If a single message is generated, option "separator" (a string, default is ', ' - same as in errorsText method) defines how to join multiple messages into a single string. // TODO +If a single message is generated, option "separator" (a string, default is ', ' - same as in errorsText method) defines how to join multiple messages into a single string. // DONE -Option "prefix" can be used to prepend error message with "property" name or "dataPath"? // TODO? - -Option "mode" determines whether the errors are replaced by "errorMessage" errors or they are kept ("replace"/"append", default is "replace"). // DONE replace, append - TODO +Option "mode" determines whether the errors are replaced by "errorMessage" errors or they are kept ("replace"/"append", default is "replace"). // DONE diff --git a/lib/dot/errorMessage.jst b/lib/dot/errorMessage.jst index 540c651..328a0f5 100644 --- a/lib/dot/errorMessage.jst +++ b/lib/dot/errorMessage.jst @@ -69,6 +69,12 @@ }; #}} +{{## def.em_errorMessage: + {{=$key}} in {{=$templates}} + ? {{=$templates}}[{{=$key}}] () + : validate.schema{{=$schemaPath}}[{{=$key}}] +#}} + {{? it.createErrors !== false }} {{ @@ -86,6 +92,8 @@ , $matches = '_em_matches' + $lvl , $isArray = '_em_isArray' + $lvl , $errors = '_em_errors' + $lvl + , $message = '_em_message' + $lvl + , $paramsErrors = '_em_paramsErrors' + $lvl , $templates = '_em_templates' + $lvl , $errSchemaPathString = it.util.toQuotedString(it.errSchemaPath); }} @@ -162,25 +170,50 @@ {{=$i}}++; } } - for (var {{=$key}} in {{=$errors}}) { - if ({{=$errors}}[{{=$key}}].length) { - var err = { - keyword: '{{=$keyword}}' - , dataPath: {{=$dataPath}} - , schemaPath: {{=$errSchemaPathString}} + '/{{=$keyword}}' - , params: { errors: {{=$errors}}[{{=$key}}] } - , message: {{=$key}} in {{=$templates}} - ? {{=$templates}}[{{=$key}}] () - : validate.schema{{=$schemaPath}}[{{=$key}}] - {{? it.opts.verbose }} - , schema: validate.schema{{=$schemaPath}} - , parentSchema: validate.schema{{=it.schemaPath}} - , data: {{=$data}} - {{?}} - }; - {{# def._addError:'custom' }} + + {{? $config.options.singleError }} + var {{=$message}} = ''; + var {{=$paramsErrors}} = []; + {{?}} + + for (var {{=$key}} in {{=$errors}}) { + if ({{=$errors}}[{{=$key}}].length) { + + {{? $config.options.singleError }} + if ({{=$message}}) { + {{=$message}} += {{? typeof $config.options.singleError == 'string' }} + {{= it.util.toQuotedString($config.options.singleError) }} + {{??}} + '; ' + {{?}}; + } + {{=$message}} += {{# def.em_errorMessage }}; + {{=$paramsErrors}} = {{=$paramsErrors}}.concat({{=$errors}}[{{=$key}}]); + } } - } + {{??}} + var {{=$message}} = {{# def.em_errorMessage }}; + var {{=$paramsErrors}} = {{=$errors}}[{{=$key}}]; + {{?}} + + var err = { + keyword: '{{=$keyword}}' + , dataPath: {{=$dataPath}} + , schemaPath: {{=$errSchemaPathString}} + '/{{=$keyword}}' + , params: { errors: {{=$paramsErrors}} } + , message: {{=$message}} + {{? it.opts.verbose }} + , schema: validate.schema{{=$schemaPath}} + , parentSchema: validate.schema{{=it.schemaPath}} + , data: {{=$data}} + {{?}} + }; + {{# def._addError:'custom' }} + + {{? !$config.options.singleError }} + } + } + {{?}} {{?}} {{? $hasProperties || $hasItems }} diff --git a/spec/options.spec.js b/spec/options.spec.js index 7ca05ab..2a8b306 100644 --- a/spec/options.spec.js +++ b/spec/options.spec.js @@ -10,10 +10,13 @@ describe('options', function() { beforeEach(function() { ajv = new Ajv({allErrors: true, jsonPointers: true}); - ajvErrors(ajv, {keepErrors: true}); }); - describe('keepErrors: true', function() { + describe('keepErrors = true', function() { + beforeEach(function() { + ajvErrors(ajv, {keepErrors: true}); + }); + describe('errorMessage is a string', function() { it('should keep matched errors and mark them with {emUsed: true} property', function() { var schema = { @@ -138,6 +141,58 @@ describe('options', function() { }); + describe('singleError', function() { + describe('= true', function() { + it('should generate a single error for all keywords', function() { + ajvErrors(ajv, {singleError: true}); + testSingleErrors('; '); + }); + }); + + describe('= separator', function() { + it('should generate a single error for all keywords using separator', function() { + ajvErrors(ajv, {singleError: '\n'}); + testSingleErrors('\n'); + }); + }); + + function testSingleErrors(separator) { + var schema = { + type: 'number', + minimum: 2, + maximum: 10, + multipleOf: 2, + errorMessage: { + type: 'should be number', + minimum: 'should be >= 2', + maximum: 'should be <= 10', + multipleOf: 'should be multipleOf 2' + } + }; + + var validate = ajv.compile(schema); + assert.strictEqual(validate(4), true); + assert.strictEqual(validate(11), false); + + var expectedKeywords = ['maximum', 'multipleOf']; + var expectedMessage = expectedKeywords + .map(function (keyword) { + return schema.errorMessage[keyword]; + }) + .join(separator); + + assertErrors(validate, [ + { + keyword: 'errorMessage', + message: expectedMessage, + dataPath: '', + errors: expectedKeywords + } + ]); + } + }); + + function assertErrors(validate, expectedErrors) { assert.strictEqual(validate.errors.length, expectedErrors.length);