Repository navigation
Positive errors - #280
Merged
Merged
Positive errors#280
Conversation
A 'not' failure happens because its schema passed, so there was no failure to report and the message could only refer to the schema. Error handlers can now define a `success` method that describes how an instance satisfied a keyword, and `getSuccesses` collects those messages for a passing subschema. The 'not' error lists them as alternatives and is worded without mentioning schemas. Success messages are implemented for type, required (including dependentRequired and draft-04 dependencies), and pattern. A 'not' schema with nothing to describe gets the boolean schema message. Nested passing applicators can't be described yet because their subschema output isn't kept when they pass (#120). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When more than one oneOf alternative passes, the error now lists how the instance satisfied each matching alternative so the user can see what needs to change. Messages common to all matches are removed when that leaves each alternative with something to say. Failing alternatives are no longer included. If a match can't be described, the previous message is used without alternatives. Matches are now counted before alternatives are filtered. Previously, passing alternatives without declared properties were filtered out, which reported that none matched when several did. Type messages now come first because the type error handler is registered first, and the not message wording is adjusted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When more items match than maxContains allows, an error is now reported on each matching item describing how it satisfied the 'contains' schema. Reporting on the items rather than the array lets consumers such as the language server put diagnostics on the items that need to change. If every item matched and none can be described, the maxItems message is used. Otherwise, if a match can't be described, the previous message is used. isPassing is now shared by the oneOf and contains handlers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Consumers like the language server put each top-level error at its instanceLocation and present nested messages relative to their parent. Every test now checks that alternatives only contain messages at or below the location of the error they belong to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Success messages now describe what a keyword requires rather than what is true of the instance. A message like "The value is not an object" was misleading when a keyword passed only because the instance was the wrong type, and describing only the matched type gave the wrong advice when negated. dependentRequired also threw on non-objects. Each keyword now has an error, a success, and a negated message. 'not' uses the negated messages through `localization.negated()` and says what is expected rather than what isn't, which is easier to understand than negating an either/or statement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each normalized keyword result is now an object with a `valid` flag and, for applicators, the `outputs` of their subschemas. Applicators that pass now keep their subschema output instead of being reduced to `true`, so handlers can describe what happened inside them. Validator output usually only reports errors, so a keyword it doesn't mention is assumed to pass. That isn't safe inside an applicator that passed because the output doesn't say which subschemas passed or failed. Results there are now unknown (`valid` is undefined) unless the output includes them. Passing results in the output (such as `valid: true` units or annotations) are now indexed as well so more detailed output gives more detailed results. This doesn't change any error messages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
'not' and 'anyOf' can now describe themselves when they appear inside a subschema that passed, such as inside a 'not' or a 'oneOf' alternative. When it's known which subschemas passed, the messages describe only what matters. When the validator's output doesn't say, they describe all the possibilities instead, which is less specific but still correct. Some descriptions need to say that at least one or all of a group of things is true, so there are new messages for those groups. Alternatives are now consistently a list of options where everything in an option is true, so a 'not' error with several possibilities lists them as options. The test suite has a new optional `errorsWithFullResults` for the errors expected when the results of every keyword are known. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
'oneOf' can now describe itself when it appears inside a subschema that passed. It passes when exactly one alternative matches, so describing what would make it fail means describing how none of them could match or how at least two of them could match. Groups of options can now say how many of the options are true, such as "exactly one" or "at least two", using a single message. Describing what would make 'anyOf' fail no longer leaves out alternatives that currently fail. Changing the value to make one alternative fail could make another one pass, so a description that leaves them out can lead the user to a value that's still invalid. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
'contains' can now describe itself when it appears inside a subschema that passed. It passes when the number of matching items is within its range, so describing what would make it fail means describing how too few or too many items could match. When it's known which items match, the description only mentions the items that need to change. Items are separate values, so changing one can't change whether another one matches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
'if' was treated as part of its parent schema, so its result was merged into the parent. When validating with this package's output plugin, a failing 'if' was reported as an error. 'then' and 'else' were merged too, even when they didn't apply. Now 'if', 'then', and 'else' each keep the output of their own subschema. Validators often only report the errors in a 'then' or 'else' subschema and not the keyword itself, so a normalization handler can now say that the keyword's result comes from its subschema when the validator's output doesn't include it. The output plugin now decides whether a keyword's output is merged into its parent based on this package's handlers rather than the validator's. if/then/else can now describe itself when it appears inside a subschema that passed. Now that both ways of getting errors agree, every test in the test suite checks the errors from full results, which default to `errors`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
maximum, exclusiveMaximum, minimum, exclusiveMinimum, multipleOf, maxLength, minLength, maxItems, minItems, uniqueItems, maxProperties, minProperties, const, enum, format-assertion, and draft-04 schema dependencies can now describe what they require and what would make them fail. 'format' isn't described because whether it's an assertion depends on how the validator is configured, which can't be known from its output. Describing it when it isn't an assertion would give the user false information. 'format-assertion' is always an assertion, so it's described. Unknown keywords are never described because we don't know what they require. There's now a test to make sure they stay out of descriptions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Whether 'format' is an assertion depends on how the validator is
configured and whether it supports the format. That can't be known from
the validator's output, so 'format' wasn't described at all. That could
leave a confusing generic message, such as saying no value is allowed
for {"not":{"format":"email"}} when any string that isn't an email
address would pass.
All format keywords are now described with wording that makes clear it
only applies if formats are validated. This includes 'format-assertion',
which the previous commit treated as always being an assertion, because
some validators can be configured not to validate it either.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A schema dependency only applies when the value is an object with the dependency's property, but its subschema was merged into its parent as if it always applied. A description of what would make it fail could suggest a change that removes the property, which would make the dependency not apply at all. When the property was absent, there was nothing to describe, so it fell back to a generic message that could be wrong. dependentSchemas now keeps the output of its subschemas and is described like if/then. Descriptions of what would make it fail include that the value needs to be an object with the property. Schema-form dependencies in draft-04 work the same way. When the validator didn't evaluate a subschema, it's now evaluated without results so it can still be described. Success messages only describe what keywords require, so the results aren't needed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Validators usually only evaluate the 'then' or 'else' branch that applies. The other branch had no output, so it was left out of descriptions of what would make if/then/else fail, even though changing the value could make it apply. It's now evaluated without results so it can be described. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… allow anything
The anyOf and oneOf messages talked about matching "alternatives," which
requires the user to look at the schema. They now describe the listed
options instead.
When more than one oneOf alternative matched and one of them couldn't be
described, the options weren't listed at all. Alternatives that allow any
value, such as `{}` or a schema with only annotations, are now described
as allowing any value. Annotation keywords are marked as such so this can
be determined from the schema.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
properties and prefixItems only produce results for the properties and items that are present, so when an option matched only because they didn't apply, it couldn't be described. Descriptions of what would make them fail also couldn't say that a missing property or item needs to be added. They now record a result for themselves while still merging their subschema results into their parent, so errors don't change. Properties and items that aren't present are described with what they require: either the property or item isn't there or its value passes. To describe a value that doesn't exist yet, a placeholder is used for it. Placeholders aren't described further, which prevents describing recursive schemas forever. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Descriptions of what made a subschema pass are shown as options the user needs to break, such as when more than one oneOf alternative matches. When it was known which anyOf or oneOf alternatives matched, which keywords in a 'not' schema failed, or which way 'if' went, only those were described. Changing the value to break what was described could make another part pass instead, so following the description might not fix the error. anyOf, oneOf, not, if/then/else, and dependentSchemas now always describe their full requirements. Known results are still used for contains because changing one item can't change whether another item matches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
'items' with an array of schemas works like prefixItems, so items that aren't present are now described the same way. Items that aren't present were described using the maxItems messages, such as "has no more than 0 items." That reads like a limit on the number of items rather than whether there's an item at a particular index. There are now messages that describe whether there's an item at an index, like the ones for properties. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
items, additionalItems, and draft-04 single-schema items apply a schema to every item starting at some index. Only the items that are present were described, so an option that matched only because there were no such items couldn't be described, and a description of what would make them fail couldn't say that an item could be added. They're now described with what's required of every item they apply to, including items that could be added. The schema is described for an item that doesn't exist and the description is moved to the array because it applies to any item. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…or every property they apply to These keywords apply a schema to every property, or property name, in some scope. Only the properties that are present were described, so an option that matched only because there were no such properties couldn't be described, and a description of what would make them fail couldn't say that a property could be added. They're now described with what's required of every property they apply to, including properties that could be added. When the schema is `false`, such as `additionalProperties: false`, it's described as not allowing any such properties. Looking up a location that doesn't exist can fail rather than returning nothing, such as the name of a property that isn't present. Those locations are now described using a placeholder like other locations that don't exist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When there weren't enough matching items, the contains message referred to "the 'contains' schema," so the user had to look at the schema to know what was wrong. The message now describes what an item needs to be like. When any item would match, the problem is that the array doesn't have enough items, so the minItems message is used. If the schema can't be described, the message still refers to the schema. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Several handlers described the same two shapes in their own way. properties, prefixItems, dependentSchemas, and if/then/else describe subschemas that only apply under some condition. items, patternProperties, additionalProperties, and propertyNames describe a subschema that applies to every location in some scope. Each shape is now described by a single function so they're described consistently. There's also now a single way to create a placeholder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Most success handlers described each occurrence of a keyword with a single message based on its value, each with its own copy of the same loop. That's now done by a shared function. `error` is now optional for error handlers like `success` is, so handlers that only describe keywords don't need an empty `error`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Some simple applicators recorded a result for themselves so they could be described, which required a separate flag. All simple applicators now record a result whose validity comes from their subschemas, so the flag isn't needed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The type message used "a" for every type, such as "Expected a object." It now uses the right article and says "a value of type" for more than one type. - Messages with counts of characters, items, or properties now use the singular for one, and describe zero and one in a more natural way, such as "an empty string" or "a non-empty array." - Descriptions of what's required of every item or property are shorter and don't say "at least one of the following" when there's only one thing that follows. - Property names are quoted rather than following a colon in the middle of a sentence. How names are quoted is part of the translation. - Removed a stray brace from the English translation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Descriptions of complex schemas can get very large, especially when applicators are nested, and so can the work needed to build them. Descriptions now stop at three levels of nesting and say that further details aren't shown. Groups show at most five entries and say how many more aren't shown. Lists of options show the smallest options first so the simplest ones are the ones that are shown, except for the matching oneOf alternatives, which stay in the order they appear in the schema. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolves #120
This adds success and negation messages so we can construct messages for things that fail because something passed, like multiple
oneOfalternatives passing.This is too big to review, so I'm just going to merge it. But, please let me know if you find any issues.
This was the last blocker for an official release, so that's coming soon.