Skip to content

Commit

Permalink
Handle some JSONPath sytnax errors
Browse files Browse the repository at this point in the history
  • Loading branch information
jg-rp committed May 17, 2024
1 parent cec7a25 commit 5b61940
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 1 deletion.
32 changes: 31 additions & 1 deletion jsonpath/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
from .filter import InfixExpression
from .filter import IntegerLiteral
from .filter import ListLiteral
from .filter import Literal
from .filter import Nil
from .filter import Path
from .filter import PrefixExpression
from .filter import RegexLiteral
Expand Down Expand Up @@ -191,6 +193,7 @@ class Parser:
"<=",
"<",
"!=",
"=~",
]
)

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

if isinstance(expr, (Literal, Nil)):
raise JSONPathSyntaxError(
"filter expression literals outside of "
"function expressions must be compared",
token=tok,
)

return Filter(env=self.env, token=tok, expression=BooleanExpression(expr))

def parse_boolean(self, stream: TokenStream) -> FilterExpression:
Expand Down Expand Up @@ -520,6 +530,20 @@ def parse_infix_expression(
self._raise_for_non_comparable_function(left, tok)
self._raise_for_non_comparable_function(right, tok)

if operator not in self.COMPARISON_OPERATORS:
if isinstance(left, Literal):
raise JSONPathSyntaxError(
"filter expression literals outside of "
"function expressions must be compared",
token=tok,
)
if isinstance(right, Literal):
raise JSONPathSyntaxError(
"filter expression literals outside of "
"function expressions must be compared",
token=tok,
)

return InfixExpression(left, operator, right)

def parse_grouped_expression(self, stream: TokenStream) -> FilterExpression:
Expand All @@ -532,14 +556,20 @@ def parse_grouped_expression(self, stream: TokenStream) -> FilterExpression:
raise JSONPathSyntaxError(
"unbalanced parentheses", token=stream.current
)

if stream.current.kind not in self.BINARY_OPERATORS:
raise JSONPathSyntaxError(
f"expected an expression, found '{stream.current.value}'",
token=stream.current,
)

expr = self.parse_infix_expression(stream, expr)

stream.expect(TOKEN_RPAREN)
return expr

def parse_root_path(self, stream: TokenStream) -> FilterExpression:
root = stream.next_token()
assert root.kind in {TOKEN_ROOT, TOKEN_FAKE_ROOT} # XXX:
return RootPath(
JSONPath(
env=self.env,
Expand Down
40 changes: 40 additions & 0 deletions tests/test_errors.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from operator import attrgetter
from typing import List
from typing import NamedTuple

import pytest

from jsonpath import JSONPathEnvironment
Expand Down Expand Up @@ -28,3 +32,39 @@ def test_function_too_many_params(env: JSONPathEnvironment) -> None:
def test_non_singular_query_is_not_comparable(env: JSONPathEnvironment) -> None:
with pytest.raises(JSONPathTypeError):
env.compile("$[?@.* > 2]")


def test_unbalanced_parens(env: JSONPathEnvironment) -> None:
with pytest.raises(JSONPathSyntaxError):
env.compile("$[?((@.foo)]")


class FilterLiteralTestCase(NamedTuple):
description: str
query: str


# TODO: add these to the CTS?
BAD_FILTER_LITERAL_TEST_CASES: List[FilterLiteralTestCase] = [
FilterLiteralTestCase("just true", "$[?true]"),
FilterLiteralTestCase("just string", "$[?'foo']"),
FilterLiteralTestCase("just int", "$[?2]"),
FilterLiteralTestCase("just float", "$[?2.2]"),
FilterLiteralTestCase("just null", "$[?null]"),
FilterLiteralTestCase("literal and literal", "$[?true && false]"),
FilterLiteralTestCase("literal or literal", "$[?true || false]"),
FilterLiteralTestCase("comparison and literal", "$[?true == false && false]"),
FilterLiteralTestCase("comparison or literal", "$[?true == false || false]"),
FilterLiteralTestCase("literal and comparison", "$[?true && true == false]"),
FilterLiteralTestCase("literal or comparison", "$[?false || true == false]"),
]


@pytest.mark.parametrize(
"case", BAD_FILTER_LITERAL_TEST_CASES, ids=attrgetter("description")
)
def test_filter_literals_must_be_compared(
env: JSONPathEnvironment, case: FilterLiteralTestCase
) -> None:
with pytest.raises(JSONPathSyntaxError):
env.compile(case.query)

0 comments on commit 5b61940

Please sign in to comment.