turul_http_mcp_server/middleware/
error.rs

1//! Middleware error types
2
3use std::fmt;
4
5/// JSON-RPC 2.0 error codes for middleware errors
6///
7/// These codes are used when converting `MiddlewareError` to `JsonRpcError`.
8/// Codes `-32000` to `-32099` are reserved for application-defined server errors.
9pub mod error_codes {
10    /// Authentication required (-32001)
11    pub const UNAUTHENTICATED: i64 = -32001;
12    /// Permission denied (-32002)
13    pub const UNAUTHORIZED: i64 = -32002;
14    /// Rate limit exceeded (-32003)
15    pub const RATE_LIMIT_EXCEEDED: i64 = -32003;
16    /// Invalid request (standard JSON-RPC error)
17    pub const INVALID_REQUEST: i64 = -32600;
18    /// Internal error (standard JSON-RPC error)
19    pub const INTERNAL_ERROR: i64 = -32603;
20}
21
22/// Errors that can occur during middleware execution
23///
24/// These errors are converted to `McpError` by the framework and then to
25/// JSON-RPC error responses. Middleware should use semantic error types
26/// rather than creating JSON-RPC errors directly.
27///
28/// # Conversion Chain
29///
30/// ```text
31/// MiddlewareError → McpError → JsonRpcError → HTTP/Lambda response
32/// ```
33///
34/// # JSON-RPC Error Codes
35///
36/// Each error variant maps to a specific JSON-RPC error code (see [`error_codes`]):
37///
38/// - `Unauthenticated` → `-32001` "Authentication required"
39/// - `Unauthorized` → `-32002` "Permission denied"
40/// - `RateLimitExceeded` → `-32003` "Rate limit exceeded"
41/// - `InvalidRequest` → `-32600` (standard Invalid Request)
42/// - `Internal` → `-32603` (standard Internal error)
43/// - `Custom{code, msg}` → custom code from variant
44///
45/// # Examples
46///
47/// ```rust,no_run
48/// use turul_http_mcp_server::middleware::{MiddlewareError, McpMiddleware, RequestContext, SessionInjection};
49/// use turul_mcp_session_storage::SessionView;
50/// use async_trait::async_trait;
51///
52/// struct ApiKeyAuth {
53///     valid_key: String,
54/// }
55///
56/// #[async_trait]
57/// impl McpMiddleware for ApiKeyAuth {
58///     async fn before_dispatch(
59///         &self,
60///         ctx: &mut RequestContext<'_>,
61///         _session: Option<&dyn SessionView>,
62///         _injection: &mut SessionInjection,
63///     ) -> Result<(), MiddlewareError> {
64///         let key = ctx.metadata()
65///             .get("api-key")
66///             .and_then(|v| v.as_str())
67///             .ok_or_else(|| MiddlewareError::Unauthorized("Missing API key".into()))?;
68///
69///         if key != self.valid_key {
70///             return Err(MiddlewareError::Unauthorized("Invalid API key".into()));
71///         }
72///
73///         Ok(())
74///     }
75/// }
76/// ```
77#[derive(Debug, Clone, PartialEq)]
78pub enum MiddlewareError {
79    /// Authentication required but not provided
80    Unauthenticated(String),
81
82    /// Authentication provided but insufficient permissions
83    Unauthorized(String),
84
85    /// Rate limit exceeded
86    RateLimitExceeded {
87        /// Human-readable message
88        message: String,
89        /// Seconds until limit resets
90        retry_after: Option<u64>,
91    },
92
93    /// Request validation failed
94    InvalidRequest(String),
95
96    /// Internal middleware error (should not expose to client)
97    Internal(String),
98
99    /// Custom error with code and message
100    Custom {
101        /// Error code (for structured error handling)
102        code: String,
103        /// Human-readable message
104        message: String,
105    },
106}
107
108impl fmt::Display for MiddlewareError {
109    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110        match self {
111            Self::Unauthenticated(msg) => write!(f, "Authentication required: {}", msg),
112            Self::Unauthorized(msg) => write!(f, "Unauthorized: {}", msg),
113            Self::RateLimitExceeded { message, retry_after } => {
114                if let Some(seconds) = retry_after {
115                    write!(f, "{} (retry after {} seconds)", message, seconds)
116                } else {
117                    write!(f, "{}", message)
118                }
119            }
120            Self::InvalidRequest(msg) => write!(f, "Invalid request: {}", msg),
121            Self::Internal(msg) => write!(f, "Internal middleware error: {}", msg),
122            Self::Custom { code, message } => write!(f, "{}: {}", code, message),
123        }
124    }
125}
126
127impl std::error::Error for MiddlewareError {}
128
129impl MiddlewareError {
130    /// Create an unauthenticated error
131    pub fn unauthenticated(msg: impl Into<String>) -> Self {
132        Self::Unauthenticated(msg.into())
133    }
134
135    /// Create an unauthorized error
136    pub fn unauthorized(msg: impl Into<String>) -> Self {
137        Self::Unauthorized(msg.into())
138    }
139
140    /// Create a rate limit error
141    pub fn rate_limit(msg: impl Into<String>, retry_after: Option<u64>) -> Self {
142        Self::RateLimitExceeded {
143            message: msg.into(),
144            retry_after,
145        }
146    }
147
148    /// Create an invalid request error
149    pub fn invalid_request(msg: impl Into<String>) -> Self {
150        Self::InvalidRequest(msg.into())
151    }
152
153    /// Create an internal error
154    pub fn internal(msg: impl Into<String>) -> Self {
155        Self::Internal(msg.into())
156    }
157
158    /// Create a custom error
159    pub fn custom(code: impl Into<String>, message: impl Into<String>) -> Self {
160        Self::Custom {
161            code: code.into(),
162            message: message.into(),
163        }
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170
171    #[test]
172    fn test_error_display() {
173        let err = MiddlewareError::unauthenticated("Missing token");
174        assert_eq!(err.to_string(), "Authentication required: Missing token");
175
176        let err = MiddlewareError::unauthorized("Insufficient permissions");
177        assert_eq!(err.to_string(), "Unauthorized: Insufficient permissions");
178
179        let err = MiddlewareError::rate_limit("Too many requests", Some(60));
180        assert_eq!(err.to_string(), "Too many requests (retry after 60 seconds)");
181
182        let err = MiddlewareError::rate_limit("Too many requests", None);
183        assert_eq!(err.to_string(), "Too many requests");
184
185        let err = MiddlewareError::invalid_request("Malformed params");
186        assert_eq!(err.to_string(), "Invalid request: Malformed params");
187
188        let err = MiddlewareError::internal("Database connection failed");
189        assert_eq!(err.to_string(), "Internal middleware error: Database connection failed");
190
191        let err = MiddlewareError::custom("CUSTOM_ERROR", "Something went wrong");
192        assert_eq!(err.to_string(), "CUSTOM_ERROR: Something went wrong");
193    }
194
195    #[test]
196    fn test_error_equality() {
197        let err1 = MiddlewareError::unauthenticated("test");
198        let err2 = MiddlewareError::unauthenticated("test");
199        assert_eq!(err1, err2);
200
201        let err3 = MiddlewareError::rate_limit("test", Some(60));
202        let err4 = MiddlewareError::rate_limit("test", Some(60));
203        assert_eq!(err3, err4);
204    }
205}