• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wboayue / rust-ibapi / 16251934242

13 Jul 2025 06:01PM UTC coverage: 73.375% (-3.0%) from 76.394%
16251934242

push

github

wboayue
fix: create target/coverage directory before writing merged report

The lcov tool was failing because the output directory didn't exist.
Added mkdir -p to ensure the directory is created before attempting
to write the merged coverage report.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

4809 of 6554 relevant lines covered (73.38%)

34.57 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

43.75
/src/client/error_handler.rs
1
//! Consolidated error handling utilities for the client
2
//!
3
//! This module provides common error handling functions used throughout
4
//! the client implementation.
5

6
// TODO: Remove this when async reconnection/retry logic is implemented (see transport/async.rs:175)
7
// Currently only is_connection_error and is_timeout_error are used by sync transport
8
#![allow(dead_code)]
9

10
use std::io::ErrorKind;
11

12
use crate::errors::Error;
13

14
/// Maximum number of retries for transient errors
15
pub(crate) const MAX_RETRIES: u32 = 3;
16

17
/// Checks if the error is a connection-related IO error that should trigger reconnection
18
pub(crate) fn is_connection_error(error: &Error) -> bool {
18✔
19
    match error {
18✔
20
        Error::Io(io_err) => matches!(
6✔
21
            io_err.kind(),
×
22
            ErrorKind::ConnectionReset | ErrorKind::ConnectionAborted | ErrorKind::UnexpectedEof
23
        ),
24
        Error::ConnectionReset | Error::ConnectionFailed => true,
8✔
25
        _ => false,
4✔
26
    }
27
}
28

29
/// Checks if the error is a timeout that can be safely ignored
30
pub(crate) fn is_timeout_error(error: &Error) -> bool {
63✔
31
    match error {
63✔
32
        Error::Io(io_err) => matches!(io_err.kind(), ErrorKind::WouldBlock | ErrorKind::TimedOut),
69✔
33
        _ => false,
×
34
    }
35
}
36

37
/// Checks if an error should trigger a retry
38
pub(crate) fn should_retry_request(error: &Error, retry_count: u32) -> bool {
8✔
39
    retry_count < MAX_RETRIES && (is_connection_error(error) || is_transient_error(error))
22✔
40
}
41

42
/// Checks if an error is transient and may succeed on retry
43
pub(crate) fn is_transient_error(error: &Error) -> bool {
2✔
44
    match error {
2✔
45
        Error::UnexpectedResponse(_) => true,
×
46
        Error::Io(io_err) => matches!(io_err.kind(), ErrorKind::Interrupted | ErrorKind::WouldBlock | ErrorKind::TimedOut),
×
47
        _ => false,
2✔
48
    }
49
}
50

51
/// Checks if an error is fatal and should not be retried
52
pub(crate) fn is_fatal_error(error: &Error) -> bool {
8✔
53
    matches!(
2✔
54
        error,
8✔
55
        Error::Shutdown | Error::InvalidArgument(_) | Error::NotImplemented | Error::ServerVersion(_, _, _) | Error::AlreadySubscribed
56
    )
57
}
58

59
/// Converts an error to a user-friendly message
60
pub(crate) fn error_message(error: &Error) -> String {
×
61
    match error {
×
62
        Error::ConnectionFailed => "Connection to TWS/Gateway failed".to_string(),
×
63
        Error::ConnectionReset => "Connection was reset by TWS/Gateway".to_string(),
×
64
        Error::Shutdown => "Client is shutting down".to_string(),
×
65
        Error::Cancelled => "Operation was cancelled".to_string(),
×
66
        Error::EndOfStream => "No more data available".to_string(),
×
67
        Error::ServerVersion(required, actual, feature) => {
×
68
            format!("Server version {required} required for {feature}, but connected to version {actual}")
×
69
        }
70
        Error::Message(code, msg) => format!("TWS Error [{code}]: {msg}"),
×
71
        _ => error.to_string(),
×
72
    }
73
}
74

75
/// Error categories for logging and metrics
76
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77
pub(crate) enum ErrorCategory {
78
    Connection,
79
    Parsing,
80
    Validation,
81
    ServerError,
82
    Timeout,
83
    Cancelled,
84
    Fatal,
85
    Transient,
86
}
87

88
/// Categorizes an error for logging and metrics purposes
89
pub(crate) fn categorize_error(error: &Error) -> ErrorCategory {
8✔
90
    match error {
×
91
        Error::ConnectionFailed | Error::ConnectionReset => ErrorCategory::Connection,
2✔
92
        Error::Io(io_err) if is_connection_io_error(io_err) => ErrorCategory::Connection,
×
93
        Error::Io(io_err) if is_timeout_io_error(io_err) => ErrorCategory::Timeout,
×
94
        Error::Parse(_, _, _) | Error::ParseInt(_) | Error::FromUtf8(_) | Error::ParseTime(_) => ErrorCategory::Parsing,
2✔
95
        Error::InvalidArgument(_) | Error::ServerVersion(_, _, _) => ErrorCategory::Validation,
×
96
        Error::Message(_, _) => ErrorCategory::ServerError,
2✔
97
        Error::Cancelled => ErrorCategory::Cancelled,
2✔
98
        Error::Shutdown | Error::NotImplemented | Error::AlreadySubscribed => ErrorCategory::Fatal,
×
99
        Error::UnexpectedResponse(_) | Error::UnexpectedEndOfStream => ErrorCategory::Transient,
×
100
        _ => ErrorCategory::Transient,
×
101
    }
102
}
103

104
fn is_connection_io_error(io_err: &std::io::Error) -> bool {
×
105
    matches!(
×
106
        io_err.kind(),
×
107
        ErrorKind::ConnectionReset | ErrorKind::ConnectionAborted | ErrorKind::UnexpectedEof | ErrorKind::BrokenPipe | ErrorKind::ConnectionRefused
108
    )
109
}
110

111
fn is_timeout_io_error(io_err: &std::io::Error) -> bool {
×
112
    matches!(io_err.kind(), ErrorKind::WouldBlock | ErrorKind::TimedOut)
×
113
}
114

115
#[cfg(test)]
116
mod tests {
117
    use super::*;
118
    use std::io;
119

120
    #[test]
121
    fn test_is_connection_error() {
122
        let io_err = Error::Io(std::sync::Arc::new(io::Error::new(ErrorKind::ConnectionReset, "reset")));
123
        assert!(is_connection_error(&io_err));
124

125
        assert!(is_connection_error(&Error::ConnectionReset));
126
        assert!(is_connection_error(&Error::ConnectionFailed));
127
        assert!(!is_connection_error(&Error::Cancelled));
128
    }
129

130
    #[test]
131
    fn test_is_timeout_error() {
132
        let timeout_err = Error::Io(std::sync::Arc::new(io::Error::new(ErrorKind::WouldBlock, "would block")));
133
        assert!(is_timeout_error(&timeout_err));
134

135
        let non_timeout = Error::Io(std::sync::Arc::new(io::Error::other("other")));
136
        assert!(!is_timeout_error(&non_timeout));
137
    }
138

139
    #[test]
140
    fn test_should_retry_request() {
141
        let conn_err = Error::ConnectionReset;
142
        assert!(should_retry_request(&conn_err, 0));
143
        assert!(should_retry_request(&conn_err, 2));
144
        assert!(!should_retry_request(&conn_err, MAX_RETRIES));
145

146
        let fatal_err = Error::Shutdown;
147
        assert!(!should_retry_request(&fatal_err, 0));
148
    }
149

150
    #[test]
151
    fn test_is_fatal_error() {
152
        assert!(is_fatal_error(&Error::Shutdown));
153
        assert!(is_fatal_error(&Error::InvalidArgument("test".to_string())));
154
        assert!(is_fatal_error(&Error::NotImplemented));
155
        assert!(!is_fatal_error(&Error::ConnectionReset));
156
    }
157

158
    #[test]
159
    fn test_error_categorization() {
160
        assert_eq!(categorize_error(&Error::ConnectionFailed), ErrorCategory::Connection);
161
        assert_eq!(
162
            categorize_error(&Error::ParseInt("123x".parse::<i32>().unwrap_err())),
163
            ErrorCategory::Parsing
164
        );
165
        assert_eq!(categorize_error(&Error::Cancelled), ErrorCategory::Cancelled);
166
        assert_eq!(categorize_error(&Error::Message(200, "test".to_string())), ErrorCategory::ServerError);
167
    }
168
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc