turbomcp_server/
error.rs

1//! Server error types and handling
2
3/// Result type for server operations
4pub type ServerResult<T> = Result<T, ServerError>;
5
6/// Comprehensive server error types
7#[derive(Debug, thiserror::Error)]
8pub enum ServerError {
9    /// Core errors
10    #[error("Core error: {0}")]
11    Core(#[from] turbomcp_core::registry::RegistryError),
12
13    /// Transport layer errors
14    #[error("Transport error: {0}")]
15    Transport(#[from] turbomcp_transport::TransportError),
16
17    /// Handler registration errors
18    #[error("Handler error: {message}")]
19    Handler {
20        /// Error message
21        message: String,
22        /// Optional error context
23        context: Option<String>,
24    },
25
26    /// Configuration errors
27    #[error("Configuration error: {message}")]
28    Configuration {
29        /// Error message
30        message: String,
31        /// Configuration key that caused the error
32        key: Option<String>,
33    },
34
35    /// Authentication errors
36    #[error("Authentication error: {message}")]
37    Authentication {
38        /// Error message
39        message: String,
40        /// Authentication method that failed
41        method: Option<String>,
42    },
43
44    /// Authorization errors
45    #[error("Authorization error: {message}")]
46    Authorization {
47        /// Error message
48        message: String,
49        /// Resource being accessed
50        resource: Option<String>,
51    },
52
53    /// Rate limiting errors
54    #[error("Rate limit exceeded: {message}")]
55    RateLimit {
56        /// Error message
57        message: String,
58        /// Retry after seconds
59        retry_after: Option<u64>,
60    },
61
62    /// Server lifecycle errors
63    #[error("Lifecycle error: {0}")]
64    Lifecycle(String),
65
66    /// Server shutdown errors
67    #[error("Shutdown error: {0}")]
68    Shutdown(String),
69
70    /// Middleware errors
71    #[error("Middleware error: {name}: {message}")]
72    Middleware {
73        /// Middleware name
74        name: String,
75        /// Error message
76        message: String,
77    },
78
79    /// Registry errors
80    #[error("Registry error: {0}")]
81    Registry(String),
82
83    /// Routing errors
84    #[error("Routing error: {message}")]
85    Routing {
86        /// Error message
87        message: String,
88        /// Request method that failed
89        method: Option<String>,
90    },
91
92    /// Resource not found
93    #[error("Resource not found: {resource}")]
94    NotFound {
95        /// Resource that was not found
96        resource: String,
97    },
98
99    /// Internal server errors
100    #[error("Internal server error: {0}")]
101    Internal(String),
102
103    /// IO errors
104    #[error("IO error: {0}")]
105    Io(#[from] std::io::Error),
106
107    /// Serialization errors
108    #[error("Serialization error: {0}")]
109    Serialization(#[from] serde_json::Error),
110
111    /// Timeout errors
112    #[error("Timeout error: {operation} timed out after {timeout_ms}ms")]
113    Timeout {
114        /// Operation that timed out
115        operation: String,
116        /// Timeout in milliseconds
117        timeout_ms: u64,
118    },
119
120    /// Resource exhaustion
121    #[error("Resource exhausted: {resource}")]
122    ResourceExhausted {
123        /// Resource type
124        resource: String,
125        /// Current usage
126        current: Option<usize>,
127        /// Maximum allowed
128        max: Option<usize>,
129    },
130}
131
132impl ServerError {
133    /// Create a new handler error
134    pub fn handler(message: impl Into<String>) -> Self {
135        Self::Handler {
136            message: message.into(),
137            context: None,
138        }
139    }
140
141    /// Create a handler error with context
142    pub fn handler_with_context(message: impl Into<String>, context: impl Into<String>) -> Self {
143        Self::Handler {
144            message: message.into(),
145            context: Some(context.into()),
146        }
147    }
148
149    /// Create a new configuration error
150    pub fn configuration(message: impl Into<String>) -> Self {
151        Self::Configuration {
152            message: message.into(),
153            key: None,
154        }
155    }
156
157    /// Create a configuration error with key
158    pub fn configuration_with_key(message: impl Into<String>, key: impl Into<String>) -> Self {
159        Self::Configuration {
160            message: message.into(),
161            key: Some(key.into()),
162        }
163    }
164
165    /// Create a new authentication error
166    pub fn authentication(message: impl Into<String>) -> Self {
167        Self::Authentication {
168            message: message.into(),
169            method: None,
170        }
171    }
172
173    /// Create an authentication error with method
174    pub fn authentication_with_method(
175        message: impl Into<String>,
176        method: impl Into<String>,
177    ) -> Self {
178        Self::Authentication {
179            message: message.into(),
180            method: Some(method.into()),
181        }
182    }
183
184    /// Create a new authorization error
185    pub fn authorization(message: impl Into<String>) -> Self {
186        Self::Authorization {
187            message: message.into(),
188            resource: None,
189        }
190    }
191
192    /// Create an authorization error with resource
193    pub fn authorization_with_resource(
194        message: impl Into<String>,
195        resource: impl Into<String>,
196    ) -> Self {
197        Self::Authorization {
198            message: message.into(),
199            resource: Some(resource.into()),
200        }
201    }
202
203    /// Create a new rate limit error
204    pub fn rate_limit(message: impl Into<String>) -> Self {
205        Self::RateLimit {
206            message: message.into(),
207            retry_after: None,
208        }
209    }
210
211    /// Create a rate limit error with retry after
212    pub fn rate_limit_with_retry(message: impl Into<String>, retry_after: u64) -> Self {
213        Self::RateLimit {
214            message: message.into(),
215            retry_after: Some(retry_after),
216        }
217    }
218
219    /// Create a new middleware error
220    pub fn middleware(name: impl Into<String>, message: impl Into<String>) -> Self {
221        Self::Middleware {
222            name: name.into(),
223            message: message.into(),
224        }
225    }
226
227    /// Create a new routing error
228    pub fn routing(message: impl Into<String>) -> Self {
229        Self::Routing {
230            message: message.into(),
231            method: None,
232        }
233    }
234
235    /// Create a routing error with method
236    pub fn routing_with_method(message: impl Into<String>, method: impl Into<String>) -> Self {
237        Self::Routing {
238            message: message.into(),
239            method: Some(method.into()),
240        }
241    }
242
243    /// Create a not found error
244    pub fn not_found(resource: impl Into<String>) -> Self {
245        Self::NotFound {
246            resource: resource.into(),
247        }
248    }
249
250    /// Create a timeout error
251    pub fn timeout(operation: impl Into<String>, timeout_ms: u64) -> Self {
252        Self::Timeout {
253            operation: operation.into(),
254            timeout_ms,
255        }
256    }
257
258    /// Create a resource exhausted error
259    pub fn resource_exhausted(resource: impl Into<String>) -> Self {
260        Self::ResourceExhausted {
261            resource: resource.into(),
262            current: None,
263            max: None,
264        }
265    }
266
267    /// Create a resource exhausted error with usage info
268    pub fn resource_exhausted_with_usage(
269        resource: impl Into<String>,
270        current: usize,
271        max: usize,
272    ) -> Self {
273        Self::ResourceExhausted {
274            resource: resource.into(),
275            current: Some(current),
276            max: Some(max),
277        }
278    }
279
280    /// Check if this error is retryable
281    #[must_use]
282    pub const fn is_retryable(&self) -> bool {
283        matches!(
284            self,
285            Self::Timeout { .. } | Self::ResourceExhausted { .. } | Self::RateLimit { .. }
286        )
287    }
288
289    /// Check if this error should cause server shutdown
290    #[must_use]
291    pub const fn is_fatal(&self) -> bool {
292        matches!(
293            self,
294            Self::Lifecycle(_) | Self::Shutdown(_) | Self::Internal(_)
295        )
296    }
297
298    /// Get error code for JSON-RPC responses
299    #[must_use]
300    pub const fn error_code(&self) -> i32 {
301        match self {
302            Self::Core(_) => -32603,
303            Self::NotFound { .. } => -32004,
304            Self::Authentication { .. } => -32008,
305            Self::Authorization { .. } => -32005,
306            Self::RateLimit { .. } => -32009,
307            Self::ResourceExhausted { .. } => -32010,
308            Self::Timeout { .. } => -32603,
309            Self::Handler { .. } => -32002,
310            _ => -32603,
311        }
312    }
313}
314
315/// Error recovery strategies
316#[derive(Debug, Clone, Copy, PartialEq, Eq)]
317pub enum ErrorRecovery {
318    /// Retry the operation
319    Retry,
320    /// Skip and continue
321    Skip,
322    /// Fail immediately
323    Fail,
324    /// Graceful degradation
325    Degrade,
326}
327
328/// Error context for detailed error reporting
329#[derive(Debug, Clone)]
330pub struct ErrorContext {
331    /// Error category
332    pub category: String,
333    /// Operation being performed
334    pub operation: String,
335    /// Request ID if applicable
336    pub request_id: Option<String>,
337    /// Client ID if applicable
338    pub client_id: Option<String>,
339    /// Additional metadata
340    pub metadata: std::collections::HashMap<String, String>,
341}
342
343impl ErrorContext {
344    /// Create a new error context
345    pub fn new(category: impl Into<String>, operation: impl Into<String>) -> Self {
346        Self {
347            category: category.into(),
348            operation: operation.into(),
349            request_id: None,
350            client_id: None,
351            metadata: std::collections::HashMap::new(),
352        }
353    }
354
355    /// Add request ID to context
356    pub fn with_request_id(mut self, request_id: impl Into<String>) -> Self {
357        self.request_id = Some(request_id.into());
358        self
359    }
360
361    /// Add client ID to context
362    pub fn with_client_id(mut self, client_id: impl Into<String>) -> Self {
363        self.client_id = Some(client_id.into());
364        self
365    }
366
367    /// Add metadata to context
368    pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
369        self.metadata.insert(key.into(), value.into());
370        self
371    }
372}
373
374// Conversion from core errors to server errors
375impl From<Box<turbomcp_core::Error>> for ServerError {
376    fn from(core_error: Box<turbomcp_core::Error>) -> Self {
377        use turbomcp_core::ErrorKind;
378
379        match core_error.kind {
380            ErrorKind::Handler => Self::Handler {
381                message: core_error.message,
382                context: core_error.context.operation,
383            },
384            ErrorKind::Authentication => Self::Authentication {
385                message: core_error.message,
386                method: None,
387            },
388            ErrorKind::PermissionDenied => Self::Authorization {
389                message: core_error.message,
390                resource: None,
391            },
392            ErrorKind::NotFound => Self::NotFound {
393                resource: core_error.message,
394            },
395            ErrorKind::BadRequest | ErrorKind::Validation => Self::Handler {
396                message: format!("Validation error: {}", core_error.message),
397                context: None,
398            },
399            ErrorKind::Timeout => Self::Timeout {
400                operation: core_error
401                    .context
402                    .operation
403                    .unwrap_or_else(|| "unknown".to_string()),
404                timeout_ms: 30000, // Default timeout
405            },
406            ErrorKind::RateLimited => Self::RateLimit {
407                message: core_error.message,
408                retry_after: None,
409            },
410            ErrorKind::Configuration => Self::Configuration {
411                message: core_error.message,
412                key: None,
413            },
414            ErrorKind::Transport => {
415                Self::Internal(format!("Transport error: {}", core_error.message))
416            }
417            ErrorKind::Serialization => {
418                Self::Internal(format!("Serialization error: {}", core_error.message))
419            }
420            ErrorKind::Protocol => {
421                Self::Internal(format!("Protocol error: {}", core_error.message))
422            }
423            ErrorKind::Unavailable => Self::ResourceExhausted {
424                resource: "service".to_string(),
425                current: None,
426                max: None,
427            },
428            ErrorKind::ExternalService => {
429                Self::Internal(format!("External service error: {}", core_error.message))
430            }
431            ErrorKind::Cancelled => {
432                Self::Internal(format!("Operation cancelled: {}", core_error.message))
433            }
434            ErrorKind::Internal => Self::Internal(core_error.message),
435        }
436    }
437}
438
439// Note: McpError conversion is handled by the turbomcp crate
440// since McpError wraps ServerError, not the other way around