Skip to content

Commit 9a957d7

Browse files
committed
Unambiguously quote and escape properties in JSON path rendering
Fixes #1389
1 parent 1145521 commit 9a957d7

File tree

2 files changed

+126
-1
lines changed

2 files changed

+126
-1
lines changed

jsonschema/exceptions.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from textwrap import dedent, indent
99
from typing import TYPE_CHECKING, Any, ClassVar
1010
import heapq
11+
import re
1112
import warnings
1213

1314
from attrs import define
@@ -23,6 +24,8 @@
2324
WEAK_MATCHES: frozenset[str] = frozenset(["anyOf", "oneOf"])
2425
STRONG_MATCHES: frozenset[str] = frozenset()
2526

27+
JSON_PATH_COMPATIBLE_PROPERTY_PATTERN = re.compile("^[a-zA-Z][a-zA-Z0-9_]*$")
28+
2629
_unset = _utils.Unset()
2730

2831

@@ -152,8 +155,11 @@ def json_path(self) -> str:
152155
for elem in self.absolute_path:
153156
if isinstance(elem, int):
154157
path += "[" + str(elem) + "]"
155-
else:
158+
elif JSON_PATH_COMPATIBLE_PROPERTY_PATTERN.match(elem):
156159
path += "." + elem
160+
else:
161+
escaped_elem = elem.replace("\\", "\\\\").replace("'", r"\'")
162+
path += "['" + escaped_elem + "']"
157163
return path
158164

159165
def _set(

jsonschema/tests/test_exceptions.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,3 +700,122 @@ class TestHashable(TestCase):
700700
def test_hashable(self):
701701
{exceptions.ValidationError("")}
702702
{exceptions.SchemaError("")}
703+
704+
705+
class TestJsonPathRendering(TestCase):
706+
def test_str(self):
707+
e = exceptions.ValidationError(
708+
path=["x"],
709+
message="1",
710+
validator="foo",
711+
instance="i1",
712+
)
713+
self.assertEqual(e.json_path, "$.x")
714+
715+
def test_empty_str(self):
716+
e = exceptions.ValidationError(
717+
path=[""],
718+
message="1",
719+
validator="foo",
720+
instance="i1",
721+
)
722+
self.assertEqual(e.json_path, "$['']")
723+
724+
def test_numeric_str(self):
725+
e = exceptions.ValidationError(
726+
path=["1"],
727+
message="1",
728+
validator="foo",
729+
instance="i1",
730+
)
731+
self.assertEqual(e.json_path, "$['1']")
732+
733+
def test_period_str(self):
734+
e = exceptions.ValidationError(
735+
path=["."],
736+
message="1",
737+
validator="foo",
738+
instance="i1",
739+
)
740+
self.assertEqual(e.json_path, "$['.']")
741+
742+
def test_single_quote_str(self):
743+
e = exceptions.ValidationError(
744+
path=["'"],
745+
message="1",
746+
validator="foo",
747+
instance="i1",
748+
)
749+
self.assertEqual(e.json_path, r"$['\'']")
750+
751+
def test_space_str(self):
752+
e = exceptions.ValidationError(
753+
path=[" "],
754+
message="1",
755+
validator="foo",
756+
instance="i1",
757+
)
758+
self.assertEqual(e.json_path, "$[' ']")
759+
760+
def test_backslash_str(self):
761+
e = exceptions.ValidationError(
762+
path=["\\"],
763+
message="1",
764+
validator="foo",
765+
instance="i1",
766+
)
767+
self.assertEqual(e.json_path, r"$['\\']")
768+
769+
def test_backslash_single_quote(self):
770+
e = exceptions.ValidationError(
771+
path=[r"\'"],
772+
message="1",
773+
validator="foo",
774+
instance="i1",
775+
)
776+
self.assertEqual(e.json_path, r"$['\\\'']")
777+
778+
def test_underscore(self):
779+
e = exceptions.ValidationError(
780+
path=["_"],
781+
message="1",
782+
validator="foo",
783+
instance="i1",
784+
)
785+
self.assertEqual(e.json_path, r"$['_']")
786+
787+
def test_double_quote(self):
788+
e = exceptions.ValidationError(
789+
path=['"'],
790+
message="1",
791+
validator="foo",
792+
instance="i1",
793+
)
794+
self.assertEqual(e.json_path, """$['"']""")
795+
796+
def test_hyphen(self):
797+
e = exceptions.ValidationError(
798+
path=["-"],
799+
message="1",
800+
validator="foo",
801+
instance="i1",
802+
)
803+
self.assertEqual(e.json_path, "$['-']")
804+
805+
def test_json_path_injection(self):
806+
e = exceptions.ValidationError(
807+
path=["a[0]"],
808+
message="1",
809+
validator="foo",
810+
instance="i1",
811+
)
812+
self.assertEqual(e.json_path, "$['a[0]']")
813+
814+
def test_open_bracket(self):
815+
e = exceptions.ValidationError(
816+
path=["["],
817+
message="1",
818+
validator="foo",
819+
instance="i1",
820+
)
821+
self.assertEqual(e.json_path, "$['[']")

0 commit comments

Comments
 (0)