mcpkit_transport/
error.rs

1//! Transport error types.
2
3use mcpkit_core::error::{McpError, TransportContext, TransportDetails, TransportErrorKind};
4use thiserror::Error;
5
6/// Errors that can occur during transport operations.
7#[derive(Error, Debug)]
8pub enum TransportError {
9    /// I/O error occurred.
10    #[error("I/O error: {message}")]
11    Io {
12        /// Error message.
13        message: String,
14    },
15
16    /// I/O error from `std::io::Error`.
17    #[error("I/O error: {0}")]
18    IoError(#[from] std::io::Error),
19
20    /// JSON serialization/deserialization error.
21    #[error("JSON error: {0}")]
22    Json(#[from] serde_json::Error),
23
24    /// Serialization error.
25    #[error("Serialization error: {message}")]
26    Serialization {
27        /// Error message.
28        message: String,
29    },
30
31    /// Deserialization error.
32    #[error("Deserialization error: {message}")]
33    Deserialization {
34        /// Error message.
35        message: String,
36    },
37
38    /// Connection error.
39    #[error("Connection error: {message}")]
40    Connection {
41        /// Error message.
42        message: String,
43    },
44
45    /// Connection was closed.
46    #[error("Connection closed")]
47    ConnectionClosed,
48
49    /// Transport is not connected.
50    #[error("Not connected")]
51    NotConnected,
52
53    /// Message was too large.
54    #[error("Message too large: {size} bytes (max: {max})")]
55    MessageTooLarge {
56        /// Actual message size.
57        size: usize,
58        /// Maximum allowed size.
59        max: usize,
60    },
61
62    /// Invalid message format.
63    #[error("Invalid message: {message}")]
64    InvalidMessage {
65        /// Description of the problem.
66        message: String,
67    },
68
69    /// Protocol error.
70    #[error("Protocol error: {message}")]
71    Protocol {
72        /// Description of the protocol violation.
73        message: String,
74    },
75
76    /// Timeout occurred.
77    #[error("{operation} timed out after {duration:?}")]
78    Timeout {
79        /// The operation that timed out.
80        operation: String,
81        /// How long the operation waited.
82        duration: std::time::Duration,
83    },
84
85    /// Transport was already closed.
86    #[error("Transport already closed")]
87    AlreadyClosed,
88
89    /// Rate limit exceeded.
90    #[error("Rate limit exceeded{}", retry_after.map(|d| format!(", retry after {d:?}")).unwrap_or_default())]
91    RateLimited {
92        /// Suggested retry delay.
93        retry_after: Option<std::time::Duration>,
94    },
95}
96
97impl TransportError {
98    /// Create an invalid message error.
99    pub fn invalid_message(message: impl Into<String>) -> Self {
100        Self::InvalidMessage {
101            message: message.into(),
102        }
103    }
104
105    /// Create a protocol error.
106    pub fn protocol(message: impl Into<String>) -> Self {
107        Self::Protocol {
108            message: message.into(),
109        }
110    }
111
112    /// Get the transport error kind.
113    #[must_use]
114    pub fn kind(&self) -> TransportErrorKind {
115        match self {
116            Self::Io { .. } => TransportErrorKind::ReadFailed,
117            Self::IoError(e) => match e.kind() {
118                std::io::ErrorKind::ConnectionRefused
119                | std::io::ErrorKind::ConnectionAborted
120                | std::io::ErrorKind::NotConnected => TransportErrorKind::ConnectionFailed,
121                std::io::ErrorKind::ConnectionReset | std::io::ErrorKind::BrokenPipe => {
122                    TransportErrorKind::ConnectionClosed
123                }
124                std::io::ErrorKind::TimedOut => TransportErrorKind::Timeout,
125                std::io::ErrorKind::WouldBlock
126                | std::io::ErrorKind::Interrupted
127                | std::io::ErrorKind::UnexpectedEof => TransportErrorKind::ReadFailed,
128                std::io::ErrorKind::WriteZero => TransportErrorKind::WriteFailed,
129                _ => TransportErrorKind::ReadFailed,
130            },
131            Self::Json(_) => TransportErrorKind::InvalidMessage,
132            Self::Serialization { .. } => TransportErrorKind::WriteFailed,
133            Self::Deserialization { .. } => TransportErrorKind::InvalidMessage,
134            Self::Connection { .. } => TransportErrorKind::ConnectionFailed,
135            Self::ConnectionClosed | Self::AlreadyClosed => TransportErrorKind::ConnectionClosed,
136            Self::NotConnected => TransportErrorKind::ConnectionFailed,
137            Self::MessageTooLarge { .. } => TransportErrorKind::InvalidMessage,
138            Self::InvalidMessage { .. } => TransportErrorKind::InvalidMessage,
139            Self::Protocol { .. } => TransportErrorKind::ProtocolViolation,
140            Self::Timeout { .. } => TransportErrorKind::Timeout,
141            Self::RateLimited { .. } => TransportErrorKind::RateLimited,
142        }
143    }
144}
145
146impl From<TransportError> for McpError {
147    fn from(err: TransportError) -> Self {
148        Self::Transport(Box::new(TransportDetails {
149            kind: err.kind(),
150            message: err.to_string(),
151            context: TransportContext::default(),
152            source: Some(Box::new(err)),
153        }))
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    fn test_error_kinds() {
163        assert_eq!(
164            TransportError::ConnectionClosed.kind(),
165            TransportErrorKind::ConnectionClosed
166        );
167        assert_eq!(
168            TransportError::Timeout {
169                operation: "test".to_string(),
170                duration: std::time::Duration::from_secs(1),
171            }
172            .kind(),
173            TransportErrorKind::Timeout
174        );
175        assert_eq!(
176            TransportError::invalid_message("bad").kind(),
177            TransportErrorKind::InvalidMessage
178        );
179    }
180
181    #[test]
182    fn test_mcp_error_conversion() {
183        let err = TransportError::ConnectionClosed;
184        let mcp_err: McpError = err.into();
185
186        match mcp_err {
187            McpError::Transport(details) => {
188                assert_eq!(details.kind, TransportErrorKind::ConnectionClosed);
189            }
190            _ => panic!("Expected Transport error"),
191        }
192    }
193}