fix: surface TWS errors on subscription channels (closes #434)#490
Merged
Conversation
Audit StreamDecoders for IncomingMessages::Error handling. When TWS
returns an error tied to a subscription's request_id, decoders that
didn't match on the message type either reproduced the original
"invalid digit found in string" parse failure (PnL, PnLSingle,
WshEventData, ScannerData), terminated with Error::Simple
("unexpected message: Error") losing the IB code/text (option
computations, account summary/position/update variants), or silently
skipped via UnexpectedResponse (option chain, news, wsh metadata,
display groups). 15 decoders now return Err(Error::Message(code, msg))
for type-4 messages.
Also: regression test for realtime_bars Error handling (sync + async)
locks down the prior fix (e1333df). Sync Subscription log line
distinguishes TWS errors (warn!) from real decode failures (error!).
Replace 15 copies of the Error::Message match-and-panic pattern with a shared helper in common::test_utils. Tightens the new decoder tests without changing semantics.
wboayue
added a commit
that referenced
this pull request
May 3, 2026
- test-builders.md: PRs #495-#501 merged. - issue-441-order-status-option-doubles.md: issue closed; OrderStatus fields are now Option<f64>. - issue-434-stream-decoder-error-handling.md: issue closed; main fixed via #490 (v2-stable mirror is a separate concern if it surfaces). - improve-encoder-test-coverage.md: approach superseded by the test-builders pattern (assert_request<B>(builder) at the integration layer in place of field-level encoder unit tests). Merged commits preserve the history.
10 tasks
wboayue
added a commit
that referenced
this pull request
May 12, 2026
#490) (#567) * fix: surface TWS errors on subscription channels (v2-stable backport of #490) Backports the issue #434 fix from main (#490) to v2-stable. 15 StreamDecoders now match on IncomingMessages::Error and return Err(Error::Message(code, msg)) instead of producing a parse error, dropping the IB error code via Error::Simple("unexpected message: ..."), or silently skipping via UnexpectedResponse. Affects: OptionComputation, OptionChain, AccountSummaryResult, PnL, PnLSingle, PositionUpdate, PositionUpdateMulti, AccountUpdate, AccountUpdateMulti, Vec<ScannerData>, NewsBulletin, NewsArticle, WshMetadata, WshEventData, DisplayGroupUpdate. Adds regression test for realtime_bars (sync + async) locking down the prior decoder fix, plus per-decoder unit tests verifying a wire error decodes to Error::Message(10089, _). Sync Subscription log line in subscriptions/sync.rs now branches on Error::Message: TWS-side errors log warn! "subscription terminated by TWS error [code] msg"; decode failures keep error!. Layout differences vs main: v2-stable predates the news/scanner/common/stream_decoders.rs split, so news/{sync,async}.rs and scanner/{sync,async}.rs are edited directly with matching cfg gates. Tests live inline. Sync news/scanner gain explicit RESPONSE_MESSAGE_IDS arrays for symmetry with async (no behavior change). * simplify: collapse decoder error tests behind a generic helper Adds `assert_decode_surfaces_tws_error<T: StreamDecoder<T>>(code, msg)` and `decoder_test_context()` to `common::test_utils::helpers`. The 15 per-decoder error tests collapse from a 5-line `from(wire) -> decode -> unwrap_err -> assert` body to a single typed call. Also drops per-task narration comments (#434 references, "previously this was called blindly", "Issue #434: when TWS returns ..."), and merges 4 sibling `mod decoder_error_tests` blocks into the host `mod tests` for news/scanner (sync + async). Branch diff: 414 / 40 → 308 / 40 (-106 LoC of additions).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #434. The original symptom (realtime_bars decoder treating an error as a
Bar, producingParseIntError"invalid digit found in string") was fixed in e1333df but had no regression test. This PR locks that down and audits every other StreamDecoder for the same gap.IncomingMessages::Errorand returnErr(Error::Message(code, msg))instead of producing a parse error, losing the IB error code viaError::Simple("unexpected message: …"), or silently skipping viaUnexpectedResponse. Affects:OptionComputation,OptionChain,AccountSummaryResult,PnL,PnLSingle,PositionUpdate,PositionUpdateMulti,AccountUpdate,AccountUpdateMulti,Vec<ScannerData>,NewsBulletin,NewsArticle,WshMetadata,WshEventData,DisplayGroupUpdate.subscriptions/sync.rsnow branches onError::Message: TWS-side errors logwarn!("subscription terminated by TWS error [code] msg"), genuine decode failures keeperror!("error decoding message: ...").Error::Message(10089, _). One existing test (AccountSummaryResult::test_decode_unexpected_message) updated since its old assertion no longer holds.Behavior change
Five decoders (
OptionChain,NewsBulletin,NewsArticle,WshMetadata,DisplayGroupUpdate) previously fell through toError::UnexpectedResponsefor type-4 messages, whichprocess_decode_resulttranslates toSkip— i.e. the subscription kept running silently after a TWS error. They now terminate withError::Message(code, msg), surfacing the IB error to the caller. Warning codes (2100-2169) are filtered at the dispatcher (is_warning_error), so only real errors reach these decoders; terminating is the correct UX, but it is a visible behavior shift beyond what #434 literally requested.The Group B
accounts/contractsdecoders also see a small side effect: their wildcard_arm now returnsUnexpectedResponse(skip) instead ofError::Simple("unexpected message: …")(terminate). Unrelated message types routed to these channels — if any — will now be skipped instead of killing the subscription, matching the rest of the codebase.Test plan
cargo fmt --checkcleancargo clippy --all-targets -- -D warningscleancargo clippy --all-targets --features sync -- -D warningscleancargo clippy --all-featurescleancargo test --lib(default async): 895 passcargo test --lib --features sync: 1079 passtest_realtime_bars_error_handling(sync + async) verifiesError::Message(10089, _)surfaces correctly.