Repository: speckit (difficulty: medium)

Issue:
json_pointer_get mishandles keys that literally contain the text "~1" or "~0". Per RFC 6901 the escape sequence "~01" must decode to the literal two-character key "~1": decoding replaces "~1" with "/" first and "~0" with "~" second, exactly in that order. Our decoder does the replacements in the opposite order, so the "~" produced by the "~0" step immediately combines with the following "1" and gets rewritten to "/".

Observed with doc = {"~1": {"~0": "x"}, "~0": "y"}:
- json_pointer_get(doc, "/~01") raises KeyError: '/' ; expected {"~0": "x"}
- json_pointer_get(doc, "/~01/~00") raises the same KeyError; expected "x"
- json_pointer_get(doc, "/~00") correctly returns "y" (single escapes happen to be order-insensitive), which is why this went unnoticed for so long.

Everything else already behaves per the module docstring and RFC 6901 and must keep working: "" returns the whole document; every other pointer must start with "/" (else ValueError); "/a~1b" addresses the key "a/b" and "/m~0n" addresses "m~n"; array tokens must consist only of decimal digits (else IndexError) and out-of-range indices raise IndexError; missing dict keys raise KeyError; descending into a scalar raises KeyError. The full RFC 6901 section 5 example document must resolve exactly as the RFC specifies.

Source files:
core.py: """JSON Pointer (RFC 6901) evaluation for JSON-like documents.

json_pointer_get(doc, pointer) resolves ``pointer`` against ``doc``,
where doc is built from dicts, lists, and scalar leaves.

Contract, per RFC 6901:

- The empty pointer "" returns doc itself.
- Every non-empty pointer must start with "/"; any other non-empty
  pointer raises ValueError.
- The text after the leading "/" is split on "/" into reference
  tokens, and each token is unescaped before lookup: "~1" decodes to
  "/" and "~0" decodes to "~". Decoding must replace "~1" first and
  "~0" second, so that "~01" decodes to the literal two-character key
  "~1" (not to "/") and "~00" decodes to "~0".
- A token applied to a dict is a key lookup; a missing key raises
  KeyError.
- A token applied to a list must consist only of decimal digits; any
  other token (for example "", "-1", or "bar") raises IndexError, and
  a digit token whose value is out of range raises IndexError.
- A token applied to any other value (string, number, bool, None)
  raises KeyError, because scalars have no children.
"""


def _unescape(token):
    """Decode the RFC 6901 escape sequences in one reference token."""
    return token.replace("~0", "~").replace("~1", "/")


def json_pointer_get(doc, pointer):
    """Resolve an RFC 6901 JSON Pointer against doc and return the value."""
    if pointer == "":
        return doc
    if not pointer.startswith("/"):
        raise ValueError("pointer must be empty or start with '/': %r" % (pointer,))
    node = doc
    for token in pointer[1:].split("/"):
        key = _unescape(token)
        if isinstance(node, dict):
            if key not in node:
                raise KeyError(key)
            node = node[key]
        elif isinstance(node, list):
            if not key or not all("0" <= ch <= "9" for ch in key):
                raise IndexError("invalid array index: %r" % (key,))
            index = int(key)
            if index >= len(node):
                raise IndexError("array index out of range: %d" % index)
            node = node[index]
        else:
            raise KeyError(key)
    return node


Test files:
test_core.py: """Tests for json_pointer_get against RFC 6901 semantics."""

import pytest

from core import json_pointer_get

# The RFC 6901 section 5 example document, extended with two keys whose
# names are literally the escape sequences themselves.
DOC = {
    "foo": ["bar", "baz"],
    "": 0,
    "a/b": 1,
    "c%d": 2,
    "e^f": 3,
    "g|h": 4,
    "i\\j": 5,
    "k\"l": 6,
    " ": 7,
    "m~n": 8,
    "~1": {"~0": "deep", "a/b": ["x", "y"]},
    "~0": "plain",
}


def escape(key):
    # RFC 6901 escaping for one reference token: "~" first, then "/".
    return key.replace("~", "~0").replace("/", "~1")


def test_rfc6901_example_document():
    assert json_pointer_get(DOC, "") is DOC
    expected = [
        ("/foo", ["bar", "baz"]),
        ("/foo/0", "bar"),
        ("/foo/1", "baz"),
        ("/", 0),
        ("/a~1b", 1),
        ("/c%d", 2),
        ("/e^f", 3),
        ("/g|h", 4),
        ("/i\\j", 5),
        ("/k\"l", 6),
        ("/ ", 7),
        ("/m~0n", 8),
    ]
    for pointer, value in expected:
        assert json_pointer_get(DOC, pointer) == value, pointer


def test_tilde_escape_decoding():
    assert json_pointer_get(DOC, "/~00") == "plain"
    assert json_pointer_get(DOC, "/~01") == {"~0": "deep", "a/b": ["x", "y"]}
    assert json_pointer_get(DOC, "/~01/~00") == "deep"
    assert json_pointer_get(DOC, "/~01/a~1b/1") == "y"
    # Escaping any top-level key must address exactly that key, so the
    # expected values here are computed, not pinned.
    for key, value in DOC.items():
        assert json_pointer_get(DOC, "/" + escape(key)) is value


def test_error_cases():
    with pytest.raises(ValueError):
        json_pointer_get(DOC, "foo")
    with pytest.raises(ValueError):
        json_pointer_get(DOC, "~01")
    with pytest.raises(KeyError):
        json_pointer_get(DOC, "/nope")
    with pytest.raises(KeyError):
        json_pointer_get(DOC, "/foo/0/x")
    with pytest.raises(IndexError):
        json_pointer_get(DOC, "/foo/2")
    with pytest.raises(IndexError):
        json_pointer_get(DOC, "/foo/-1")
    with pytest.raises(IndexError):
        json_pointer_get(DOC, "/foo/bar")
    with pytest.raises(IndexError):
        json_pointer_get(DOC, "/foo/")


Patch:
--- a/core.py
+++ b/core.py
@@ -25,7 +25,7 @@
 
 def _unescape(token):
     """Decode the RFC 6901 escape sequences in one reference token."""
-    return token.replace("~0", "~").replace("~1", "/")
+    return token.replace("~1", "/").replace("~0", "~")
 
 
 def json_pointer_get(doc, pointer):