diff --git a/index.js b/index.js index e15bc0c5..4bdc540c 100644 --- a/index.js +++ b/index.js @@ -866,6 +866,7 @@ function buildValue (context, location, input) { if ((type === undefined || type === 'object') && (schema.anyOf || schema.oneOf)) { context.validatorSchemasIds.add(location.getSchemaId()) + code += 'const errors = []\n' if (schema.type === 'object') { context.wrapObjects = false @@ -885,8 +886,9 @@ function buildValue (context, location, input) { const schemaRef = optionLocation.getSchemaRef() const nestedResult = buildValue(context, optionLocation, input) code += ` - ${index === 0 ? 'if' : 'else if'}(validator.validate("${schemaRef}", ${input})) + ${index === 0 ? 'if' : 'else if'}(validator.validate("${schemaRef}", ${input}, errors)) { ${nestedResult} + } ` } @@ -896,7 +898,9 @@ function buildValue (context, location, input) { } code += ` - else throw new TypeError(\`The value of '${schemaRef}' does not match schema definition.\`) + else throw Object.assign(new TypeError(\`The value of '${schemaRef}' does not match schema definition.\`), { + validationErrors: errors + }) ` if (schema.type === 'object') { code += ` diff --git a/lib/validator.js b/lib/validator.js index 26c93f28..640b4a99 100644 --- a/lib/validator.js +++ b/lib/validator.js @@ -47,8 +47,10 @@ class Validator { } } - validate (schemaRef, data) { - return this.ajv.validate(schemaRef, data) + validate (schemaRef, data, errors) { + const valid = this.ajv.validate(schemaRef, data) + if (this.ajv.errors && Array.isArray(errors)) errors.push(...this.ajv.errors) + return valid } // Ajv does not support js date format. In order to properly validate objects containing a date, diff --git a/test/any.test.js b/test/any.test.js index be136abb..b449a8f1 100644 --- a/test/any.test.js +++ b/test/any.test.js @@ -154,7 +154,7 @@ test('empty schema on anyOf', (t) => { }) test('should throw a TypeError with the path to the key of the invalid value /1', (t) => { - t.plan(1) + t.plan(3) // any on Foo codepath. const schema = { @@ -186,11 +186,21 @@ test('should throw a TypeError with the path to the key of the invalid value /1' const stringify = build(schema) - t.throws(() => stringify({ kind: 'Baz', value: 1 }), new TypeError('The value of \'#\' does not match schema definition.')) + try { + stringify({ kind: 'Baz', value: 1 }) + t.fail('should throw') + } catch (err) { + t.equal(err.message, 'The value of \'#\' does not match schema definition.') + t.same(err.validationErrors, [ + { message: 'must be equal to one of the allowed values', schemaPath: '#/properties/kind/enum', instancePath: '/kind', keyword: 'enum', params: { allowedValues: ['Foo'] } }, + { message: 'must be equal to one of the allowed values', schemaPath: '#/properties/kind/enum', instancePath: '/kind', keyword: 'enum', params: { allowedValues: ['Bar'] } } + ]) + t.ok(err instanceof TypeError) + } }) test('should throw a TypeError with the path to the key of the invalid value /2', (t) => { - t.plan(1) + t.plan(3) // any on Foo codepath. const schema = { @@ -227,5 +237,15 @@ test('should throw a TypeError with the path to the key of the invalid value /2' const stringify = build(schema) - t.throws(() => stringify({ data: { kind: 'Baz', value: 1 } }), new TypeError('The value of \'#/properties/data\' does not match schema definition.')) + try { + stringify({ data: { kind: 'Baz', value: 1 } }) + t.fail('should throw') + } catch (err) { + t.equal(err.message, 'The value of \'#/properties/data\' does not match schema definition.') + t.same(err.validationErrors, [ + { message: 'must be equal to one of the allowed values', schemaPath: '#/properties/kind/enum', instancePath: '/kind', keyword: 'enum', params: { allowedValues: ['Foo'] } }, + { message: 'must be equal to one of the allowed values', schemaPath: '#/properties/kind/enum', instancePath: '/kind', keyword: 'enum', params: { allowedValues: ['Bar'] } } + ]) + t.ok(err instanceof TypeError) + } })