mcpkit_transport/
error.rs1use mcpkit_core::error::{McpError, TransportContext, TransportDetails, TransportErrorKind};
4use thiserror::Error;
5
6#[derive(Error, Debug)]
8pub enum TransportError {
9 #[error("I/O error: {message}")]
11 Io {
12 message: String,
14 },
15
16 #[error("I/O error: {0}")]
18 IoError(#[from] std::io::Error),
19
20 #[error("JSON error: {0}")]
22 Json(#[from] serde_json::Error),
23
24 #[error("Serialization error: {message}")]
26 Serialization {
27 message: String,
29 },
30
31 #[error("Deserialization error: {message}")]
33 Deserialization {
34 message: String,
36 },
37
38 #[error("Connection error: {message}")]
40 Connection {
41 message: String,
43 },
44
45 #[error("Connection closed")]
47 ConnectionClosed,
48
49 #[error("Not connected")]
51 NotConnected,
52
53 #[error("Message too large: {size} bytes (max: {max})")]
55 MessageTooLarge {
56 size: usize,
58 max: usize,
60 },
61
62 #[error("Invalid message: {message}")]
64 InvalidMessage {
65 message: String,
67 },
68
69 #[error("Protocol error: {message}")]
71 Protocol {
72 message: String,
74 },
75
76 #[error("{operation} timed out after {duration:?}")]
78 Timeout {
79 operation: String,
81 duration: std::time::Duration,
83 },
84
85 #[error("Transport already closed")]
87 AlreadyClosed,
88
89 #[error("Rate limit exceeded{}", retry_after.map(|d| format!(", retry after {d:?}")).unwrap_or_default())]
91 RateLimited {
92 retry_after: Option<std::time::Duration>,
94 },
95}
96
97impl TransportError {
98 pub fn invalid_message(message: impl Into<String>) -> Self {
100 Self::InvalidMessage {
101 message: message.into(),
102 }
103 }
104
105 pub fn protocol(message: impl Into<String>) -> Self {
107 Self::Protocol {
108 message: message.into(),
109 }
110 }
111
112 #[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}