pulseengine_mcp_protocol/
errors.rs

1//! Error harmonization and convenience utilities
2//!
3//! This module provides a unified approach to error handling across the PulseEngine MCP framework.
4//! It includes common error types, conversion utilities, and patterns that make it easier for
5//! backend implementers and framework users to handle errors consistently.
6
7pub use crate::error::{Error, ErrorCode, McpResult};
8
9/// Common error handling prelude
10///
11/// Import this to get access to the most commonly used error types and utilities:
12///
13/// ```rust,ignore
14/// use pulseengine_mcp_protocol::errors::prelude::*;
15/// ```
16pub mod prelude {
17    pub use super::{BackendErrorExt, CommonError, CommonResult, ErrorContext, ErrorContextExt};
18    pub use super::{Error, ErrorCode, McpResult};
19}
20
21/// Extension trait for adding context to errors
22pub trait ErrorContext<T> {
23    /// Add context to an error
24    fn with_context<F>(self, f: F) -> McpResult<T>
25    where
26        F: FnOnce() -> String;
27
28    /// Add context to an error with a static string
29    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
48/// Extension trait for converting errors into standard error contexts
49pub trait ErrorContextExt<T> {
50    /// Convert to internal error
51    fn internal_error(self) -> McpResult<T>;
52
53    /// Convert to validation error
54    fn validation_error(self) -> McpResult<T>;
55
56    /// Convert to invalid params error
57    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/// Common error types that backend implementers often need
78#[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
136/// Common result type for backend implementations
137pub type CommonResult<T> = Result<T, CommonError>;
138
139/// Extension trait for backend error handling
140pub trait BackendErrorExt {
141    /// Convert any error to a backend-friendly error
142    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 for quick error creation
152#[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}