pulseengine_mcp_protocol/
error.rs1use serde::{Deserialize, Serialize};
4use std::fmt;
5
6pub type Result<T> = std::result::Result<T, Error>;
10
11pub type McpResult<T> = std::result::Result<T, Error>;
13
14#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, thiserror::Error)]
16pub struct Error {
17 pub code: ErrorCode,
19 pub message: String,
21 #[serde(skip_serializing_if = "Option::is_none")]
23 pub data: Option<serde_json::Value>,
24}
25
26impl fmt::Display for Error {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 write!(f, "{}: {}", self.code, self.message)
29 }
30}
31
32impl Error {
33 pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
35 Self {
36 code,
37 message: message.into(),
38 data: None,
39 }
40 }
41
42 pub fn with_data(code: ErrorCode, message: impl Into<String>, data: serde_json::Value) -> Self {
44 Self {
45 code,
46 message: message.into(),
47 data: Some(data),
48 }
49 }
50
51 pub fn parse_error(message: impl Into<String>) -> Self {
53 Self::new(ErrorCode::ParseError, message)
54 }
55
56 pub fn invalid_request(message: impl Into<String>) -> Self {
58 Self::new(ErrorCode::InvalidRequest, message)
59 }
60
61 pub fn method_not_found(method: impl Into<String>) -> Self {
63 Self::new(
64 ErrorCode::MethodNotFound,
65 format!("Method not found: {}", method.into()),
66 )
67 }
68
69 pub fn invalid_params(message: impl Into<String>) -> Self {
71 Self::new(ErrorCode::InvalidParams, message)
72 }
73
74 pub fn internal_error(message: impl Into<String>) -> Self {
76 Self::new(ErrorCode::InternalError, message)
77 }
78
79 pub fn protocol_version_mismatch(client_version: &str, server_version: &str) -> Self {
81 Self::with_data(
82 ErrorCode::InvalidRequest,
83 format!("Protocol version mismatch: client={client_version}, server={server_version}"),
84 serde_json::json!({
85 "client_version": client_version,
86 "server_version": server_version
87 }),
88 )
89 }
90
91 pub fn unauthorized(message: impl Into<String>) -> Self {
93 Self::new(ErrorCode::Unauthorized, message)
94 }
95
96 pub fn forbidden(message: impl Into<String>) -> Self {
98 Self::new(ErrorCode::Forbidden, message)
99 }
100
101 pub fn resource_not_found(resource: impl Into<String>) -> Self {
103 Self::new(
104 ErrorCode::ResourceNotFound,
105 format!("Resource not found: {}", resource.into()),
106 )
107 }
108
109 pub fn tool_not_found(tool: impl Into<String>) -> Self {
111 Self::new(
112 ErrorCode::ToolNotFound,
113 format!("Tool not found: {}", tool.into()),
114 )
115 }
116
117 pub fn validation_error(message: impl Into<String>) -> Self {
119 Self::new(ErrorCode::ValidationError, message)
120 }
121
122 pub fn rate_limit_exceeded(message: impl Into<String>) -> Self {
124 Self::new(ErrorCode::RateLimitExceeded, message)
125 }
126
127 pub fn url_elicitation_required(
133 message: impl Into<String>,
134 elicitations: Vec<crate::UrlElicitationInfo>,
135 ) -> Self {
136 let data = crate::UrlElicitationRequiredData { elicitations };
137 Self::with_data(
138 ErrorCode::UrlElicitationRequired,
139 message,
140 serde_json::to_value(data).unwrap_or_default(),
141 )
142 }
143}
144
145#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
147#[repr(i32)]
148pub enum ErrorCode {
149 ParseError = -32700,
151 InvalidRequest = -32600,
152 MethodNotFound = -32601,
153 InvalidParams = -32602,
154 InternalError = -32603,
155
156 Unauthorized = -32000,
158 Forbidden = -32001,
159 ResourceNotFound = -32002,
160 ToolNotFound = -32003,
161 ValidationError = -32004,
162 RateLimitExceeded = -32005,
163
164 UrlElicitationRequired = -32042,
167}
168
169impl Serialize for ErrorCode {
170 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
171 where
172 S: serde::Serializer,
173 {
174 serializer.serialize_i32(*self as i32)
175 }
176}
177
178impl<'de> Deserialize<'de> for ErrorCode {
179 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
180 where
181 D: serde::Deserializer<'de>,
182 {
183 let code = i32::deserialize(deserializer)?;
184 match code {
185 -32700 => Ok(ErrorCode::ParseError),
186 -32600 => Ok(ErrorCode::InvalidRequest),
187 -32601 => Ok(ErrorCode::MethodNotFound),
188 -32602 => Ok(ErrorCode::InvalidParams),
189 -32603 => Ok(ErrorCode::InternalError),
190 -32000 => Ok(ErrorCode::Unauthorized),
191 -32001 => Ok(ErrorCode::Forbidden),
192 -32002 => Ok(ErrorCode::ResourceNotFound),
193 -32003 => Ok(ErrorCode::ToolNotFound),
194 -32004 => Ok(ErrorCode::ValidationError),
195 -32005 => Ok(ErrorCode::RateLimitExceeded),
196 -32042 => Ok(ErrorCode::UrlElicitationRequired),
197 _ => Err(serde::de::Error::custom(format!(
198 "Unknown error code: {code}"
199 ))),
200 }
201 }
202}
203
204impl fmt::Display for ErrorCode {
205 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206 let name = match self {
207 ErrorCode::ParseError => "ParseError",
208 ErrorCode::InvalidRequest => "InvalidRequest",
209 ErrorCode::MethodNotFound => "MethodNotFound",
210 ErrorCode::InvalidParams => "InvalidParams",
211 ErrorCode::InternalError => "InternalError",
212 ErrorCode::Unauthorized => "Unauthorized",
213 ErrorCode::Forbidden => "Forbidden",
214 ErrorCode::ResourceNotFound => "ResourceNotFound",
215 ErrorCode::ToolNotFound => "ToolNotFound",
216 ErrorCode::ValidationError => "ValidationError",
217 ErrorCode::RateLimitExceeded => "RateLimitExceeded",
218 ErrorCode::UrlElicitationRequired => "UrlElicitationRequired",
219 };
220 write!(f, "{name}")
221 }
222}
223
224impl From<serde_json::Error> for Error {
226 fn from(err: serde_json::Error) -> Self {
227 Error::parse_error(err.to_string())
228 }
229}
230
231impl From<uuid::Error> for Error {
232 fn from(err: uuid::Error) -> Self {
233 Error::validation_error(format!("Invalid UUID: {err}"))
234 }
235}
236
237impl From<validator::ValidationErrors> for Error {
238 fn from(err: validator::ValidationErrors) -> Self {
239 Error::validation_error(err.to_string())
240 }
241}
242
243#[cfg(feature = "logging")]
244impl From<pulseengine_mcp_logging::LoggingError> for Error {
245 fn from(err: pulseengine_mcp_logging::LoggingError) -> Self {
246 match err {
247 pulseengine_mcp_logging::LoggingError::Config(msg) => {
248 Error::invalid_request(format!("Logging config: {msg}"))
249 }
250 pulseengine_mcp_logging::LoggingError::Io(io_err) => {
251 Error::internal_error(format!("Logging I/O: {io_err}"))
252 }
253 pulseengine_mcp_logging::LoggingError::Serialization(serde_err) => {
254 Error::internal_error(format!("Logging serialization: {serde_err}"))
255 }
256 pulseengine_mcp_logging::LoggingError::Tracing(msg) => {
257 Error::internal_error(format!("Tracing: {msg}"))
258 }
259 }
260 }
261}
262
263#[cfg(feature = "logging")]
265impl pulseengine_mcp_logging::ErrorClassification for Error {
266 fn error_type(&self) -> &str {
267 match self.code {
268 ErrorCode::ParseError => "parse_error",
269 ErrorCode::InvalidRequest => "invalid_request",
270 ErrorCode::MethodNotFound => "method_not_found",
271 ErrorCode::InvalidParams => "invalid_params",
272 ErrorCode::InternalError => "internal_error",
273 ErrorCode::Unauthorized => "unauthorized",
274 ErrorCode::Forbidden => "forbidden",
275 ErrorCode::ResourceNotFound => "resource_not_found",
276 ErrorCode::ToolNotFound => "tool_not_found",
277 ErrorCode::ValidationError => "validation_error",
278 ErrorCode::RateLimitExceeded => "rate_limit_exceeded",
279 ErrorCode::UrlElicitationRequired => "url_elicitation_required",
280 }
281 }
282
283 fn is_retryable(&self) -> bool {
284 matches!(
286 self.code,
287 ErrorCode::InternalError
288 | ErrorCode::RateLimitExceeded
289 | ErrorCode::UrlElicitationRequired
290 )
291 }
292
293 fn is_timeout(&self) -> bool {
294 false }
296
297 fn is_auth_error(&self) -> bool {
298 matches!(self.code, ErrorCode::Unauthorized | ErrorCode::Forbidden)
299 }
300
301 fn is_connection_error(&self) -> bool {
302 false }
304}