turbomcp_protocol/error.rs
1//! Error handling types for MCP protocol.
2//!
3//! v3.0: The primary error type is now `McpError` from `turbomcp-core`.
4//! This module only contains supplementary types like `RetryInfo`.
5//!
6//! For error handling, use:
7//! - `turbomcp_protocol::McpError` - The unified error type
8//! - `turbomcp_protocol::ErrorKind` - Error classification
9//! - `turbomcp_protocol::McpResult<T>` - Result alias
10
11use serde::{Deserialize, Serialize};
12
13/// Information about retry attempts
14///
15/// This type is used to track retry state for operations that may need
16/// multiple attempts. It includes the number of attempts made, the
17/// maximum allowed, and an optional delay before the next retry.
18///
19/// # Example
20///
21/// ```rust
22/// use turbomcp_protocol::error::RetryInfo;
23///
24/// let retry_info = RetryInfo {
25/// attempts: 2,
26/// max_attempts: 5,
27/// retry_after_ms: Some(1000),
28/// };
29///
30/// assert!(!retry_info.exhausted());
31/// ```
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
33pub struct RetryInfo {
34 /// Number of attempts made so far
35 pub attempts: u32,
36
37 /// Maximum attempts allowed before giving up
38 pub max_attempts: u32,
39
40 /// Suggested delay in milliseconds before the next retry attempt
41 #[serde(skip_serializing_if = "Option::is_none")]
42 pub retry_after_ms: Option<u64>,
43}
44
45impl RetryInfo {
46 /// Create new retry info with default values
47 ///
48 /// # Arguments
49 ///
50 /// * `max_attempts` - Maximum number of retry attempts allowed
51 ///
52 /// # Example
53 ///
54 /// ```rust
55 /// use turbomcp_protocol::error::RetryInfo;
56 ///
57 /// let retry_info = RetryInfo::new(3);
58 /// assert_eq!(retry_info.attempts, 0);
59 /// assert_eq!(retry_info.max_attempts, 3);
60 /// ```
61 #[must_use]
62 pub const fn new(max_attempts: u32) -> Self {
63 Self {
64 attempts: 0,
65 max_attempts,
66 retry_after_ms: None,
67 }
68 }
69
70 /// Create retry info with a specific delay
71 ///
72 /// # Arguments
73 ///
74 /// * `max_attempts` - Maximum number of retry attempts allowed
75 /// * `retry_after_ms` - Delay in milliseconds before next retry
76 ///
77 /// # Example
78 ///
79 /// ```rust
80 /// use turbomcp_protocol::error::RetryInfo;
81 ///
82 /// let retry_info = RetryInfo::with_delay(3, 1000);
83 /// assert_eq!(retry_info.retry_after_ms, Some(1000));
84 /// ```
85 #[must_use]
86 pub const fn with_delay(max_attempts: u32, retry_after_ms: u64) -> Self {
87 Self {
88 attempts: 0,
89 max_attempts,
90 retry_after_ms: Some(retry_after_ms),
91 }
92 }
93
94 /// Check if retry attempts are exhausted
95 ///
96 /// # Example
97 ///
98 /// ```rust
99 /// use turbomcp_protocol::error::RetryInfo;
100 ///
101 /// let mut retry_info = RetryInfo::new(2);
102 /// assert!(!retry_info.exhausted());
103 ///
104 /// retry_info.attempts = 2;
105 /// assert!(retry_info.exhausted());
106 /// ```
107 #[must_use]
108 pub const fn exhausted(&self) -> bool {
109 self.attempts >= self.max_attempts
110 }
111
112 /// Increment the attempt counter
113 ///
114 /// # Example
115 ///
116 /// ```rust
117 /// use turbomcp_protocol::error::RetryInfo;
118 ///
119 /// let mut retry_info = RetryInfo::new(3);
120 /// retry_info.increment();
121 /// assert_eq!(retry_info.attempts, 1);
122 /// ```
123 pub fn increment(&mut self) {
124 self.attempts = self.attempts.saturating_add(1);
125 }
126
127 /// Get remaining attempts
128 ///
129 /// # Example
130 ///
131 /// ```rust
132 /// use turbomcp_protocol::error::RetryInfo;
133 ///
134 /// let mut retry_info = RetryInfo::new(5);
135 /// retry_info.attempts = 2;
136 /// assert_eq!(retry_info.remaining(), 3);
137 /// ```
138 #[must_use]
139 pub const fn remaining(&self) -> u32 {
140 self.max_attempts.saturating_sub(self.attempts)
141 }
142}
143
144impl Default for RetryInfo {
145 fn default() -> Self {
146 Self::new(3) // Default to 3 retry attempts
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn test_retry_info_creation() {
156 let retry_info = RetryInfo::new(5);
157 assert_eq!(retry_info.attempts, 0);
158 assert_eq!(retry_info.max_attempts, 5);
159 assert_eq!(retry_info.retry_after_ms, None);
160 }
161
162 #[test]
163 fn test_retry_info_with_delay() {
164 let retry_info = RetryInfo::with_delay(3, 1000);
165 assert_eq!(retry_info.attempts, 0);
166 assert_eq!(retry_info.max_attempts, 3);
167 assert_eq!(retry_info.retry_after_ms, Some(1000));
168 }
169
170 #[test]
171 fn test_retry_exhausted() {
172 let mut retry_info = RetryInfo::new(2);
173 assert!(!retry_info.exhausted());
174
175 retry_info.attempts = 1;
176 assert!(!retry_info.exhausted());
177
178 retry_info.attempts = 2;
179 assert!(retry_info.exhausted());
180
181 retry_info.attempts = 3;
182 assert!(retry_info.exhausted());
183 }
184
185 #[test]
186 fn test_retry_increment() {
187 let mut retry_info = RetryInfo::new(5);
188 assert_eq!(retry_info.attempts, 0);
189
190 retry_info.increment();
191 assert_eq!(retry_info.attempts, 1);
192
193 retry_info.increment();
194 assert_eq!(retry_info.attempts, 2);
195 }
196
197 #[test]
198 fn test_retry_remaining() {
199 let mut retry_info = RetryInfo::new(5);
200 assert_eq!(retry_info.remaining(), 5);
201
202 retry_info.attempts = 2;
203 assert_eq!(retry_info.remaining(), 3);
204
205 retry_info.attempts = 5;
206 assert_eq!(retry_info.remaining(), 0);
207 }
208
209 #[test]
210 fn test_retry_default() {
211 let retry_info = RetryInfo::default();
212 assert_eq!(retry_info.max_attempts, 3);
213 assert_eq!(retry_info.attempts, 0);
214 }
215
216 #[test]
217 fn test_retry_serialization() {
218 let retry_info = RetryInfo {
219 attempts: 2,
220 max_attempts: 5,
221 retry_after_ms: Some(1000),
222 };
223
224 let json = serde_json::to_string(&retry_info).unwrap();
225 let deserialized: RetryInfo = serde_json::from_str(&json).unwrap();
226
227 assert_eq!(retry_info, deserialized);
228 }
229
230 #[test]
231 fn test_retry_serialization_no_delay() {
232 let retry_info = RetryInfo::new(3);
233
234 let json = serde_json::to_string(&retry_info).unwrap();
235 assert!(!json.contains("retry_after_ms")); // Should be skipped when None
236
237 let deserialized: RetryInfo = serde_json::from_str(&json).unwrap();
238 assert_eq!(retry_info, deserialized);
239 }
240}