Skip to content

Commit

Permalink
refactor: code for multiple options (#179)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-akait committed Jun 14, 2023
1 parent 3806c65 commit 423d653
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 52 deletions.
27 changes: 0 additions & 27 deletions declarations/validate.d.ts
Expand Up @@ -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<ErrorObject>}} 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> | object} options
Expand Down
46 changes: 21 additions & 25 deletions src/validate.js
Expand Up @@ -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> | object} options
Expand All @@ -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);
}
Expand Down
27 changes: 27 additions & 0 deletions test/__snapshots__/api.test.js.snap
Expand Up @@ -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"
`;
99 changes: 99 additions & 0 deletions test/api.test.js
Expand Up @@ -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();
}
});
});

0 comments on commit 423d653

Please sign in to comment.