Fix/error location as jsonpointer - #279
Merged
fredbi merged 8 commits intoAug 9, 2026
Merged
Conversation
Locations were assembled by string concatenation, which lost information and
produced notations no consumer could parse. They are now built from a list of
JSON pointer reference tokens and rendered on demand, so a separator can never
be confused with a token that contains one.
Locations that change:
- array items carry their index again: "a.b" becomes "a.0.b". The index was
set on the schema validator after its sub-validators had been built, so it
never reached the validator raising the error;
- "items[0]" and "allOf[0]" become "items.0" and "allOf.0";
- the "default" and "example" keywords are no longer appended twice;
- a response is located by its operation rather than by a bare status code:
"200.name.default" becomes "paths./pets/{id}.get.responses.200.name.default";
- a required property missing at the root loses its leading separator:
".paths in body is required" becomes "paths in body is required".
The predicates telling a schema apart from plain data (isProperties, isDefault,
isExample) and the recursion guard on visited schemas walk tokens instead of
splitting on dots, so they no longer match mid-token. Trailing indices are
trimmed before the marker lookup: an element of an example is example data, and
schema-only checks stay off it.
Error names keep the dotted rendering, so messages returned by generated servers
are unchanged apart from the locations listed above. The JSON pointer rendering
is available internally; no API exposes it yet.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Frederic BIDON <fredbi@yahoo.com>
Result now knows where every error and warning it holds came from, and reports
it as an RFC 6901 JSON pointer into the validated document:
for _, e := range result.LocatedErrors() {
fmt.Printf("%s: %v\n", e.Pointer, e.Err)
}
/definitions/Pet/name/default: ... must be of type string: "number"
/paths/~1pets~1{id}/get/responses/200/examples/friends/0/name: ...
Errors and Warnings keep their contents and their types, so nothing downstream
has to change and no error value is wrapped. A pointer is empty when the check
that failed has no single location to name, such as a duplicate operation id or
a document that could not be read at all.
Locations are held in slices kept in step with the error slices, and carried
through merges, through the relevance filter and through the result pool. They
are recorded by the validators themselves, so a location is as precise as the
validator that reported the failure: the document-wide schema pass emits true
document pointers, while a check that only knows a parameter by name reports
the name.
Two spec messages move as a result, both now naming the operation they belong
to rather than the parameter or header alone:
- "user.items in body has invalid pattern" becomes
"paths./foo.get.parameters.user.items in body has invalid pattern";
- "X-Foo in header has invalid pattern" becomes
"paths./foo.get.responses.default.headers.X-Foo in header has invalid pattern".
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Frederic BIDON <fredbi@yahoo.com>
An invalid or dubious $ref now reports where it was declared:
/definitions/Bad: invalid ref "file:///etc/passwd"
Both checks iterate the analyzer's references, which are handed over as values
only: its index is keyed by document location, but neither AllRefs nor
AllReferences exposes the keys. The locations are recovered instead by walking
the raw document for "$ref" members, which needs no interpretation of what a
reference points to, and keeps the index in step with the unexpanded spec the
two checks are meant to inspect.
The index is best effort. Example subtrees are skipped, since a "$ref" member
there is plain data rather than a declaration, and a reference declared more
than once keeps the smallest pointer so the answer does not depend on map
iteration order. A miss costs a misleading or empty pointer, never a wrong
verdict: nothing else is decided from it.
Diagnostics that no single location can describe still report an empty
pointer, a spread of remote hosts and a duplicate operation id among them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Frederic BIDON <fredbi@yahoo.com>
A default is a value, so a "$ref" member sitting in one declares nothing and must not be taken for the location of a reference, the same way example values are already skipped. The exception is "default" under "responses", which names a response rather than holding a value, and is commonly a $ref to a shared response. That one keeps being indexed, both at the document level and under an operation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Frederic BIDON <fredbi@yahoo.com>
analysis v0.26.0 exposes AllRefsByLocation, so the reference index no longer has to be rebuilt here by walking the raw document. Inverting the analyzer's map replaces that walk, halving the file. The guesswork goes with it. Deciding whether a "$ref" member was a declaration or a value meant knowing example and default subtrees hold data, with an exception for a default that names a response; the analyzer indexes declarations only, so none of that has to be restated. Locations and the two diagnostics now read the same index, which makes every reference they can report one the index knows where to find. A $ref sitting directly on a shared parameter or shared response is indexed by neither, so nothing that used to be located has stopped being so. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Frederic BIDON <fredbi@yahoo.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #279 +/- ##
==========================================
+ Coverage 92.31% 92.99% +0.67%
==========================================
Files 24 27 +3
Lines 3280 3498 +218
==========================================
+ Hits 3028 3253 +225
+ Misses 166 161 -5
+ Partials 86 84 -2 ☔ View full report in Codecov by Harness. |
A required property that is never defined, and one marked both required and
readOnly, now report the entry of the required array they come from rather
than the definition holding it:
/definitions/Pet/required/1: "notDeclared" is present in required but not
defined as property in definition "Pet"
That is the text a reader has to go and amend, so it is what a consumer
tracking positions in the document wants to anchor on.
The search for the property descends into additionalProperties schemas, so the
two locations are carried apart: the schema being searched moves down, while
the required entry that started the search stays put.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Frederic BIDON <fredbi@yahoo.com>
A parameter was reported at its name, which no document can address: they are
held in an array. Over the fixture specs, that left most pointers unresolvable
and made a consumer walk up to the array before finding anything.
Parameters are now indexed from the unexpanded document, which is the only
place the index survives: expansion merges the parameters an operation declares
with those of its path item, and resolves the ones written as a $ref. The
lookup falls back to the path item, so a parameter declared once for every
operation under a path is found from any of them.
Messages keep naming the parameter. A path token now carries a readable form
beside the addressed one, so the pointer indexes while the message reads as it
did:
/paths/~1pets/get/parameters/1 paths./pets.get.parameters.tags
A parameter too broken to be identified, one with no name, has no index to
point at; the name stands in, as before.
One message moves, and it is a correction: a parameter declared on a path item
is no longer reported under an operation that did not declare it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Frederic BIDON <fredbi@yahoo.com>
Two classes of pointer addressed nothing, so a consumer had to walk up before
finding anything to show.
A finding about something absent is now reported on the value that should hold
it: a missing required property points at the object, not at the property that
is not there. At the root that is the empty pointer, which addresses the whole
document, and Located.Pointer says so rather than calling it unknown.
Tokens a document needs to address a value, but that a message has never
spelled, are marked structural: part of the pointer, absent from the dotted
form. That puts "properties" between a schema and its members, "schema" under
a response, and the media type under its examples:
/definitions/Pet/properties/name/default
definitions.Pet.name.default
The predicates telling a schema apart from plain data skip structural tokens.
They ask what a value is, and plumbing must not answer: an example addressed
through its media type is still an example.
Not covered, and left as it stands: a parameter or response written as a $ref
is located where it is declared, so anything below that site resolves in the
shared definition rather than there.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Frederic BIDON <fredbi@yahoo.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.
Change type
Please select: 🆕 New feature or enhancement|🔧 Bug fix'|📃 Documentation update
Short description
Fixes
Full description
Checklist