diff --git a/haystack/components/routers/document_type_router.py b/haystack/components/routers/document_type_router.py index 51069373fb0..a5d576cf8db 100644 --- a/haystack/components/routers/document_type_router.py +++ b/haystack/components/routers/document_type_router.py @@ -134,9 +134,12 @@ def run(self, documents: list[Document]) -> dict[str, list[Document]]: matched = False if mime_type: - for pattern in self._mime_type_patterns: - if pattern.fullmatch(mime_type): - mime_types[pattern.pattern].append(doc) + for bucket_key, pattern in zip(self.mime_types, self._mime_type_patterns, strict=True): + # Match an exact MIME type first, so literal types containing regex + # metacharacters (e.g. the '+' in 'image/svg+xml') are not misread as + # a regex; fall back to regex matching for patterns like 'audio/.*'. + if mime_type == bucket_key or pattern.fullmatch(mime_type): + mime_types[bucket_key].append(doc) matched = True break if not matched: diff --git a/releasenotes/notes/fix-document-type-router-literal-mime-141fa900eda54ec5.yaml b/releasenotes/notes/fix-document-type-router-literal-mime-141fa900eda54ec5.yaml new file mode 100644 index 00000000000..e6c459a0fb7 --- /dev/null +++ b/releasenotes/notes/fix-document-type-router-literal-mime-141fa900eda54ec5.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + Fixed ``DocumentTypeRouter`` misrouting documents whose MIME type contains a + regex metacharacter. A declared type such as the standard IANA ``image/svg+xml`` + was compiled as a regex, so the ``+`` was treated as a quantifier and the + document fell into ``unclassified`` instead of its own bucket. Declared MIME + types are now matched by exact equality first, falling back to regex matching + so patterns like ``audio/.*`` keep working. diff --git a/test/components/routers/test_document_type_router.py b/test/components/routers/test_document_type_router.py index 61bb09fe5d7..eb6a280ae6d 100644 --- a/test/components/routers/test_document_type_router.py +++ b/test/components/routers/test_document_type_router.py @@ -107,6 +107,24 @@ def test_run_with_mime_type_meta_field(self): assert result["audio/x-wav"][0].content == "Audio content" assert result["unclassified"][0].content == "Unknown type" + def test_run_with_literal_mime_type_containing_regex_metacharacter(self): + # 'image/svg+xml' is a standard IANA type; the '+' must be treated as a + # literal, not a regex quantifier. A regex pattern like 'audio/.*' must + # still match by regex. + docs = [ + Document(content="An SVG", meta={"mime_type": "image/svg+xml"}), + Document(content="Some audio", meta={"mime_type": "audio/mpeg"}), + ] + + router = DocumentTypeRouter(mime_type_meta_field="mime_type", mime_types=["image/svg+xml", "audio/.*"]) + result = router.run(documents=docs) + + assert "unclassified" not in result + assert len(result["image/svg+xml"]) == 1 + assert result["image/svg+xml"][0].content == "An SVG" + assert len(result["audio/.*"]) == 1 + assert result["audio/.*"][0].content == "Some audio" + def test_run_with_file_path_meta_field(self): docs = [ Document(content="Example text", meta={"file_path": "example.txt"}),