From 7bc83121e337b72336a44bb8c6b817ea48787dac Mon Sep 17 00:00:00 2001 From: iammuhammadfurqan Date: Sun, 28 Jun 2026 02:00:12 +0500 Subject: [PATCH 1/2] fix: raise FilterError on unknown metadata filter operators Filtering with an unsupported operator (e.g. a typo like "gt" instead of ">", or "XOR" as a logical operator) raised a bare KeyError with no context. Since filters.py is the shared filter engine used by InMemoryDocumentStore and every external document store, this affected all stores. Both _comparison_condition and _logic_condition now validate the operator and raise a FilterError that lists the valid operators, consistent with how other malformed-filter cases are already handled. --- haystack/utils/filters.py | 6 ++++++ .../filter-unknown-operator-error-8df4039c0db976ff.yaml | 8 ++++++++ test/utils/test_filters.py | 6 ++++++ 3 files changed, 20 insertions(+) create mode 100644 releasenotes/notes/filter-unknown-operator-error-8df4039c0db976ff.yaml diff --git a/haystack/utils/filters.py b/haystack/utils/filters.py index 5d89e9eefab..7443bcc14c9 100644 --- a/haystack/utils/filters.py +++ b/haystack/utils/filters.py @@ -173,6 +173,9 @@ def _logic_condition(condition: dict[str, Any], document: Document | ByteStream) msg = f"'conditions' key missing in {condition}" raise FilterError(msg) operator: str = condition["operator"] + if operator not in LOGICAL_OPERATORS: + msg = f"Unknown logical operator '{operator}'. Valid operators are: {sorted(LOGICAL_OPERATORS)}" + raise FilterError(msg) conditions: list[dict[str, Any]] = condition["conditions"] return LOGICAL_OPERATORS[operator](document=document, conditions=conditions) @@ -214,5 +217,8 @@ def _comparison_condition(condition: dict[str, Any], document: Document | ByteSt else: document_value = getattr(document, field) operator: str = condition["operator"] + if operator not in COMPARISON_OPERATORS: + msg = f"Unknown comparison operator '{operator}'. Valid operators are: {sorted(COMPARISON_OPERATORS)}" + raise FilterError(msg) filter_value: Any = condition["value"] return COMPARISON_OPERATORS[operator](filter_value=filter_value, value=document_value) diff --git a/releasenotes/notes/filter-unknown-operator-error-8df4039c0db976ff.yaml b/releasenotes/notes/filter-unknown-operator-error-8df4039c0db976ff.yaml new file mode 100644 index 00000000000..d3c566bf042 --- /dev/null +++ b/releasenotes/notes/filter-unknown-operator-error-8df4039c0db976ff.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + Metadata filters that use an unsupported operator now raise a clear ``FilterError`` + that lists the valid operators, instead of a cryptic ``KeyError``. For example, a + typo such as ``{"field": "meta.year", "operator": "gt", "value": 2000}`` (instead of + ``">"``) previously surfaced as ``KeyError: 'gt'``. This applies to both comparison + operators and logical operators (``AND``, ``OR``, ``NOT``). diff --git a/test/utils/test_filters.py b/test/utils/test_filters.py index f5fbf66d6a8..3fff1a9dbc9 100644 --- a/test/utils/test_filters.py +++ b/test/utils/test_filters.py @@ -566,6 +566,12 @@ def test_document_matches_filter(filters, document, expected_result): pytest.param({"operator": "==", "value": "test"}, id="Missing condition field key"), pytest.param({"field": "meta.name", "value": "test"}, id="Missing condition operator key"), pytest.param({"field": "meta.name", "operator": "=="}, id="Missing condition value key"), + # Unknown operators + pytest.param({"field": "meta.page", "operator": "gt", "value": 10}, id="Unknown comparison operator"), + pytest.param( + {"operator": "XOR", "conditions": [{"field": "meta.page", "operator": "==", "value": 10}]}, + id="Unknown logical operator", + ), ] From 537b5e6d63b851c0b8523bf74bc31630abc4329c Mon Sep 17 00:00:00 2001 From: "David S. Batista" Date: Mon, 29 Jun 2026 11:05:43 +0200 Subject: [PATCH 2/2] expanding tests --- test/utils/test_filters.py | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/test/utils/test_filters.py b/test/utils/test_filters.py index 3fff1a9dbc9..777105a01dd 100644 --- a/test/utils/test_filters.py +++ b/test/utils/test_filters.py @@ -572,6 +572,17 @@ def test_document_matches_filter(filters, document, expected_result): {"operator": "XOR", "conditions": [{"field": "meta.page", "operator": "==", "value": 10}]}, id="Unknown logical operator", ), + pytest.param( + { + "operator": "AND", + "conditions": [{"operator": "XOR", "conditions": [{"field": "meta.page", "operator": "==", "value": 10}]}], + }, + id="Unknown nested logical operator", + ), + pytest.param( + {"operator": "and", "conditions": [{"field": "meta.page", "operator": "==", "value": 10}]}, + id="Lowercase logical operator", + ), ] @@ -580,3 +591,38 @@ def test_document_matches_filter_raises_error(filters): with pytest.raises(FilterError): document = Document(meta={"page": 10}) document_matches_filter(filters, document) + + +@pytest.mark.parametrize( + "filters,expected_message", + [ + pytest.param( + {"field": "meta.page", "operator": "gt", "value": 10}, + "Unknown comparison operator 'gt'", + id="Unknown comparison operator", + ), + pytest.param( + {"operator": "XOR", "conditions": [{"field": "meta.page", "operator": "==", "value": 10}]}, + "Unknown logical operator 'XOR'", + id="Unknown logical operator", + ), + pytest.param( + { + "operator": "AND", + "conditions": [ + {"operator": "XOR", "conditions": [{"field": "meta.page", "operator": "==", "value": 10}]} + ], + }, + "Unknown logical operator 'XOR'", + id="Unknown nested logical operator", + ), + pytest.param( + {"operator": "and", "conditions": [{"field": "meta.page", "operator": "==", "value": 10}]}, + "Unknown logical operator 'and'", + id="Lowercase logical operator", + ), + ], +) +def test_document_matches_filter_unknown_operator_error_message(filters, expected_message): + with pytest.raises(FilterError, match=expected_message): + document_matches_filter(filters, Document(meta={"page": 10}))