From 51b87045b2beeb8564f91fa02c1b751c4ae29ab9 Mon Sep 17 00:00:00 2001 From: Cognis Digital Date: Fri, 24 Jul 2026 23:20:16 -0400 Subject: [PATCH] Raise PointerToNowhere for non-integer array-index pointer segments A JSON pointer segment that indexes into an array but is not a valid integer (e.g. /foo or /-) previously caused int() to raise a raw ValueError that escaped Resource.pointer, rather than the documented PointerToNowhere. Per RFC 6901 such a segment references a location that does not exist, so treat it the same as an out-of-range index and raise PointerToNowhere. --- referencing/_core.py | 9 ++++++++- referencing/tests/test_core.py | 12 ++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/referencing/_core.py b/referencing/_core.py index db926a2..770142e 100644 --- a/referencing/_core.py +++ b/referencing/_core.py @@ -264,7 +264,14 @@ def pointer(self, pointer: str, resolver: Resolver[D]) -> Resolved[D]: segments: list[int | str] = [] for segment in unquote(pointer[1:]).split("/"): if isinstance(contents, Sequence): - segment = int(segment) + try: + segment = int(segment) + except ValueError as value_error: + error = exceptions.PointerToNowhere( + ref=pointer, + resource=self, + ) + raise error from value_error else: segment = segment.replace("~1", "/").replace("~0", "~") try: diff --git a/referencing/tests/test_core.py b/referencing/tests/test_core.py index 3edddbc..14b52ba 100644 --- a/referencing/tests/test_core.py +++ b/referencing/tests/test_core.py @@ -762,6 +762,18 @@ def test_lookup_non_existent_pointer_to_array_index(self): resource=resource, ) + def test_lookup_non_integer_pointer_to_array_index(self): + resource = Resource.opaque([1, 2, 4, 8]) + resolver = Registry({"http://example.com/1": resource}).resolver() + ref = "http://example.com/1#/foo" + with pytest.raises(exceptions.Unresolvable) as e: + resolver.lookup(ref) + assert e.value == exceptions.PointerToNowhere( + ref="/foo", + resource=resource, + ) + assert str(e.value) == "'/foo' does not exist within [1, 2, 4, 8]" + def test_lookup_pointer_to_empty_string(self): resolver = Registry().resolver_with_root(Resource.opaque({"": {}})) assert resolver.lookup("#/").contents == {}