• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SlowAPI / fast-json-pointer / 3798344209

pending completion
3798344209

push

github

Tristan Sweeney
Clean up a bit

6 of 6 new or added lines in 2 files covered. (100.0%)

285 of 294 relevant lines covered (96.94%)

0.97 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/src/fast_json_pointer/rfc6901_parser.py
1
"""Implements json pointer parsing. See `RFC 6901 Section 4
1✔
2
<https://www.rfc-editor.org/rfc/rfc6901#section-4>`_ for the specification.
3
"""
4

5
import re
1✔
6
from typing import *
1✔
7

8
from .exceptions import ParseException
1✔
9

10
RE_INVALID_ESCAPE = re.compile("(~[^01]|~$)")
1✔
11

12

13
def validate(pointer: str) -> None:
1✔
14
    """Validate that a string is a well formed json pointer.
15

16
    :raises: :exc:`.ParseException`: If json pointer is invalid.
17

18
    >>> validate('') # empty string is fine, means "whole json object"
19
    >>> validate('foo') # parts must lead with '/'
20
    Traceback (most recent call last):
21
    fast_json_pointer.exceptions.ParseException: ...
22
    >>> validate('/foo~') # ~ is the escape char, can't be solo
23
    Traceback (most recent call last):
24
    fast_json_pointer.exceptions.ParseException: ...
25
    >>> validate('/~2/foo') # only ~0, ~1 are valid escapes
26
    Traceback (most recent call last):
27
    fast_json_pointer.exceptions.ParseException: ...
28
    """
29

30
    if len(pointer) > 0 and not pointer.startswith("/"):
1✔
31
        raise ParseException("JSON pointers must be empty or start with '/'")
1✔
32

33
    if match := RE_INVALID_ESCAPE.search(pointer):
1✔
34
        raise ParseException("Found invalid escape {}".format(match.group()))
1✔
35

36

37
def parse(pointer: str) -> list[str]:
1✔
38
    r"""Parse a json pointer into a list of unescaped path parts.
39

40
    :raises: :exc:`.ParseException`: If json pointer is invalid.
41

42
    >>> parse('') # empty string is "the whole json object"
43
    []
44
    >>> parse('/') # keys can be zero-length strings
45
    ['']
46
    >>> parse('/ //  ') # which can look funky
47
    [' ', '', '  ']
48
    >>> parse('/foo/m~0n/a~1b') # ~1 escapes /, ~0 escapes ~
49
    ['foo', 'm~n', 'a/b']
50
    >>> parse('/c%d/e^f') # funky symbols are fine too!
51
    ['c%d', 'e^f']
52
    >>> parse(r'/i\\j/g|h/k\l') # r-string avoids escaping backslashes
53
    ['i\\\\j', 'g|h', 'k\\l']
54
    """
55
    validate(pointer)
1✔
56

57
    parts = pointer.split("/")
1✔
58
    # discard "empty" str, as "/foo/bar".split() becomes ["", "foo", "bar"]
59
    parts.pop(0)
1✔
60
    return [unescape(p) for p in parts]
1✔
61

62

63
def unparse(parts: Iterable[str]) -> str:
1✔
64
    r"""Combine an iterable of unescaped path parts into a json pointer.
65

66
    >>> unparse([])
67
    ''
68
    >>> unparse([''])
69
    '/'
70
    >>> unparse([' ', '', '  '])
71
    '/ //  '
72
    >>> unparse(['foo', 'm~n', 'a/b'])
73
    '/foo/m~0n/a~1b'
74
    >>> unparse(['c%d', 'e^f'])
75
    '/c%d/e^f'
76
    >>> unparse([r'i\\j', 'g|h', r'k\l'])
77
    '/i\\\\j/g|h/k\\l'
78
    """
79
    return "".join("/" + escape(part) for part in parts)
1✔
80

81

82
def escape(part: str) -> str:
1✔
83
    """Escape a path part.
84

85
    >>> escape("foo")
86
    'foo'
87
    >>> escape("m~/0")
88
    'm~0~10'
89
    """
90
    # Escape `~` first! https://www.rfc-editor.org/rfc/rfc6901#section-4
91
    return part.replace("~", "~0").replace("/", "~1")
1✔
92

93

94
def unescape(part: str) -> str:
1✔
95
    """Unescape a path part.
96

97
    >>> unescape("foo")
98
    'foo'
99
    >>> unescape("m~0~10")
100
    'm~/0'
101
    """
102
    # Unscape `~` last! https://www.rfc-editor.org/rfc/rfc6901#section-4
103
    return part.replace("~1", "/").replace("~0", "~")
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc