Skip to content

Commit 7beee92

Browse files
committed
Give JSON pointer failed lookups slightly more specific errors.
1 parent b7b1379 commit 7beee92

File tree

3 files changed

+46
-14
lines changed

3 files changed

+46
-14
lines changed

referencing/_core.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
from pyrsistent import m, pmap, s
99
from pyrsistent.typing import PMap, PSet
1010

11+
from referencing import exceptions
1112
from referencing._attrs import frozen
12-
from referencing.exceptions import CannotDetermineSpecification, Unresolvable
1313
from referencing.typing import URI, D, Mapping
1414

1515

@@ -89,7 +89,7 @@ def from_contents(
8989
)
9090

9191
if specification is None:
92-
raise CannotDetermineSpecification(contents)
92+
raise exceptions.CannotDetermineSpecification(contents)
9393
return cls(contents=contents, specification=specification) # type: ignore[reportUnknownArgumentType] # noqa: E501
9494

9595
@classmethod
@@ -122,14 +122,23 @@ def subresources(self) -> Iterable[Resource[D]]:
122122
def pointer(self, pointer: str, resolver: Resolver[D]) -> Resolved[D]:
123123
"""
124124
Resolve the given JSON pointer.
125+
126+
Raises:
127+
128+
`exceptions.PointerToNowhere`
129+
130+
if the pointer points to a location not present in the document
125131
"""
126132
contents = self.contents
127133
for segment in unquote(pointer[1:]).split("/"):
128134
if isinstance(contents, Sequence):
129135
segment = int(segment)
130136
else:
131137
segment = segment.replace("~1", "/").replace("~0", "~")
132-
contents = contents[segment] # type: ignore[reportUnknownArgumentType] # noqa: E501
138+
try:
139+
contents = contents[segment] # type: ignore[reportUnknownArgumentType] # noqa: E501
140+
except LookupError:
141+
raise exceptions.PointerToNowhere(ref=pointer, resource=self)
133142
return Resolved(contents=contents, resolver=resolver) # type: ignore[reportUnknownArgumentType] # noqa: E501
134143

135144

@@ -295,21 +304,21 @@ def lookup(self, ref: URI) -> Resolved[D]:
295304
296305
Raises:
297306
298-
`Unresolvable`
307+
`exceptions.Unresolvable`
299308
300309
if the reference isn't resolvable
301310
"""
302311
uri, fragment = urldefrag(urljoin(self._base_uri, ref))
303312
resolver, registry = self, self._registry
304313
resource = registry.get(uri)
305-
try:
306-
if resource is None:
307-
registry = registry.crawl()
314+
if resource is None:
315+
registry = registry.crawl()
316+
try:
308317
resource = registry[uri]
309-
resolver = evolve(resolver, registry=registry)
310-
if fragment.startswith("/"):
311-
return resource.pointer(pointer=fragment, resolver=resolver)
312-
except KeyError:
313-
raise Unresolvable(ref=ref) from None
318+
except KeyError:
319+
raise exceptions.Unresolvable(ref=ref) from None
320+
resolver = evolve(resolver, registry=registry)
321+
if fragment.startswith("/"):
322+
return resource.pointer(pointer=fragment, resolver=resolver)
314323

315324
return Resolved(contents=resource.contents, resolver=resolver)

referencing/exceptions.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class CannotDetermineSpecification(Exception):
2121
contents: Any
2222

2323

24-
@frozen
24+
@attrs.frozen
2525
class Unresolvable(Exception):
2626
"""
2727
A reference was unresolvable.
@@ -33,3 +33,12 @@ def __eq__(self, other: Any) -> bool:
3333
if self.__class__ is not other.__class__:
3434
return NotImplemented
3535
return attrs.astuple(self) == attrs.astuple(other)
36+
37+
38+
@frozen
39+
class PointerToNowhere(Unresolvable):
40+
"""
41+
A JSON Pointer leads to a part of a document that does not exist.
42+
"""
43+
44+
resource: Any

referencing/tests/test_core.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,21 @@ def test_lookup_non_existent_pointer(self):
347347
ref = "http://example.com/1#/foo/bar"
348348
with pytest.raises(exceptions.Unresolvable) as e:
349349
resolver.lookup(ref)
350-
assert e.value == exceptions.Unresolvable(ref=ref)
350+
assert e.value == exceptions.PointerToNowhere(
351+
ref="/foo/bar",
352+
resource=resource,
353+
)
354+
355+
def test_lookup_non_existent_pointer_to_array_index(self):
356+
resource = Resource.opaque([1, 2, 4, 8])
357+
resolver = Registry({"http://example.com/1": resource}).resolver()
358+
ref = "http://example.com/1#/10"
359+
with pytest.raises(exceptions.Unresolvable) as e:
360+
resolver.lookup(ref)
361+
assert e.value == exceptions.PointerToNowhere(
362+
ref="/10",
363+
resource=resource,
364+
)
351365

352366

353367
class TestSpecification:

0 commit comments

Comments
 (0)