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
Allow rewriter to gracefully bail out on content handler errors.
This is the symmetric companion to the previous `MemoryLimitExceeded` bail-out. Until now,
a content handler returning `Err` would abort the rewriter and leave the output sink in a
potentially inconsistent state (the sink has whatever transformed bytes the rewriter had
already produced, but the remaining input bytes are lost). Downstream this typically
manifests as a truncated, broken HTTP response.
This change adds an opt-in `Settings::graceful_bail_out_on_content_handler_error` flag.
When set, before propagating `RewritingError::ContentHandlerError` the rewriter flushes
every input byte it has received but not yet emitted to the sink, as-is. The caller can
then continue the response by writing subsequent bytes directly to its own downstream
sink, bypassing the (now poisoned) rewriter.
The flag lives on `Settings` rather than `MemorySettings` because the underlying error has
nothing to do with memory: a handler returning `Err` is an explicit signal from the
application, not an environmental constraint. The two flags are intentionally independent
so callers can pick the recovery posture per error class.
The implementation requires a small but important refactor of the dispatcher. Previously,
`lexeme_consumed()` did two things: emit the chunk of input before the lexeme, and advance
`remaining_content_start` past the lexeme. The second step happened before the lexeme's
token was actually serialized to the sink, so a handler error from `token_produced()` (or
text emission) left `remaining_content_start` past a lexeme whose bytes had never been
emitted. Flushing from there as-is would lose the lexeme.
We split that into:
1. `emit_chunk_before_lexeme()`: emit the raw input chunk between the previously emitted
byte and the start of the lexeme, advance `remaining_content_start` to the lexeme's
start.
2. `consume_lexeme()`: advance `remaining_content_start` past the lexeme's end. Called
only after the lexeme's token has been successfully emitted.
With this split, on a `ContentHandlerError` mid-lexeme `remaining_content_start` still
points at the failing lexeme's start, and a graceful bail-out can flush the lexeme + the
rest of the chunk raw.
The bail-out decision in `TransformStream::write()` and `end()` is centralized in
`should_bail_out_for()`, which considers each `RewritingError` variant against its own
flag. `ParsingAmbiguity` is never recovered from (the whole point of strict mode is to
refuse uncertain markup).
The C API is left on the original behavior for now. The previous memory flag fit cleanly
into `MemorySettings` (a `#[repr(C)]` struct), so it was an additive change to the C ABI.
The new flag lives on `Settings`, which is not `repr(C)`, so exposing it through the C
API needs either a new function signature or a new settings struct — a maintainer call.
A TODO captures this in `c-api/src/rewriter.rs`, and the CHANGELOG notes it.
Tested with 6 new tests in `rewriter::tests::fatal_errors`:
1. `test_graceful_bail_out_on_element_handler_error`: element handler returns `Err`, sink
ends up with the transformed prefix + the failing element and everything after it raw.
2. `test_no_graceful_bail_out_on_content_handler_error_by_default`: sanity check that the
flag is opt-in.
3. `test_graceful_bail_out_on_comment_handler_error`: comment handler error path
(exercises the same `lexeme_consumed` restructure).
4. `test_graceful_bail_out_on_end_handler_error`: `handle_end()` returns `Err` after
`flush_remaining_input` has already emitted every input byte; sink already has the
complete document.
5. `test_bail_out_reconstruct_handler_error_midstream`: end-to-end reconstruction
(`sink_output == original_html`).
6. `test_bail_out_flags_independent`: enabling content-handler bail-out doesn't
accidentally enable memory bail-out, and vice versa.
Caveats documented on the new field's doc comment:
1. Active content removal at the time of the bail-out can produce mismatched tags. Same
caveat as the memory flag; uncommon in practice and not byte-truncating.
2. Multi-chunk text emission where a later chunk's handler errors will re-emit the raw
input bytes, duplicating the already-emitted chunks. Rare (only on encoding-converted
text); the response is byte-bigger but not truncated.
0 commit comments