diff --git a/src/error-handlers/dependentRequired.js b/src/error-handlers/dependentRequired.js deleted file mode 100644 index d149a85..0000000 --- a/src/error-handlers/dependentRequired.js +++ /dev/null @@ -1,44 +0,0 @@ -import { getSchema } from "@hyperjump/json-schema/experimental"; -import * as Schema from "@hyperjump/browser"; -import * as Instance from "@hyperjump/json-schema/instance/experimental"; - -/** - * @import { ErrorHandler, ErrorObject } from "../index.d.ts" - */ - -/** @type ErrorHandler */ -const dependentRequiredErrorHandler = async (normalizedErrors, instance, localization) => { - /** @type ErrorObject[] */ - const errors = []; - - for (const schemaLocation in normalizedErrors["https://json-schema.org/keyword/dependentRequired"]) { - if (normalizedErrors["https://json-schema.org/keyword/dependentRequired"][schemaLocation]) { - continue; - } - - const keyword = await getSchema(schemaLocation); - const dependentRequired = /** @type Record */ (Schema.value(keyword)); - - /** @type Set */ - const required = new Set(); - for (const propertyName in dependentRequired) { - if (Instance.has(propertyName, instance)) { - for (const requiredPropertyName of dependentRequired[propertyName]) { - if (!Instance.has(requiredPropertyName, instance)) { - required.add(requiredPropertyName); - } - } - } - } - - errors.push({ - message: localization.getRequiredErrorMessage([...required]), - instanceLocation: Instance.uri(instance), - schemaLocations: [schemaLocation] - }); - } - - return errors; -}; - -export default dependentRequiredErrorHandler; diff --git a/src/error-handlers/draft-04/dependencies.js b/src/error-handlers/draft-04/dependencies.js index d16effc..c01a9f5 100644 --- a/src/error-handlers/draft-04/dependencies.js +++ b/src/error-handlers/draft-04/dependencies.js @@ -1,6 +1,3 @@ -import { getSchema } from "@hyperjump/json-schema/experimental"; -import * as Schema from "@hyperjump/browser"; -import * as Instance from "@hyperjump/json-schema/instance/experimental"; import { getErrors } from "../../json-schema-errors.js"; /** @@ -22,25 +19,6 @@ const dependenciesErrorHandler = async (normalizedErrors, instance, localization const dependentSchemaErrors = await getErrors(dependentSchemaOutput, instance, localization); errors.push(...dependentSchemaErrors); } - - const dependencies = await getSchema(schemaLocation); - for await (const [propertyName, dependency] of Schema.entries(dependencies)) { - if (!Instance.has(propertyName, instance)) { - continue; - } - - if (Schema.typeOf(dependency) !== "array") { - continue; - } - - const dependentRequired = /** @type {string[]} */ (Schema.value(dependency)); - const missing = dependentRequired.filter((required) => !Instance.has(required, instance)); - errors.push({ - message: localization.getRequiredErrorMessage(missing), - instanceLocation: Instance.uri(instance), - schemaLocations: [schemaLocation] - }); - } } return errors; diff --git a/src/error-handlers/required.js b/src/error-handlers/required.js index b9677a2..b06f88c 100644 --- a/src/error-handlers/required.js +++ b/src/error-handlers/required.js @@ -8,32 +8,93 @@ import * as Instance from "@hyperjump/json-schema/instance/experimental"; /** @type ErrorHandler */ const requiredErrorHandler = async (normalizedErrors, instance, localization) => { - /** @type ErrorObject[] */ - const errors = []; + const allMissingRequired = new Set(); + const allSchemaLocations = new Set(); - for (const schemaLocation in normalizedErrors["https://json-schema.org/keyword/required"]) { - if (normalizedErrors["https://json-schema.org/keyword/required"][schemaLocation]) { - continue; + const requiredErrors = normalizedErrors["https://json-schema.org/keyword/required"]; + if (requiredErrors) { + for (const schemaLocation in requiredErrors) { + if (requiredErrors[schemaLocation] === false) { + allSchemaLocations.add(schemaLocation); + const keyword = await getSchema(schemaLocation); + const required = /** @type string[] */ (Schema.value(keyword)); + + for (const propertyName of required) { + if (!Instance.has(propertyName, instance)) { + allMissingRequired.add(propertyName); + } + } + } + } + } + + const dependentRequiredErrors = normalizedErrors["https://json-schema.org/keyword/dependentRequired"]; + if (dependentRequiredErrors) { + for (const schemaLocation in dependentRequiredErrors) { + if (dependentRequiredErrors[schemaLocation] === false) { + allSchemaLocations.add(schemaLocation); + const keyword = await getSchema(schemaLocation); + const dependentRequired = /** @type Record */ (Schema.value(keyword)); + + for (const propertyName in dependentRequired) { + if (Instance.has(propertyName, instance)) { + for (const requiredPropertyName of dependentRequired[propertyName]) { + if (!Instance.has(requiredPropertyName, instance)) { + allMissingRequired.add(requiredPropertyName); + } + } + } + } + } } + } + + const dependenciesErrors = normalizedErrors["https://json-schema.org/keyword/draft-04/dependencies"]; + if (dependenciesErrors) { + for (const schemaLocation in dependenciesErrors) { + const dependencyValue = dependenciesErrors[schemaLocation]; + if (dependencyValue === false || Array.isArray(dependencyValue)) { + const keyword = await getSchema(schemaLocation); + const dependencies = /** @type Record */ (Schema.value(keyword)); - const keyword = await getSchema(schemaLocation); - const required = /** @type string[] */ (Schema.value(keyword)); + let hasArrayFormDependencies = false; + for (const propertyName in dependencies) { + if (Instance.has(propertyName, instance)) { + const dependency = dependencies[propertyName]; + if (Array.isArray(dependency)) { + hasArrayFormDependencies = true; + const dependencyArray = /** @type {string[]} */ (dependency); + for (const requiredPropertyName of dependencyArray) { + if (!Instance.has(requiredPropertyName, instance)) { + allMissingRequired.add(requiredPropertyName); + } + } + } + } + } - const missingRequired = []; - for (const propertyName of required) { - if (!Instance.has(propertyName, instance)) { - missingRequired.push(propertyName); + if (hasArrayFormDependencies) { + allSchemaLocations.add(schemaLocation); + } } } + } + + if (allMissingRequired.size > 0) { + /** @type {string[]} */ + const missingProperties = [...allMissingRequired].sort(); + + /** @type {string[]} */ + const locations = [...allSchemaLocations].sort(); - errors.push({ - message: localization.getRequiredErrorMessage(missingRequired), + return [{ + message: localization.getRequiredErrorMessage(missingProperties), instanceLocation: Instance.uri(instance), - schemaLocations: [schemaLocation] - }); + schemaLocations: locations + }]; } - return errors; + return []; }; export default requiredErrorHandler; diff --git a/src/index.js b/src/index.js index 1bff19e..ce48b7f 100644 --- a/src/index.js +++ b/src/index.js @@ -58,7 +58,6 @@ import booleanSchemaErrorHandler from "./error-handlers/boolean-schema.js"; import constErrorHandler from "./error-handlers/const.js"; import containsErrorHandler from "./error-handlers/contains.js"; import dependenciesErrorHandler from "./error-handlers/draft-04/dependencies.js"; -import dependentRequiredErrorHandler from "./error-handlers/dependentRequired.js"; import enumErrorHandler from "./error-handlers/enum.js"; import exclusiveMaximumErrorHandler from "./error-handlers/exclusiveMaximum.js"; import exclusiveMinimumErrorHandler from "./error-handlers/exclusiveMinimum.js"; @@ -144,7 +143,6 @@ addErrorHandler(booleanSchemaErrorHandler); addErrorHandler(constErrorHandler); addErrorHandler(containsErrorHandler); addErrorHandler(dependenciesErrorHandler); -addErrorHandler(dependentRequiredErrorHandler); addErrorHandler(enumErrorHandler); addErrorHandler(exclusiveMaximumErrorHandler); addErrorHandler(exclusiveMinimumErrorHandler); diff --git a/src/normalization-handlers/draft-04/dependencies.js b/src/normalization-handlers/draft-04/dependencies.js index e2cf5c9..b4de39f 100644 --- a/src/normalization-handlers/draft-04/dependencies.js +++ b/src/normalization-handlers/draft-04/dependencies.js @@ -12,18 +12,18 @@ const dependenciesNormalizationHandler = { const outputs = []; if (Instance.typeOf(instance) !== "object") { - return outputs; + return undefined; } for (const [propertyName, dependency] of dependencies) { - if (!Instance.has(propertyName, instance) || typeof dependency !== "string") { + if (!Instance.has(propertyName, instance)) { continue; } - - outputs.push(evaluateSchema(dependency, instance, context)); + if (typeof dependency === "string") { + outputs.push(evaluateSchema(dependency, instance, context)); + } } - - return outputs; + return outputs.length > 0 ? outputs : undefined; } }; diff --git a/src/test-suite/tests/dependencies.json b/src/test-suite/tests/dependencies.json index d9bd39a..20b57f2 100644 --- a/src/test-suite/tests/dependencies.json +++ b/src/test-suite/tests/dependencies.json @@ -1,6 +1,5 @@ { "$schema": "../test-suite.schema.json", - "description": "The dependentRequired, dependentSchemas, and dependencies keywords", "tests": [ { @@ -8,8 +7,13 @@ "compatibility": "2019", "schema": { "dependentRequired": { - "foo": ["bar", "baz"], - "aaa": ["bbb"] + "foo": [ + "bar", + "baz" + ], + "aaa": [ + "bbb" + ] } }, "instance": { @@ -22,10 +26,17 @@ "messageId": "required-message", "messageParams": { "count": 2, - "required": { "and": ["baz", "bbb"] } + "required": { + "and": [ + "baz", + "bbb" + ] + } }, "instanceLocation": "#", - "schemaLocations": ["#/dependentRequired"] + "schemaLocations": [ + "#/dependentRequired" + ] } ] }, @@ -34,8 +45,13 @@ "compatibility": "<=7", "schema": { "dependencies": { - "foo": ["bar", "baz"], - "aaa": ["bbb"] + "foo": [ + "bar", + "baz" + ], + "aaa": [ + "bbb" + ] } }, "instance": { @@ -47,20 +63,18 @@ { "messageId": "required-message", "messageParams": { - "count": 1, - "required": { "and": ["baz"] } - }, - "instanceLocation": "#", - "schemaLocations": ["#/dependencies"] - }, - { - "messageId": "required-message", - "messageParams": { - "count": 1, - "required": { "and": ["bbb"] } + "count": 2, + "required": { + "and": [ + "baz", + "bbb" + ] + } }, "instanceLocation": "#", - "schemaLocations": ["#/dependencies"] + "schemaLocations": [ + "#/dependencies" + ] } ] }, @@ -69,7 +83,9 @@ "compatibility": "2019", "schema": { "dependentRequired": { - "foo": ["bar"] + "foo": [ + "bar" + ] } }, "instance": { @@ -83,7 +99,9 @@ "compatibility": "<=7", "schema": { "dependencies": { - "foo": ["bar"] + "foo": [ + "bar" + ] } }, "instance": { @@ -97,8 +115,17 @@ "compatibility": "2019", "schema": { "dependentSchemas": { - "foo": { "required": ["bar", "baz"] }, - "aaa": { "required": ["bbb"] } + "foo": { + "required": [ + "bar", + "baz" + ] + }, + "aaa": { + "required": [ + "bbb" + ] + } } }, "instance": { @@ -110,20 +137,19 @@ { "messageId": "required-message", "messageParams": { - "count": 1, - "required": { "and": ["baz"] } - }, - "instanceLocation": "#", - "schemaLocations": ["#/dependentSchemas/foo/required"] - }, - { - "messageId": "required-message", - "messageParams": { - "count": 1, - "required": { "and": ["bbb"] } + "count": 2, + "required": { + "and": [ + "baz", + "bbb" + ] + } }, "instanceLocation": "#", - "schemaLocations": ["#/dependentSchemas/aaa/required"] + "schemaLocations": [ + "#/dependentSchemas/aaa/required", + "#/dependentSchemas/foo/required" + ] } ] }, @@ -132,8 +158,17 @@ "compatibility": "<=7", "schema": { "dependencies": { - "foo": { "required": ["bar", "baz"] }, - "aaa": { "required": ["bbb"] } + "foo": { + "required": [ + "bar", + "baz" + ] + }, + "aaa": { + "required": [ + "bbb" + ] + } } }, "instance": { @@ -146,19 +181,31 @@ "messageId": "required-message", "messageParams": { "count": 1, - "required": { "and": ["baz"] } + "required": { + "and": [ + "baz" + ] + } }, "instanceLocation": "#", - "schemaLocations": ["#/dependencies/foo/required"] + "schemaLocations": [ + "#/dependencies/foo/required" + ] }, { "messageId": "required-message", "messageParams": { "count": 1, - "required": { "and": ["bbb"] } + "required": { + "and": [ + "bbb" + ] + } }, "instanceLocation": "#", - "schemaLocations": ["#/dependencies/aaa/required"] + "schemaLocations": [ + "#/dependencies/aaa/required" + ] } ] }, @@ -167,7 +214,11 @@ "compatibility": "2019", "schema": { "dependentSchemas": { - "foo": { "required": ["bar"] } + "foo": { + "required": [ + "bar" + ] + } } }, "instance": 42, @@ -178,7 +229,11 @@ "compatibility": "<=7", "schema": { "dependentSchemas": { - "foo": { "required": ["bar"] } + "foo": { + "required": [ + "bar" + ] + } } }, "instance": 42, @@ -188,7 +243,11 @@ "description": "dependentSchemas pass", "schema": { "dependentSchemas": { - "foo": { "required": ["bar"] } + "foo": { + "required": [ + "bar" + ] + } } }, "instance": { @@ -201,7 +260,11 @@ "description": "schem-form dependencies pass", "schema": { "dependencies": { - "foo": { "required": ["bar"] } + "foo": { + "required": [ + "bar" + ] + } } }, "instance": { @@ -215,8 +278,15 @@ "compatibility": "<=7", "schema": { "dependencies": { - "foo": { "required": ["bar", "baz"] }, - "aaa": ["bbb"] + "foo": { + "required": [ + "bar", + "baz" + ] + }, + "aaa": [ + "bbb" + ] } }, "instance": { @@ -229,21 +299,33 @@ "messageId": "required-message", "messageParams": { "count": 1, - "required": { "and": ["baz"] } + "required": { + "and": [ + "baz" + ] + } }, "instanceLocation": "#", - "schemaLocations": ["#/dependencies/foo/required"] + "schemaLocations": [ + "#/dependencies/foo/required" + ] }, { "messageId": "required-message", "messageParams": { "count": 1, - "required": { "and": ["bbb"] } + "required": { + "and": [ + "bbb" + ] + } }, "instanceLocation": "#", - "schemaLocations": ["#/dependencies"] + "schemaLocations": [ + "#/dependencies" + ] } ] } ] -} +} \ No newline at end of file diff --git a/src/test-suite/tests/required.json b/src/test-suite/tests/required.json index 6ac31e6..1aab783 100644 --- a/src/test-suite/tests/required.json +++ b/src/test-suite/tests/required.json @@ -14,13 +14,35 @@ "messageId": "required-message", "messageParams": { "count": 1, - "required": { "or": ["bar"] } + "required": { "and": ["bar"] } }, "instanceLocation": "#", "schemaLocations": ["#/required"] } ] }, + { + "description": "combined required and dependentRequired", + "compatibility": ">=2019", + "schema": { + "required": ["foo", "bar"], + "dependentRequired": { + "a": ["b"] + } + }, + "instance": { "a": 1 }, + "errors": [ + { + "messageId": "required-message", + "messageParams": { + "count": 3, + "required": { "and": ["b", "bar", "foo"] } + }, + "instanceLocation": "#", + "schemaLocations": ["#/dependentRequired", "#/required"] + } + ] + }, { "description": "required pass", "schema": {