From 423d653d3b0f4e4c39b40a37cd6f961d46a74721 Mon Sep 17 00:00:00 2001 From: Alexander Akait <4567934+alexander-akait@users.noreply.github.com> Date: Wed, 14 Jun 2023 03:49:45 +0300 Subject: [PATCH] refactor: code for multiple options (#179) --- declarations/validate.d.ts | 27 -------- src/validate.js | 46 ++++++-------- test/__snapshots__/api.test.js.snap | 27 ++++++++ test/api.test.js | 99 +++++++++++++++++++++++++++++ 4 files changed, 147 insertions(+), 52 deletions(-) diff --git a/declarations/validate.d.ts b/declarations/validate.d.ts index d7171ec..11fb927 100644 --- a/declarations/validate.d.ts +++ b/declarations/validate.d.ts @@ -23,33 +23,6 @@ export type ValidationErrorConfiguration = { baseDataPath?: string | undefined; postFormatter?: PostFormatter | undefined; }; -/** @typedef {import("json-schema").JSONSchema4} JSONSchema4 */ -/** @typedef {import("json-schema").JSONSchema6} JSONSchema6 */ -/** @typedef {import("json-schema").JSONSchema7} JSONSchema7 */ -/** @typedef {import("ajv").ErrorObject} ErrorObject */ -/** - * @typedef {Object} Extend - * @property {string=} formatMinimum - * @property {string=} formatMaximum - * @property {string=} formatExclusiveMinimum - * @property {string=} formatExclusiveMaximum - * @property {string=} link - * @property {boolean=} undefinedAsNull - */ -/** @typedef {(JSONSchema4 | JSONSchema6 | JSONSchema7) & Extend} Schema */ -/** @typedef {ErrorObject & { children?: Array}} SchemaUtilErrorObject */ -/** - * @callback PostFormatter - * @param {string} formattedError - * @param {SchemaUtilErrorObject} error - * @returns {string} - */ -/** - * @typedef {Object} ValidationErrorConfiguration - * @property {string=} name - * @property {string=} baseDataPath - * @property {PostFormatter=} postFormatter - */ /** * @param {Schema} schema * @param {Array | object} options diff --git a/src/validate.js b/src/validate.js index a361ecc..71c03d6 100644 --- a/src/validate.js +++ b/src/validate.js @@ -71,6 +71,22 @@ const getAjv = memoize(() => { * @property {PostFormatter=} postFormatter */ +/** + * @param {SchemaUtilErrorObject} error + * @param {number} idx + * @returns {SchemaUtilErrorObject} + */ +function applyPrefix(error, idx) { + // eslint-disable-next-line no-param-reassign + error.instancePath = `[${idx}]${error.instancePath}`; + + if (error.children) { + error.children.forEach((err) => applyPrefix(err, idx)); + } + + return error; +} + /** * @param {Schema} schema * @param {Array | object} options @@ -81,31 +97,11 @@ function validate(schema, options, configuration) { let errors = []; if (Array.isArray(options)) { - errors = Array.from(options, (nestedOptions) => - validateObject(schema, nestedOptions) - ); - - errors.forEach((list, idx) => { - const applyPrefix = - /** - * @param {SchemaUtilErrorObject} error - */ - (error) => { - // eslint-disable-next-line no-param-reassign - error.instancePath = `[${idx}]${error.instancePath}`; - - if (error.children) { - error.children.forEach(applyPrefix); - } - }; - - list.forEach(applyPrefix); - }); - - errors = errors.reduce((arr, items) => { - arr.push(...items); - return arr; - }, []); + for (let i = 0; i <= options.length - 1; i++) { + errors.push( + ...validateObject(schema, options[i]).map((err) => applyPrefix(err, i)) + ); + } } else { errors = validateObject(schema, options); } diff --git a/test/__snapshots__/api.test.js.snap b/test/__snapshots__/api.test.js.snap index 1eef678..e3fb769 100644 --- a/test/__snapshots__/api.test.js.snap +++ b/test/__snapshots__/api.test.js.snap @@ -29,3 +29,30 @@ exports[`api should use default values when "title" is broken 1`] = ` - configuration has an unknown property 'foo'. These properties are valid: object { name? }" `; + +exports[`api should work with anyOf 1`] = ` +"Invalid configuration object. Object has been initialized using a configuration object that does not match the API schema. + - configuration should be one of these: + object { bar, … } | object { baz, … } + Details: + * configuration misses the property 'bar' | should be any non-object. Should be: + number + * configuration misses the property 'baz' | should be any non-object. Should be: + number" +`; + +exports[`api should work with minProperties properties 1`] = ` +"Invalid configuration object. Object has been initialized using a configuration object that does not match the API schema. + - configuration should be a non-empty object." +`; + +exports[`api should work with required properties #2 1`] = ` +"Invalid configuration object. Object has been initialized using a configuration object that does not match the API schema. + - configuration misses the property 'e'." +`; + +exports[`api should work with required properties 1`] = ` +"Invalid configuration object. Object has been initialized using a configuration object that does not match the API schema. + - configuration.c misses the property 'e'. Should be: + string" +`; diff --git a/test/api.test.js b/test/api.test.js index a61e1ec..445f0b9 100644 --- a/test/api.test.js +++ b/test/api.test.js @@ -97,4 +97,103 @@ describe("api", () => { expect(error.message).toMatchSnapshot(); } }); + + it("should work with required properties", () => { + try { + validate( + { + type: "object", + properties: { + c: { + type: "object", + properties: { + d: { + type: "string", + }, + e: { + type: "string", + }, + }, + additionalProperties: true, + required: ["d", "e"], + }, + }, + }, + { c: { d: "e" } } + ); + } catch (error) { + if (error.name !== "ValidationError") { + throw error; + } + + expect(error.message).toMatchSnapshot(); + } + }); + + it("should work with required properties #2", () => { + try { + validate( + { + type: "object", + properties: {}, + required: ["d", "e"], + }, + {} + ); + } catch (error) { + if (error.name !== "ValidationError") { + throw error; + } + + expect(error.message).toMatchSnapshot(); + } + }); + + it("should work with minProperties properties", () => { + try { + validate( + { + type: "object", + properties: {}, + minProperties: 1, + }, + {} + ); + } catch (error) { + if (error.name !== "ValidationError") { + throw error; + } + + expect(error.message).toMatchSnapshot(); + } + }); + + it("should work with anyOf", () => { + try { + validate( + { + type: "object", + properties: { foo: { type: "number" } }, + unevaluatedProperties: false, + anyOf: [ + { + required: ["bar"], + properties: { bar: { type: "number" } }, + }, + { + required: ["baz"], + properties: { baz: { type: "number" } }, + }, + ], + }, + {} + ); + } catch (error) { + if (error.name !== "ValidationError") { + throw error; + } + + expect(error.message).toMatchSnapshot(); + } + }); });