Skip to content

Commit

Permalink
Try to be more helpful when someone forgets a slash in JSON pointers.
Browse files Browse the repository at this point in the history
  • Loading branch information
Julian committed Mar 26, 2023
1 parent a4a9318 commit 4614520
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/spelling-wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ autodetecting
boolean
changelog
deduplication
dereferenced
deserialized
discoverability
filesystem
Expand Down
10 changes: 10 additions & 0 deletions referencing/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,16 @@ def anchor(self, uri: URI, name: str):
value = registry._anchors.get((uri, name))
if value is not None:
return Retrieved(value=value, registry=registry)
if "/" in name:
raise exceptions.InvalidAnchor(
ref=uri,
resource=self[uri],
anchor=name,
suggestion=(
f"You may have intended to use '#/{name}'. The slash is "
"required *before each* segment of a JSON Pointer."
),
)
raise exceptions.NoSuchAnchor(ref=uri, resource=self[uri], anchor=name)

def contents(self, uri: URI) -> D:
Expand Down
24 changes: 24 additions & 0 deletions referencing/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,27 @@ def __str__(self):
return (
f"{self.anchor!r} does not exist within {self.resource.contents!r}"
)


@frozen
class InvalidAnchor(Unresolvable):
"""
An anchor which could never exist in a resource was dereferenced.
It is somehow syntactically invalid.
"""

resource: Resource[Any]
anchor: str
_suggestion: str | None = attrs.field(default=None, alias="suggestion")

def __str__(self):
suggestion = (
""
if self._suggestion is None
else (f" You may have intended to use {self._suggestion}.")

This comment has been minimized.

Copy link
@ikonst

ikonst Mar 27, 2023

Seeing that at the call site

suggestion=(
                    f"You may have intended to use '#/{name}'. The slash is "
                    "required *before each* segment of a JSON Pointer."
),

shouldn't this be

else " {self._suggestion}"

?

)
return (
f"'#{self.anchor}' is not a valid anchor, neither as a "
f"plain name anchor nor as a JSON Pointer.{suggestion}"
)
17 changes: 17 additions & 0 deletions referencing/tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,23 @@ def test_lookup_non_existent_anchor(self):
anchor="noSuchAnchor",
)

def test_lookup_invalid_JSON_pointerish_anchor(self):
resolver = Registry().resolver_with_root(
ID_AND_CHILDREN.create_resource(
{
"ID": "http://example.com/",
"foo": {"bar": 12},
},
),
)

valid = resolver.lookup("#/foo/bar")
assert valid.contents == 12

with pytest.raises(exceptions.InvalidAnchor) as e:

This comment has been minimized.

Copy link
@ikonst

ikonst Mar 27, 2023

You might want to pytest.raises(..., match="...") as a smoke test for the error message.

resolver.lookup("#foo/bar")
assert " '#/foo/bar'" in str(e.value)

def test_lookup_retrieved_resource(self):
resource = Resource.opaque(contents={"foo": "baz"})
resolver = Registry(retrieve=lambda uri: resource).resolver()
Expand Down

0 comments on commit 4614520

Please sign in to comment.