-
-
Notifications
You must be signed in to change notification settings - Fork 14
filter anyOf and oneOf alternative #180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,10 @@ | ||
| import * as Instance from "@hyperjump/json-schema/instance/experimental"; | ||
| import * as JsonPointer from "@hyperjump/json-pointer"; | ||
| import * as Pact from "@hyperjump/pact"; | ||
| import { getErrors } from "../json-schema-errors.js"; | ||
|
|
||
| /** | ||
| * @import { ErrorHandler, ErrorObject } from "../index.d.ts" | ||
| * @import { ErrorHandler, ErrorObject, NormalizedOutput } from "../index.d.ts" | ||
| */ | ||
|
|
||
| /** @type ErrorHandler */ | ||
|
|
@@ -15,32 +17,65 @@ const anyOfErrorHandler = async (normalizedErrors, instance, localization) => { | |
| if (typeof anyOf === "boolean") { | ||
| continue; | ||
| } | ||
|
|
||
| const alternatives = []; | ||
| const instanceLocation = Instance.uri(instance); | ||
| let filtered = anyOf; | ||
|
|
||
| for (const alternative of anyOf) { | ||
| const typeErrors = alternative[instanceLocation]["https://json-schema.org/keyword/type"]; | ||
| const match = !typeErrors || Object.values(typeErrors).every((isValid) => isValid); | ||
| if (Instance.typeOf(instance) === "object") { | ||
| const instanceProps = Pact.collectSet( | ||
| Pact.map( | ||
| (keyNode) => /** @type {string} */ (Instance.value(keyNode)), | ||
| Instance.keys(instance) | ||
| ) | ||
| ); | ||
| const prefix = `${instanceLocation}/`; | ||
|
|
||
| if (match) { | ||
| alternatives.push(await getErrors(alternative, instance, localization)); | ||
| } | ||
| } | ||
| filtered = []; | ||
| for (const alternative of anyOf) { | ||
| const typeResults = alternative[instanceLocation]["https://json-schema.org/keyword/type"]; | ||
| if (typeResults && !Object.values(typeResults).every((isValid) => isValid)) { | ||
| continue; | ||
| } | ||
|
|
||
| if (alternatives.length === 0) { | ||
| const declaredProps = Object.keys(alternative) | ||
| .filter((loc) => loc.startsWith(prefix)) | ||
| .map((loc) => /** @type {string} */ (Pact.head(JsonPointer.pointerSegments(loc.slice(prefix.length - 1))))); | ||
|
|
||
| if (declaredProps.length > 0 && !declaredProps.some((prop) => instanceProps.has(prop))) { | ||
| continue; | ||
| } | ||
|
|
||
| if (!Pact.some((prop) => propertyPasses(alternative[JsonPointer.append(prop, instanceLocation)]), instanceProps)) { | ||
| continue; | ||
| } | ||
|
|
||
| filtered.push(alternative); | ||
| } | ||
| } else { | ||
| filtered = []; | ||
| for (const alternative of anyOf) { | ||
| alternatives.push(await getErrors(alternative, instance, localization)); | ||
| const typeResults = alternative[instanceLocation]["https://json-schema.org/keyword/type"]; | ||
| if (!typeResults || Object.values(typeResults).every((isValid) => isValid)) { | ||
| filtered.push(alternative); | ||
| } | ||
| } | ||
|
Comment on lines
+54
to
60
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is now duplicated code. You don't need the
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So basically remove if else wrapper put the type check at top for each alternative then if its an object we check rule1 and rule2 right
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes |
||
| } | ||
|
|
||
| if (filtered.length === 0) { | ||
| filtered = anyOf; | ||
| } | ||
|
|
||
| const alternatives = []; | ||
| for (const alternative of filtered) { | ||
| alternatives.push(await getErrors(alternative, instance, localization)); | ||
| } | ||
|
|
||
| if (alternatives.length === 1) { | ||
| errors.push(...alternatives[0]); | ||
| } else { | ||
| errors.push({ | ||
| message: localization.getAnyOfErrorMessage(), | ||
| alternatives: alternatives, | ||
| instanceLocation: Instance.uri(instance), | ||
| instanceLocation, | ||
| schemaLocations: [schemaLocation] | ||
| }); | ||
| } | ||
|
|
@@ -49,4 +84,12 @@ const anyOfErrorHandler = async (normalizedErrors, instance, localization) => { | |
| return errors; | ||
| }; | ||
|
|
||
| /** @type (propOutput: NormalizedOutput[string] | undefined) => boolean */ | ||
| const propertyPasses = (propOutput) => { | ||
| if (!propOutput || Object.keys(propOutput).length === 0) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand the purpose of the check for an empty object and the tests don't cover it. Either remove it or add a test showing it's needed. |
||
| return false; | ||
| } | ||
| return Object.values(propOutput).every((keywordResults) => Object.values(keywordResults).every((v) => v === true)); | ||
| }; | ||
|
|
||
| export default anyOfErrorHandler; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use Pact here to avoid creating intermediate arrays.