Skip to content

feat(events): add TimeoutKind to classify Error::Timeout by operation#55

Open
devin-ai-integration[bot] wants to merge 4 commits intomainfrom
devin/1776970193-timeout-kind
Open

feat(events): add TimeoutKind to classify Error::Timeout by operation#55
devin-ai-integration[bot] wants to merge 4 commits intomainfrom
devin/1776970193-timeout-kind

Conversation

@devin-ai-integration
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot commented Apr 23, 2026

Summary

Error::Timeout was a unit variant that collapsed every *_and_wait deadline miss into one shape, so 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, 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, returns Some(kind) only for Error::Timeout.
  • Error::Timeout { kind: TimeoutKind } — struct variant (was unit). thiserror Display now formats as Operation timed out: <kind>.

Internal call-site mapping:

  • client/core/runtime.rs::poll_command_completionTimeoutKind::Command (propagates to wait_for_command, list_bans_and_wait, list_user_accounts_and_wait).
  • client/users/auth.rs::login_and_waitTimeoutKind::Login.
  • client/channels.rs::join_channel_and_waitTimeoutKind::Join.
  • client/server.rs::update_server_and_wait, save_server_config_and_waitTimeoutKind::Command (generic command completions; ServerConfig is available for future differentiation).
  • client/users/directory.rs::create_user_account_and_wait, delete_user_account_and_waitTimeoutKind::Command.
  • client/files.rs::wait_for_file_transfer / wait_for_file_transfer_terminalTimeoutKind::Transfer.

Breaking change note:

match err { Error::Timeout => .. } no longer matches as a unit variant; it is now

match err { Error::Timeout { kind } => .. }
// or
if let Some(kind) = err.timeout_kind() { .. }

Error is #[non_exhaustive], so downstream match arms already require a catch-all; the only code 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.

Tests: crates/teamtalk/tests/timeout_kind_tests.rs adds 9 integration cases — constructor, accessor on all kinds + all non-Timeout variants, Display contains the kind name and the base phrase, Display distinguishes kinds, name-is-snake-case-stable, Display matches name, names are unique, derived traits (Copy, Clone, Debug, Eq, Hash) are usable.

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.

Review & Testing Checklist for Human

  • Confirm the mapping of internal call sites to TimeoutKind values is what you want — in particular the update_server_and_wait / save_server_config_and_wait pair currently maps to TimeoutKind::Command (they are generic command waits); the ServerConfig kind is available if you'd prefer a dedicated classification.
  • Confirm the breaking change on Error::Timeout (unit → struct variant) is acceptable. No in-repo code matched the unit shape, so the migration is trivial outside this crate.
  • Confirm the TimeoutKind::Other escape hatch — added to keep future call sites from getting stuck on PR review over naming; #[non_exhaustive] means new named kinds can replace Other later without another breaking change.

Notes

Fourth P1 API improvement after ExponentialBackoff jitter (#51), StreamTypes newtype (#52), and SdkErrorCode typed mapping (#54). Branches from clean main, independent of the other open PRs, so can be merged in any order.

Next up: SecretString + zeroize for passwords in LoginParams, indexed EventBus::dispatch / Router::dispatch via HashMap, then a coverage-gap test fill-in driven by scripts/audit_teamtalk_coverage.py.

The pre-existing semver CI gate is expected to remain red; release-plz handles the eventual version bump.

Link to Devin session: https://app.devin.ai/sessions/71fdd6196cb74723a2e277bb81993a9c
Requested by: @BlindMaster24


Open in Devin Review

@devin-ai-integration
Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

devin-ai-integration[bot]

This comment was marked as resolved.

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.
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.
@devin-ai-integration devin-ai-integration Bot force-pushed the devin/1776970193-timeout-kind branch from 893e0b6 to 145012a Compare April 24, 2026 09:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant