Skip to main content

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}