Skip to content

Commit 5b61940

Browse files
committed
Handle some JSONPath sytnax errors
1 parent cec7a25 commit 5b61940

File tree

2 files changed

+71
-1
lines changed

2 files changed

+71
-1
lines changed

jsonpath/parse.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
from .filter import InfixExpression
3131
from .filter import IntegerLiteral
3232
from .filter import ListLiteral
33+
from .filter import Literal
34+
from .filter import Nil
3335
from .filter import Path
3436
from .filter import PrefixExpression
3537
from .filter import RegexLiteral
@@ -191,6 +193,7 @@ class Parser:
191193
"<=",
192194
"<",
193195
"!=",
196+
"=~",
194197
]
195198
)
196199

@@ -477,6 +480,13 @@ def parse_filter(self, stream: TokenStream) -> Filter:
477480
f"result of {expr.name}() must be compared", token=tok
478481
)
479482

483+
if isinstance(expr, (Literal, Nil)):
484+
raise JSONPathSyntaxError(
485+
"filter expression literals outside of "
486+
"function expressions must be compared",
487+
token=tok,
488+
)
489+
480490
return Filter(env=self.env, token=tok, expression=BooleanExpression(expr))
481491

482492
def parse_boolean(self, stream: TokenStream) -> FilterExpression:
@@ -520,6 +530,20 @@ def parse_infix_expression(
520530
self._raise_for_non_comparable_function(left, tok)
521531
self._raise_for_non_comparable_function(right, tok)
522532

533+
if operator not in self.COMPARISON_OPERATORS:
534+
if isinstance(left, Literal):
535+
raise JSONPathSyntaxError(
536+
"filter expression literals outside of "
537+
"function expressions must be compared",
538+
token=tok,
539+
)
540+
if isinstance(right, Literal):
541+
raise JSONPathSyntaxError(
542+
"filter expression literals outside of "
543+
"function expressions must be compared",
544+
token=tok,
545+
)
546+
523547
return InfixExpression(left, operator, right)
524548

525549
def parse_grouped_expression(self, stream: TokenStream) -> FilterExpression:
@@ -532,14 +556,20 @@ def parse_grouped_expression(self, stream: TokenStream) -> FilterExpression:
532556
raise JSONPathSyntaxError(
533557
"unbalanced parentheses", token=stream.current
534558
)
559+
560+
if stream.current.kind not in self.BINARY_OPERATORS:
561+
raise JSONPathSyntaxError(
562+
f"expected an expression, found '{stream.current.value}'",
563+
token=stream.current,
564+
)
565+
535566
expr = self.parse_infix_expression(stream, expr)
536567

537568
stream.expect(TOKEN_RPAREN)
538569
return expr
539570

540571
def parse_root_path(self, stream: TokenStream) -> FilterExpression:
541572
root = stream.next_token()
542-
assert root.kind in {TOKEN_ROOT, TOKEN_FAKE_ROOT} # XXX:
543573
return RootPath(
544574
JSONPath(
545575
env=self.env,

tests/test_errors.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
from operator import attrgetter
2+
from typing import List
3+
from typing import NamedTuple
4+
15
import pytest
26

37
from jsonpath import JSONPathEnvironment
@@ -28,3 +32,39 @@ def test_function_too_many_params(env: JSONPathEnvironment) -> None:
2832
def test_non_singular_query_is_not_comparable(env: JSONPathEnvironment) -> None:
2933
with pytest.raises(JSONPathTypeError):
3034
env.compile("$[?@.* > 2]")
35+
36+
37+
def test_unbalanced_parens(env: JSONPathEnvironment) -> None:
38+
with pytest.raises(JSONPathSyntaxError):
39+
env.compile("$[?((@.foo)]")
40+
41+
42+
class FilterLiteralTestCase(NamedTuple):
43+
description: str
44+
query: str
45+
46+
47+
# TODO: add these to the CTS?
48+
BAD_FILTER_LITERAL_TEST_CASES: List[FilterLiteralTestCase] = [
49+
FilterLiteralTestCase("just true", "$[?true]"),
50+
FilterLiteralTestCase("just string", "$[?'foo']"),
51+
FilterLiteralTestCase("just int", "$[?2]"),
52+
FilterLiteralTestCase("just float", "$[?2.2]"),
53+
FilterLiteralTestCase("just null", "$[?null]"),
54+
FilterLiteralTestCase("literal and literal", "$[?true && false]"),
55+
FilterLiteralTestCase("literal or literal", "$[?true || false]"),
56+
FilterLiteralTestCase("comparison and literal", "$[?true == false && false]"),
57+
FilterLiteralTestCase("comparison or literal", "$[?true == false || false]"),
58+
FilterLiteralTestCase("literal and comparison", "$[?true && true == false]"),
59+
FilterLiteralTestCase("literal or comparison", "$[?false || true == false]"),
60+
]
61+
62+
63+
@pytest.mark.parametrize(
64+
"case", BAD_FILTER_LITERAL_TEST_CASES, ids=attrgetter("description")
65+
)
66+
def test_filter_literals_must_be_compared(
67+
env: JSONPathEnvironment, case: FilterLiteralTestCase
68+
) -> None:
69+
with pytest.raises(JSONPathSyntaxError):
70+
env.compile(case.query)

0 commit comments

Comments
 (0)