pulseengine_mcp_protocol/
errors.rs1pub use crate::error::{Error, ErrorCode, McpResult};
8
9pub mod prelude {
17 pub use super::{BackendErrorExt, CommonError, CommonResult, ErrorContext, ErrorContextExt};
18 pub use super::{Error, ErrorCode, McpResult};
19}
20
21pub trait ErrorContext<T> {
23 fn with_context<F>(self, f: F) -> McpResult<T>
25 where
26 F: FnOnce() -> String;
27
28 fn context(self, msg: &'static str) -> McpResult<T>;
30}
31
32impl<T, E> ErrorContext<T> for Result<T, E>
33where
34 E: std::error::Error + Send + Sync + 'static,
35{
36 fn with_context<F>(self, f: F) -> McpResult<T>
37 where
38 F: FnOnce() -> String,
39 {
40 self.map_err(|e| Error::internal_error(format!("{}: {}", f(), e)))
41 }
42
43 fn context(self, msg: &'static str) -> McpResult<T> {
44 self.map_err(|e| Error::internal_error(format!("{msg}: {e}")))
45 }
46}
47
48pub trait ErrorContextExt<T> {
50 fn internal_error(self) -> McpResult<T>;
52
53 fn validation_error(self) -> McpResult<T>;
55
56 fn invalid_params(self) -> McpResult<T>;
58}
59
60impl<T, E> ErrorContextExt<T> for Result<T, E>
61where
62 E: std::error::Error + Send + Sync + 'static,
63{
64 fn internal_error(self) -> McpResult<T> {
65 self.map_err(|e| Error::internal_error(e.to_string()))
66 }
67
68 fn validation_error(self) -> McpResult<T> {
69 self.map_err(|e| Error::validation_error(e.to_string()))
70 }
71
72 fn invalid_params(self) -> McpResult<T> {
73 self.map_err(|e| Error::invalid_params(e.to_string()))
74 }
75}
76
77#[derive(Debug, Clone, thiserror::Error)]
79pub enum CommonError {
80 #[error("Configuration error: {0}")]
81 Config(String),
82
83 #[error("Connection error: {0}")]
84 Connection(String),
85
86 #[error("Authentication error: {0}")]
87 Auth(String),
88
89 #[error("Validation error: {0}")]
90 Validation(String),
91
92 #[error("Storage error: {0}")]
93 Storage(String),
94
95 #[error("Network error: {0}")]
96 Network(String),
97
98 #[error("Timeout error: {0}")]
99 Timeout(String),
100
101 #[error("Not found: {0}")]
102 NotFound(String),
103
104 #[error("Permission denied: {0}")]
105 PermissionDenied(String),
106
107 #[error("Rate limited: {0}")]
108 RateLimit(String),
109
110 #[error("Internal error: {0}")]
111 Internal(String),
112
113 #[error("Custom error: {0}")]
114 Custom(String),
115}
116
117impl From<CommonError> for Error {
118 fn from(err: CommonError) -> Self {
119 match err {
120 CommonError::Config(msg) => Error::invalid_request(format!("Configuration: {msg}")),
121 CommonError::Connection(msg) => Error::internal_error(format!("Connection: {msg}")),
122 CommonError::Auth(msg) => Error::unauthorized(msg),
123 CommonError::Validation(msg) => Error::validation_error(msg),
124 CommonError::Storage(msg) => Error::internal_error(format!("Storage: {msg}")),
125 CommonError::Network(msg) => Error::internal_error(format!("Network: {msg}")),
126 CommonError::Timeout(msg) => Error::internal_error(format!("Timeout: {msg}")),
127 CommonError::NotFound(msg) => Error::resource_not_found(msg),
128 CommonError::PermissionDenied(msg) => Error::forbidden(msg),
129 CommonError::RateLimit(msg) => Error::rate_limit_exceeded(msg),
130 CommonError::Internal(msg) => Error::internal_error(msg),
131 CommonError::Custom(msg) => Error::internal_error(msg),
132 }
133 }
134}
135
136pub type CommonResult<T> = Result<T, CommonError>;
138
139pub trait BackendErrorExt {
141 fn backend_error(self, context: &str) -> CommonError;
143}
144
145impl<E: std::error::Error> BackendErrorExt for E {
146 fn backend_error(self, context: &str) -> CommonError {
147 CommonError::Internal(format!("{context}: {self}"))
148 }
149}
150
151#[macro_export]
153macro_rules! mcp_error {
154 (parse $msg:expr) => {
155 $crate::Error::parse_error($msg)
156 };
157 (invalid_request $msg:expr) => {
158 $crate::Error::invalid_request($msg)
159 };
160 (method_not_found $method:expr) => {
161 $crate::Error::method_not_found($method)
162 };
163 (invalid_params $msg:expr) => {
164 $crate::Error::invalid_params($msg)
165 };
166 (internal $msg:expr) => {
167 $crate::Error::internal_error($msg)
168 };
169 (unauthorized $msg:expr) => {
170 $crate::Error::unauthorized($msg)
171 };
172 (forbidden $msg:expr) => {
173 $crate::Error::forbidden($msg)
174 };
175 (not_found $resource:expr) => {
176 $crate::Error::resource_not_found($resource)
177 };
178 (tool_not_found $tool:expr) => {
179 $crate::Error::tool_not_found($tool)
180 };
181 (validation $msg:expr) => {
182 $crate::Error::validation_error($msg)
183 };
184 (rate_limit $msg:expr) => {
185 $crate::Error::rate_limit_exceeded($msg)
186 };
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192 use std::io;
193
194 #[test]
195 fn test_error_context() {
196 let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
197 let result: Result<(), _> = Err(io_error);
198
199 let mcp_error = result.context("Failed to read configuration").unwrap_err();
200 assert!(mcp_error.message.contains("Failed to read configuration"));
201 assert!(mcp_error.message.contains("file not found"));
202 }
203
204 #[test]
205 fn test_common_error_conversion() {
206 let common_error = CommonError::Auth("invalid token".to_string());
207 let mcp_error: Error = common_error.into();
208
209 assert_eq!(mcp_error.code, ErrorCode::Unauthorized);
210 assert_eq!(mcp_error.message, "invalid token");
211 }
212
213 #[test]
214 fn test_error_macro() {
215 let error = mcp_error!(validation "invalid input");
216 assert_eq!(error.code, ErrorCode::ValidationError);
217 assert_eq!(error.message, "invalid input");
218 }
219}