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