You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Error reporting is binary: ParseError with a message string, or success. Real mail is messier than that — #24 (fixed) showed decode failures being silently swallowed, and the fix chose between "error" and "best effort" globally. Pipeline consumers need the middle ground: parse what you can, tell me what was lossy. Today a message with one undecodable body part either fails entirely or succeeds with silently-mangled content, and the caller can't distinguish pristine output from patched-up output — which matters enormously when the consumer is a spam classifier or forensic tool making decisions on the content.
Proposal
1. Error taxonomy (additive; ParseError stays the base)
Each carries structured context: part_path (e.g. "1.2" tree coordinates, aligning with the part-tree issue), charset/encoding where relevant — machine-usable, not just prose.
Best-effort behavior (post-Body decode errors silently swallowed via unwrap_or_default() #24 semantics) becomes observable: every lossy repair the parser performs is recorded. Empty list = pristine parse — a guarantee consumers can finally act on (route warnings != [] mail to quarantine/manual review).
parse_email(payload, strict=True): any condition that would emit a warning raises the corresponding typed error instead (validation-pipeline mode).
Acceptance criteria
Exception hierarchy exported with stubs; except ParseError catches all subtypes (compat test); each subtype raised from a dedicated malformed fixture with part_path/context populated (table-driven, extending test_decode_errors.py / test_dos_limits.py).
Warning kinds emitted for each documented lossy path: charset fallback fixture, broken quoted-printable, bad address header, bad date — each yields exactly the expected (kind, part_path) and correct best-effort content alongside (both asserted).
Clean corpus regression: every currently-passing fixture parses with warnings == [] — no warning inflation on good mail (full-corpus assertion; any fixture that legitimately warns gets documented as such).
strict=True converts each warning scenario into its typed exception (parametrized over the same fixtures); default remains best-effort.
parse_many propagates per-item typed errors and per-mail warnings (integration with the batch issue if merged; sequencing noted).
Warning collection overhead ≤ 2% on the clean-corpus benchmark (numbers in PR; check_benchmark.py still green).
README error-handling section rewritten around the taxonomy + warnings contract; stubs/mypy --strict/ruff/clippy/fmt clean; CHANGELOG entry.
Out of scope
Python warnings module integration (list attribute is explicit and pipeline-friendly; stdlib warnings are process-global and wrong for per-message data).
Auto-repair beyond what the parser already does (observability of repairs, not new repairs).
Part of the roadmap (see tracking issue). Builds on #24's resolution; part_path vocabulary shared with the part-tree issue.
Problem
Error reporting is binary:
ParseErrorwith a message string, or success. Real mail is messier than that — #24 (fixed) showed decode failures being silently swallowed, and the fix chose between "error" and "best effort" globally. Pipeline consumers need the middle ground: parse what you can, tell me what was lossy. Today a message with one undecodable body part either fails entirely or succeeds with silently-mangled content, and the caller can't distinguish pristine output from patched-up output — which matters enormously when the consumer is a spam classifier or forensic tool making decisions on the content.Proposal
1. Error taxonomy (additive;
ParseErrorstays the base)part_path(e.g."1.2"tree coordinates, aligning with the part-tree issue),charset/encodingwhere relevant — machine-usable, not just prose.2. Parse warnings — the lossy-success channel
warnings != []mail to quarantine/manual review).parse_email(payload, strict=True): any condition that would emit a warning raises the corresponding typed error instead (validation-pipeline mode).Acceptance criteria
except ParseErrorcatches all subtypes (compat test); each subtype raised from a dedicated malformed fixture withpart_path/context populated (table-driven, extendingtest_decode_errors.py/test_dos_limits.py).warnings == []— no warning inflation on good mail (full-corpus assertion; any fixture that legitimately warns gets documented as such).strict=Trueconverts each warning scenario into its typed exception (parametrized over the same fixtures); default remains best-effort.parse_manypropagates per-item typed errors and per-mail warnings (integration with the batch issue if merged; sequencing noted).check_benchmark.pystill green).Out of scope
warningsmodule integration (list attribute is explicit and pipeline-friendly; stdlib warnings are process-global and wrong for per-message data).Part of the roadmap (see tracking issue). Builds on #24's resolution; part_path vocabulary shared with the part-tree issue.