1use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::fmt;
10use uuid::Uuid;
11
12#[cfg(feature = "fancy-errors")]
13use miette::Diagnostic;
14
15pub type Result<T> = std::result::Result<T, Box<Error>>;
17
18#[derive(Debug, Serialize)]
20#[cfg_attr(feature = "fancy-errors", derive(Diagnostic))]
21pub struct Error {
22 pub id: Uuid,
24
25 pub kind: ErrorKind,
27
28 pub message: String,
30
31 pub context: ErrorContext,
33
34 #[serde(skip)]
36 pub source: Option<Box<Error>>,
37
38 #[cfg(debug_assertions)]
40 #[serde(skip)]
41 pub backtrace: std::backtrace::Backtrace,
42}
43
44impl Clone for Error {
45 fn clone(&self) -> Self {
46 Self {
47 id: self.id,
48 kind: self.kind,
49 message: self.message.clone(),
50 context: self.context.clone(),
51 source: self.source.clone(),
52 #[cfg(debug_assertions)]
53 backtrace: std::backtrace::Backtrace::capture(),
54 }
55 }
56}
57
58impl<'de> Deserialize<'de> for Error {
59 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
60 where
61 D: serde::Deserializer<'de>,
62 {
63 #[derive(Deserialize)]
64 struct ErrorData {
65 id: Uuid,
66 kind: ErrorKind,
67 message: String,
68 context: ErrorContext,
69 }
70
71 let data = ErrorData::deserialize(deserializer)?;
72 Ok(Self {
73 id: data.id,
74 kind: data.kind,
75 message: data.message,
76 context: data.context,
77 source: None,
78 #[cfg(debug_assertions)]
79 backtrace: std::backtrace::Backtrace::capture(),
80 })
81 }
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
86#[serde(rename_all = "snake_case")]
87pub enum ErrorKind {
88 Validation,
90
91 Authentication,
93
94 NotFound,
96
97 PermissionDenied,
99
100 BadRequest,
102
103 Internal,
105
106 Transport,
108
109 Serialization,
111
112 Protocol,
114
115 Timeout,
117
118 Unavailable,
120
121 RateLimited,
123
124 Configuration,
126
127 ExternalService,
129
130 Cancelled,
132}
133
134#[derive(Debug, Clone, Default, Serialize, Deserialize)]
136pub struct ErrorContext {
137 pub operation: Option<String>,
139
140 pub component: Option<String>,
142
143 pub request_id: Option<String>,
145
146 pub user_id: Option<String>,
148
149 pub metadata: HashMap<String, serde_json::Value>,
151
152 pub timestamp: chrono::DateTime<chrono::Utc>,
154
155 pub retry_info: Option<RetryInfo>,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct RetryInfo {
162 pub attempts: u32,
164
165 pub max_attempts: u32,
167
168 pub retry_after_ms: Option<u64>,
170}
171
172impl Error {
173 pub fn new(kind: ErrorKind, message: impl Into<String>) -> Box<Self> {
175 Box::new(Self {
176 id: Uuid::new_v4(),
177 kind,
178 message: message.into(),
179 context: ErrorContext {
180 timestamp: chrono::Utc::now(),
181 ..Default::default()
182 },
183 source: None,
184 #[cfg(debug_assertions)]
185 backtrace: std::backtrace::Backtrace::capture(),
186 })
187 }
188
189 pub fn validation(message: impl Into<String>) -> Box<Self> {
191 Self::new(ErrorKind::Validation, message)
192 }
193
194 pub fn authentication(message: impl Into<String>) -> Box<Self> {
196 Self::new(ErrorKind::Authentication, message)
197 }
198
199 pub fn not_found(message: impl Into<String>) -> Box<Self> {
201 Self::new(ErrorKind::NotFound, message)
202 }
203
204 pub fn permission_denied(message: impl Into<String>) -> Box<Self> {
206 Self::new(ErrorKind::PermissionDenied, message)
207 }
208
209 pub fn bad_request(message: impl Into<String>) -> Box<Self> {
211 Self::new(ErrorKind::BadRequest, message)
212 }
213
214 pub fn internal(message: impl Into<String>) -> Box<Self> {
216 Self::new(ErrorKind::Internal, message)
217 }
218
219 pub fn transport(message: impl Into<String>) -> Box<Self> {
221 Self::new(ErrorKind::Transport, message)
222 }
223
224 pub fn serialization(message: impl Into<String>) -> Box<Self> {
226 Self::new(ErrorKind::Serialization, message)
227 }
228
229 pub fn protocol(message: impl Into<String>) -> Box<Self> {
231 Self::new(ErrorKind::Protocol, message)
232 }
233
234 #[must_use]
236 pub fn rpc(code: i32, message: &str) -> Box<Self> {
237 Self::new(ErrorKind::Protocol, format!("RPC error {code}: {message}"))
238 }
239
240 pub fn timeout(message: impl Into<String>) -> Box<Self> {
242 Self::new(ErrorKind::Timeout, message)
243 }
244
245 pub fn unavailable(message: impl Into<String>) -> Box<Self> {
247 Self::new(ErrorKind::Unavailable, message)
248 }
249
250 pub fn rate_limited(message: impl Into<String>) -> Box<Self> {
252 Self::new(ErrorKind::RateLimited, message)
253 }
254
255 pub fn configuration(message: impl Into<String>) -> Box<Self> {
257 Self::new(ErrorKind::Configuration, message)
258 }
259
260 pub fn external_service(message: impl Into<String>) -> Box<Self> {
262 Self::new(ErrorKind::ExternalService, message)
263 }
264
265 pub fn cancelled(message: impl Into<String>) -> Box<Self> {
267 Self::new(ErrorKind::Cancelled, message)
268 }
269
270 #[must_use]
272 pub fn with_context(
273 mut self: Box<Self>,
274 key: impl Into<String>,
275 value: impl Into<serde_json::Value>,
276 ) -> Box<Self> {
277 self.context.metadata.insert(key.into(), value.into());
278 self
279 }
280
281 #[must_use]
283 pub fn with_operation(mut self: Box<Self>, operation: impl Into<String>) -> Box<Self> {
284 self.context.operation = Some(operation.into());
285 self
286 }
287
288 #[must_use]
290 pub fn with_component(mut self: Box<Self>, component: impl Into<String>) -> Box<Self> {
291 self.context.component = Some(component.into());
292 self
293 }
294
295 #[must_use]
297 pub fn with_request_id(mut self: Box<Self>, request_id: impl Into<String>) -> Box<Self> {
298 self.context.request_id = Some(request_id.into());
299 self
300 }
301
302 #[must_use]
304 pub fn with_user_id(mut self: Box<Self>, user_id: impl Into<String>) -> Box<Self> {
305 self.context.user_id = Some(user_id.into());
306 self
307 }
308
309 #[must_use]
311 pub fn with_retry_info(mut self: Box<Self>, retry_info: RetryInfo) -> Box<Self> {
312 self.context.retry_info = Some(retry_info);
313 self
314 }
315
316 #[must_use]
318 pub fn with_source(mut self: Box<Self>, source: Box<Self>) -> Box<Self> {
319 self.source = Some(source);
320 self
321 }
322
323 pub const fn is_retryable(&self) -> bool {
325 matches!(
326 self.kind,
327 ErrorKind::Timeout
328 | ErrorKind::Unavailable
329 | ErrorKind::Transport
330 | ErrorKind::ExternalService
331 | ErrorKind::RateLimited
332 )
333 }
334
335 pub const fn is_temporary(&self) -> bool {
337 matches!(
338 self.kind,
339 ErrorKind::Timeout
340 | ErrorKind::Unavailable
341 | ErrorKind::RateLimited
342 | ErrorKind::ExternalService
343 )
344 }
345
346 pub const fn http_status_code(&self) -> u16 {
348 match self.kind {
349 ErrorKind::Validation | ErrorKind::BadRequest => 400,
350 ErrorKind::Authentication => 401,
351 ErrorKind::PermissionDenied => 403,
352 ErrorKind::NotFound => 404,
353 ErrorKind::Timeout => 408,
354 ErrorKind::RateLimited => 429,
355 ErrorKind::Internal
356 | ErrorKind::Configuration
357 | ErrorKind::Serialization
358 | ErrorKind::Protocol => 500,
359 ErrorKind::Transport | ErrorKind::ExternalService | ErrorKind::Unavailable => 503,
360 ErrorKind::Cancelled => 499, }
362 }
363
364 pub const fn jsonrpc_error_code(&self) -> i32 {
366 match self.kind {
367 ErrorKind::BadRequest | ErrorKind::Validation => -32600, ErrorKind::Protocol => -32601, ErrorKind::Serialization => -32602, ErrorKind::Internal => -32603, ErrorKind::NotFound => -32001, ErrorKind::Authentication => -32002, ErrorKind::PermissionDenied => -32003, ErrorKind::Timeout => -32004, ErrorKind::Unavailable => -32005, ErrorKind::RateLimited => -32006, ErrorKind::Transport => -32007, ErrorKind::Configuration => -32008, ErrorKind::ExternalService => -32009, ErrorKind::Cancelled => -32010, }
382 }
383}
384
385impl fmt::Display for Error {
386 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
387 write!(f, "{}", self.message)?;
388
389 if let Some(operation) = &self.context.operation {
390 write!(f, " (operation: {operation})")?;
391 }
392
393 if let Some(component) = &self.context.component {
394 write!(f, " (component: {component})")?;
395 }
396
397 if let Some(request_id) = &self.context.request_id {
398 write!(f, " (request_id: {request_id})")?;
399 }
400
401 Ok(())
402 }
403}
404
405impl std::error::Error for Error {
406 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
407 None
411 }
412}
413
414impl ErrorKind {
415 #[must_use]
417 pub const fn description(self) -> &'static str {
418 match self {
419 Self::Validation => "Input validation failed",
420 Self::Authentication => "Authentication failed",
421 Self::NotFound => "Resource not found",
422 Self::PermissionDenied => "Permission denied",
423 Self::BadRequest => "Bad request",
424 Self::Internal => "Internal server error",
425 Self::Transport => "Transport error",
426 Self::Serialization => "Serialization error",
427 Self::Protocol => "Protocol error",
428 Self::Timeout => "Operation timed out",
429 Self::Unavailable => "Service unavailable",
430 Self::RateLimited => "Rate limit exceeded",
431 Self::Configuration => "Configuration error",
432 Self::ExternalService => "External service error",
433 Self::Cancelled => "Operation cancelled",
434 }
435 }
436}
437
438impl fmt::Display for ErrorKind {
439 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
440 write!(f, "{}", self.description())
441 }
442}
443
444#[macro_export]
446macro_rules! mcp_error {
447 ($kind:expr, $message:expr) => {
448 $crate::error::Error::new($kind, $message)
449 };
450 ($kind:expr, $message:expr, $($key:expr => $value:expr),+) => {
451 {
452 let mut error = $crate::error::Error::new($kind, $message);
453 $(
454 error = error.with_context($key, $value);
455 )+
456 error
457 }
458 };
459}
460
461pub trait ErrorExt<T> {
463 fn with_mcp_error(self, kind: ErrorKind, message: impl Into<String>) -> Result<T>;
469
470 fn with_internal_error(self, message: impl Into<String>) -> Result<T>;
476}
477
478impl<T, E> ErrorExt<T> for std::result::Result<T, E>
479where
480 E: std::error::Error + Send + Sync + 'static,
481{
482 fn with_mcp_error(self, kind: ErrorKind, message: impl Into<String>) -> Result<T> {
483 self.map_err(|e| {
484 Error::new(kind, format!("{}: {}", message.into(), e))
485 .with_context("source_error", e.to_string())
486 })
487 }
488
489 fn with_internal_error(self, message: impl Into<String>) -> Result<T> {
490 self.with_mcp_error(ErrorKind::Internal, message)
491 }
492}
493
494impl From<serde_json::Error> for Box<Error> {
496 fn from(err: serde_json::Error) -> Self {
497 Error::serialization(format!("JSON serialization error: {err}"))
498 }
499}
500
501impl From<std::io::Error> for Box<Error> {
502 fn from(err: std::io::Error) -> Self {
503 Error::transport(format!("IO error: {err}"))
504 }
505}
506
507#[cfg(test)]
508mod tests {
509 use super::*;
510
511 #[test]
512 fn test_error_creation() {
513 let error = Error::validation("Invalid input");
514 assert_eq!(error.kind, ErrorKind::Validation);
515 assert_eq!(error.message, "Invalid input");
516 }
517
518 #[test]
519 fn test_error_context() {
520 let error = Error::internal("Something went wrong")
521 .with_operation("test_operation")
522 .with_component("test_component")
523 .with_request_id("req-123")
524 .with_context("key", "value");
525
526 assert_eq!(error.context.operation, Some("test_operation".to_string()));
527 assert_eq!(error.context.component, Some("test_component".to_string()));
528 assert_eq!(error.context.request_id, Some("req-123".to_string()));
529 assert_eq!(
530 error.context.metadata.get("key"),
531 Some(&serde_json::Value::String("value".to_string()))
532 );
533 }
534
535 #[test]
536 fn test_error_properties() {
537 let retryable_error = Error::timeout("Request timed out");
538 assert!(retryable_error.is_retryable());
539 assert!(retryable_error.is_temporary());
540
541 let permanent_error = Error::validation("Invalid data");
542 assert!(!permanent_error.is_retryable());
543 assert!(!permanent_error.is_temporary());
544 }
545
546 #[test]
547 fn test_http_status_codes() {
548 assert_eq!(Error::validation("test").http_status_code(), 400);
549 assert_eq!(Error::not_found("test").http_status_code(), 404);
550 assert_eq!(Error::internal("test").http_status_code(), 500);
551 }
552
553 #[test]
554 fn test_error_macro() {
555 let error = mcp_error!(ErrorKind::Validation, "test message");
556 assert_eq!(error.kind, ErrorKind::Validation);
557 assert_eq!(error.message, "test message");
558
559 let error_with_context = mcp_error!(
560 ErrorKind::Internal,
561 "test message",
562 "key1" => "value1",
563 "key2" => 42
564 );
565 assert_eq!(error_with_context.context.metadata.len(), 2);
566 }
567}