feat(events): add TimeoutKind to classify Error::Timeout by operation#55
Open
devin-ai-integration[bot] wants to merge 4 commits intomainfrom
Open
feat(events): add TimeoutKind to classify Error::Timeout by operation#55devin-ai-integration[bot] wants to merge 4 commits intomainfrom
devin-ai-integration[bot] wants to merge 4 commits intomainfrom
Conversation
3 tasks
Contributor
Author
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
devin-ai-integration Bot
added a commit
that referenced
this pull request
Apr 23, 2026
Devin Review caught two issues on PR #55: 1. TimeoutKind::ServerConfig was introduced but never observed: update_server_and_wait used TimeoutKind::Command, and save_server_config_and_wait delegated to wait_for_command which also surfaces TimeoutKind::Command. Point the two documented ServerConfig sites at the dedicated variant: - update_server_and_wait uses a purpose-built loop and now returns Error::timeout(TimeoutKind::ServerConfig) on expiry. - save_server_config_and_wait keeps delegating to wait_for_command (which keeps its own generic Command semantics for every other caller) but remaps a terminal Timeout to ServerConfig so the variant documented on TimeoutKind::ServerConfig matches observed behaviour. 2. TimeoutKind was only reachable via teamtalk::events::TimeoutKind while every other public error-associated type (ConnectionState, Error, Event, FfiError, Result) is re-exported from the crate root. Add TimeoutKind to the crate-root pub use so callers can write teamtalk::TimeoutKind alongside teamtalk::Error.
3 tasks
Error::Timeout was previously a unit variant that swallowed every
timeout into one shape. Callers could not tell apart 'login did
not complete in time' from 'file transfer stalled' without
string-matching on the Display output.
This PR introduces TimeoutKind, a #[non_exhaustive] enum with one
variant per categorised blocking operation:
* Command — generic *_and_wait command completion (wait_for_command,
list_bans_and_wait, list_user_accounts_and_wait,
create/delete_user_account_and_wait).
* Connect — TCP/UDP connect step did not reach ConnectSuccess.
* Login — login_and_wait did not observe MySelfLoggedIn.
* Join — join_channel_and_wait did not reach JoinedChannel.
* Transfer — wait_for_file_transfer / _terminal exceeded deadline.
* ServerConfig — update_server_and_wait / save_server_config_and_wait.
* Other — catch-all for call sites that are not yet
classified; prefer adding a typed variant.
Error::Timeout becomes a struct variant { kind: TimeoutKind } so
the information is always present. A short constructor
Error::timeout(kind) keeps call sites readable, and a
Error::timeout_kind(&self) -> Option<TimeoutKind> accessor
provides read-only classification for callers that do not want to
pattern-match.
thiserror Display on Error::Timeout now formats as
'Operation timed out: <kind>', so any log output that previously
read 'Operation timed out' now says e.g.
'Operation timed out: login'.
All internal construction sites updated:
* client/core/runtime.rs poll_command_completion helper
-> TimeoutKind::Command (propagates to wait_for_command,
list_bans_and_wait, list_user_accounts_and_wait).
* client/users/auth.rs::login_and_wait -> TimeoutKind::Login.
* client/channels.rs::join_channel_and_wait -> TimeoutKind::Join.
* client/server.rs::update_server_and_wait and
save_server_config_and_wait -> TimeoutKind::Command (they are
generic command completions; ServerConfig is available for
future differentiation).
* client/users/directory.rs::create/delete_user_account_and_wait
-> TimeoutKind::Command.
* client/files.rs::wait_for_file_transfer /
wait_for_file_transfer_terminal -> TimeoutKind::Transfer.
Tests: crates/teamtalk/tests/timeout_kind_tests.rs adds 9 new
integration cases covering the constructor, the accessor,
Display, kind-name stability, uniqueness of names, and the
derived traits (Copy, Clone, Debug, Eq, Hash).
Breaking change note:
match err { Error::Timeout => ... }
can no longer match as a unit variant; it is now
match err { Error::Timeout { kind } => ... }
or the new accessor:
if let Some(kind) = err.timeout_kind() { .. }
Rationale for accepting the minor break: Error is already
catch-all; the only code that is affected is code that was
specifically matching the unit Timeout variant, which is rare and
trivial to migrate. No tests or examples in this repository
relied on the unit shape.
Local verification:
cargo fmt --all --check clean.
cargo clippy --workspace --all-targets --all-features -- -D
warnings clean.
cargo test --workspace --all-features: all existing tests pass
plus 9 new TimeoutKind cases.
Devin Review caught two issues on PR #55: 1. TimeoutKind::ServerConfig was introduced but never observed: update_server_and_wait used TimeoutKind::Command, and save_server_config_and_wait delegated to wait_for_command which also surfaces TimeoutKind::Command. Point the two documented ServerConfig sites at the dedicated variant: - update_server_and_wait uses a purpose-built loop and now returns Error::timeout(TimeoutKind::ServerConfig) on expiry. - save_server_config_and_wait keeps delegating to wait_for_command (which keeps its own generic Command semantics for every other caller) but remaps a terminal Timeout to ServerConfig so the variant documented on TimeoutKind::ServerConfig matches observed behaviour. 2. TimeoutKind was only reachable via teamtalk::events::TimeoutKind while every other public error-associated type (ConnectionState, Error, Event, FfiError, Result) is re-exported from the crate root. Add TimeoutKind to the crate-root pub use so callers can write teamtalk::TimeoutKind alongside teamtalk::Error.
893e0b6 to
145012a
Compare
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
Error::Timeoutwas a unit variant that collapsed every*_and_waitdeadline miss into one shape, so callers could not tell apart "login did not complete in time" from "file transfer stalled" without string-matching on theDisplayoutput. This PR introducesTimeoutKind, a#[non_exhaustive]enum with one variant per categorised blocking operation, and threads it through every internal construction site.New API (
teamtalk::events):TimeoutKind(#[non_exhaustive]) —Command,Connect,Login,Join,Transfer,ServerConfig,Other.TimeoutKind::name/Display— short stable lower-snake-case label for logs and metrics.Error::timeout(kind)— short constructor.Error::timeout_kind(&self) -> Option<TimeoutKind>— non-consuming accessor, returnsSome(kind)only forError::Timeout.Error::Timeout { kind: TimeoutKind }— struct variant (was unit).thiserrorDisplaynow formats asOperation timed out: <kind>.Internal call-site mapping:
client/core/runtime.rs::poll_command_completion→TimeoutKind::Command(propagates towait_for_command,list_bans_and_wait,list_user_accounts_and_wait).client/users/auth.rs::login_and_wait→TimeoutKind::Login.client/channels.rs::join_channel_and_wait→TimeoutKind::Join.client/server.rs::update_server_and_wait,save_server_config_and_wait→TimeoutKind::Command(generic command completions;ServerConfigis available for future differentiation).client/users/directory.rs::create_user_account_and_wait,delete_user_account_and_wait→TimeoutKind::Command.client/files.rs::wait_for_file_transfer/wait_for_file_transfer_terminal→TimeoutKind::Transfer.Breaking change note:
match err { Error::Timeout => .. }no longer matches as a unit variant; it is nowErroris#[non_exhaustive], so downstream match arms already require a catch-all; the only code affected is code that was specifically matching the unitTimeoutvariant, which is rare and trivial to migrate. No tests or examples in this repository relied on the unit shape.Tests:
crates/teamtalk/tests/timeout_kind_tests.rsadds 9 integration cases — constructor, accessor on all kinds + all non-Timeoutvariants,Displaycontains the kind name and the base phrase,Displaydistinguishes kinds, name-is-snake-case-stable,Displaymatchesname, names are unique, derived traits (Copy,Clone,Debug,Eq,Hash) are usable.Local verification:
cargo fmt --all --checkclean;cargo clippy --workspace --all-targets --all-features -- -D warningsclean;cargo test --workspace --all-features— all existing tests pass plus 9 newTimeoutKindcases.Review & Testing Checklist for Human
TimeoutKindvalues is what you want — in particular theupdate_server_and_wait/save_server_config_and_waitpair currently maps toTimeoutKind::Command(they are generic command waits); theServerConfigkind is available if you'd prefer a dedicated classification.Error::Timeout(unit → struct variant) is acceptable. No in-repo code matched the unit shape, so the migration is trivial outside this crate.TimeoutKind::Otherescape hatch — added to keep future call sites from getting stuck on PR review over naming;#[non_exhaustive]means new named kinds can replaceOtherlater without another breaking change.Notes
Fourth P1 API improvement after
ExponentialBackoffjitter (#51),StreamTypesnewtype (#52), andSdkErrorCodetyped mapping (#54). Branches from cleanmain, independent of the other open PRs, so can be merged in any order.Next up:
SecretString+zeroizefor passwords inLoginParams, indexedEventBus::dispatch/Router::dispatchviaHashMap, then a coverage-gap test fill-in driven byscripts/audit_teamtalk_coverage.py.The pre-existing
semverCI gate is expected to remain red;release-plzhandles the eventual version bump.Link to Devin session: https://app.devin.ai/sessions/71fdd6196cb74723a2e277bb81993a9c
Requested by: @BlindMaster24