Skip to content

Commit 10d59b1

Browse files
authored
Merge pull request #60 from jg-rp/syntax-errors
JSONPath syntax errors
2 parents cec7a25 + 2aa5475 commit 10d59b1

File tree

6 files changed

+81
-3
lines changed

6 files changed

+81
-3
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Python JSONPath Change Log
22

3+
## Version 1.1.2 (unreleased)
4+
5+
- Fixed handling of JSONPath literals in filter expressions. We now raise a `JSONPathSyntaxError` if a filter expression literal is not part of a comparison or function expression. See [jsonpath-compliance-test-suite#81](https://github.com/jsonpath-standard/jsonpath-compliance-test-suite/pull/81).
6+
37
## Version 1.1.1
48

59
**Fixes**

jsonpath/__about__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# SPDX-FileCopyrightText: 2023-present James Prior <[email protected]>
22
#
33
# SPDX-License-Identifier: MIT
4-
__version__ = "1.1.1"
4+
__version__ = "1.1.2"

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_compliance.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ class Case:
3434
"basic, no leading whitespace": "flexible whitespace policy",
3535
"basic, no trailing whitespace": "flexible whitespace policy",
3636
"basic, bald descendant segment": "almost has a consensus",
37+
"filter, index segment on object, selects nothing": "flexible selector policy",
38+
"functions, match, dot matcher on \\u2028": "standard library re policy",
39+
"functions, match, dot matcher on \\u2029": "standard library re policy",
40+
"functions, search, dot matcher on \\u2028": "standard library re policy",
41+
"functions, search, dot matcher on \\u2029": "standard library re policy",
3742
"functions, match, filter, match function, unicode char class, uppercase": "\\p not supported", # noqa: E501
3843
"functions, match, filter, match function, unicode char class negated, uppercase": "\\P not supported", # noqa: E501
3944
"functions, search, filter, search function, unicode char class, uppercase": "\\p not supported", # noqa: E501

tests/test_errors.py

Lines changed: 39 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,38 @@ 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+
BAD_FILTER_LITERAL_TEST_CASES: List[FilterLiteralTestCase] = [
48+
FilterLiteralTestCase("just true", "$[?true]"),
49+
FilterLiteralTestCase("just string", "$[?'foo']"),
50+
FilterLiteralTestCase("just int", "$[?2]"),
51+
FilterLiteralTestCase("just float", "$[?2.2]"),
52+
FilterLiteralTestCase("just null", "$[?null]"),
53+
FilterLiteralTestCase("literal and literal", "$[?true && false]"),
54+
FilterLiteralTestCase("literal or literal", "$[?true || false]"),
55+
FilterLiteralTestCase("comparison and literal", "$[?true == false && false]"),
56+
FilterLiteralTestCase("comparison or literal", "$[?true == false || false]"),
57+
FilterLiteralTestCase("literal and comparison", "$[?true && true == false]"),
58+
FilterLiteralTestCase("literal or comparison", "$[?false || true == false]"),
59+
]
60+
61+
62+
@pytest.mark.parametrize(
63+
"case", BAD_FILTER_LITERAL_TEST_CASES, ids=attrgetter("description")
64+
)
65+
def test_filter_literals_must_be_compared(
66+
env: JSONPathEnvironment, case: FilterLiteralTestCase
67+
) -> None:
68+
with pytest.raises(JSONPathSyntaxError):
69+
env.compile(case.query)

0 commit comments

Comments
 (0)