From 04d0b3c9a9dd23831b98878a1c27a66ae3c8ef48 Mon Sep 17 00:00:00 2001 From: Moshe Atlow Date: Sun, 20 Aug 2023 14:42:57 +0300 Subject: [PATCH 1/3] feat: add validationErrors to schema mismatch errors --- index.js | 8 ++++++-- lib/validator.js | 6 ++++-- test/any.test.js | 14 ++++++++++++-- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index e15bc0c5..0fa6c653 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..c6c93b48 100644 --- a/test/any.test.js +++ b/test/any.test.js @@ -186,7 +186,12 @@ 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.')) + t.throws(() => stringify({ kind: 'Baz', value: 1 }), Object.assign(new TypeError('The value of \'#\' does not match schema definition.'), { + validationErrors: [ + { message: 'must be equal to one of the allowed values', schemaPath: '#/properties/kind/enum', instancePath: '/kind' }, + { message: 'must be equal to one of the allowed values', schemaPath: '#/properties/kind/enum', instancePath: '/kind' } + ] + })) }) test('should throw a TypeError with the path to the key of the invalid value /2', (t) => { @@ -227,5 +232,10 @@ 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.')) + t.throws(() => stringify({ data: { kind: 'Baz', value: 1 } }), Object.assign(new TypeError('The value of \'#/properties/data\' does not match schema definition.'), { + validationErrors: [ + { message: 'must be equal to one of the allowed values', schemaPath: '#/properties/kind/enum', instancePath: '/kind' }, + { message: 'must be equal to one of the allowed values', schemaPath: '#/properties/kind/enum', instancePath: '/kind' } + ] + })) }) From 35f3539115d54ed495f610814ac95835407c9b7d Mon Sep 17 00:00:00 2001 From: Moshe Atlow Date: Sun, 27 Aug 2023 13:17:59 +0300 Subject: [PATCH 2/3] CR --- test/any.test.js | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/test/any.test.js b/test/any.test.js index c6c93b48..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,16 +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 }), Object.assign(new TypeError('The value of \'#\' does not match schema definition.'), { - validationErrors: [ - { message: 'must be equal to one of the allowed values', schemaPath: '#/properties/kind/enum', instancePath: '/kind' }, - { message: 'must be equal to one of the allowed values', schemaPath: '#/properties/kind/enum', instancePath: '/kind' } - ] - })) + 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 = { @@ -232,10 +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 } }), Object.assign(new TypeError('The value of \'#/properties/data\' does not match schema definition.'), { - validationErrors: [ - { message: 'must be equal to one of the allowed values', schemaPath: '#/properties/kind/enum', instancePath: '/kind' }, - { message: 'must be equal to one of the allowed values', schemaPath: '#/properties/kind/enum', instancePath: '/kind' } - ] - })) + 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) + } }) From 437f67eca6c1373709220c071c69a1a924994f7c Mon Sep 17 00:00:00 2001 From: Moshe Atlow Date: Thu, 31 Aug 2023 11:58:30 +0300 Subject: [PATCH 3/3] Update index.js Co-authored-by: Uzlopak --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 0fa6c653..4bdc540c 100644 --- a/index.js +++ b/index.js @@ -866,7 +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' + code += 'const errors = []\n' if (schema.type === 'object') { context.wrapObjects = false