Skip to content

Commit

Permalink
Handle filter literals outside comparison or function expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
jg-rp committed Mar 17, 2024
1 parent cdfce6b commit ade1134
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 9 deletions.
4 changes: 2 additions & 2 deletions jsonpath_rfc9535/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
from .exceptions import JSONPathNameError
from .exceptions import JSONPathTypeError
from .filter_expressions import ComparisonExpression
from .filter_expressions import FilterExpressionLiteral
from .filter_expressions import FilterQuery
from .filter_expressions import FunctionExtension
from .filter_expressions import JSONPathLiteral
from .filter_expressions import LogicalExpression
from .function_extensions import ExpressionType
from .function_extensions import FilterFunction
Expand Down Expand Up @@ -185,7 +185,7 @@ def check_well_typedness(
arg = args[idx]
if typ == ExpressionType.VALUE:
if not (
isinstance(arg, JSONPathLiteral)
isinstance(arg, FilterExpressionLiteral)
or (isinstance(arg, FilterQuery) and arg.query.singular_query())
or (self._function_return_type(arg) == ExpressionType.VALUE)
):
Expand Down
12 changes: 6 additions & 6 deletions jsonpath_rfc9535/filter_expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def evaluate(self, context: FilterContext) -> bool:
LITERAL_T = TypeVar("LITERAL_T")


class JSONPathLiteral(Expression, Generic[LITERAL_T]):
class FilterExpressionLiteral(Expression, Generic[LITERAL_T]):
"""Base class for filter expression literals."""

__slots__ = ("value",)
Expand All @@ -93,13 +93,13 @@ def evaluate(self, _: FilterContext) -> LITERAL_T:
return self.value


class BooleanLiteral(JSONPathLiteral[bool]):
class BooleanLiteral(FilterExpressionLiteral[bool]):
"""A Boolean `true` or `false`."""

__slots__ = ()


class StringLiteral(JSONPathLiteral[str]):
class StringLiteral(FilterExpressionLiteral[str]):
"""A string literal."""

__slots__ = ()
Expand All @@ -108,19 +108,19 @@ def __str__(self) -> str:
return json.dumps(self.value)


class IntegerLiteral(JSONPathLiteral[int]):
class IntegerLiteral(FilterExpressionLiteral[int]):
"""An integer literal."""

__slots__ = ()


class FloatLiteral(JSONPathLiteral[float]):
class FloatLiteral(FilterExpressionLiteral[float]):
"""A float literal."""

__slots__ = ()


class NullLiteral(JSONPathLiteral[None]):
class NullLiteral(FilterExpressionLiteral[None]):
"""A null literal."""

__slots__ = ()
Expand Down
22 changes: 21 additions & 1 deletion jsonpath_rfc9535/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from .filter_expressions import ComparisonExpression
from .filter_expressions import Expression
from .filter_expressions import FilterExpression
from .filter_expressions import FilterExpressionLiteral
from .filter_expressions import FilterQuery
from .filter_expressions import FloatLiteral
from .filter_expressions import FunctionExtension
Expand Down Expand Up @@ -396,6 +397,13 @@ def parse_filter(self, stream: TokenStream) -> Filter:
f"result of {expr.name}() must be compared", token=tok
)

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

return Filter(
env=self.env,
token=tok,
Expand Down Expand Up @@ -444,7 +452,19 @@ def parse_infix_expression(
self._raise_for_non_comparable_function(right, tok)
return ComparisonExpression(tok, left, operator, right)

# TODO: check for valid basic expressions
if isinstance(left, FilterExpressionLiteral):
raise JSONPathSyntaxError(
"filter expression literals outside of "
"function expressions must be compared",
token=left.token,
)
if isinstance(right, FilterExpressionLiteral):
raise JSONPathSyntaxError(
"filter expression literals outside of "
"function expressions must be compared",
token=right.token,
)

return LogicalExpression(tok, left, operator, right)

def parse_grouped_expression(self, stream: TokenStream) -> Expression:
Expand Down
32 changes: 32 additions & 0 deletions tests/test_errors.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import operator
from typing import Any
from typing import List
from typing import NamedTuple

import pytest

Expand Down Expand Up @@ -60,3 +62,33 @@ class MockEnv(JSONPathEnvironment):

with pytest.raises(JSONPathRecursionError):
env.find(query, data)


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


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 and false]"),
FilterLiteralTestCase("literal or literal", "$[?true or false]"),
FilterLiteralTestCase("comparison and literal", "$[?true == false and false]"),
FilterLiteralTestCase("comparison or literal", "$[?true == false or false]"),
FilterLiteralTestCase("literal and comparison", "$[?true and true == false]"),
FilterLiteralTestCase("literal or comparison", "$[?false or true == false]"),
]


@pytest.mark.parametrize(
"case", BAD_FILTER_LITERAL_TEST_CASES, ids=operator.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 ade1134

Please sign in to comment.