Skip to main content

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 {
114                message,
115                retry_after,
116            } => {
117                if let Some(seconds) = retry_after {
118                    write!(f, "{} (retry after {} seconds)", message, seconds)
119                } else {
120                    write!(f, "{}", message)
121                }
122            }
123            Self::InvalidRequest(msg) => write!(f, "Invalid request: {}", msg),
124            Self::Internal(msg) => write!(f, "Internal middleware error: {}", msg),
125            Self::Custom { code, message } => write!(f, "{}: {}", code, message),
126        }
127    }
128}
129
130impl std::error::Error for MiddlewareError {}
131
132impl MiddlewareError {
133    /// Create an unauthenticated error
134    pub fn unauthenticated(msg: impl Into<String>) -> Self {
135        Self::Unauthenticated(msg.into())
136    }
137
138    /// Create an unauthorized error
139    pub fn unauthorized(msg: impl Into<String>) -> Self {
140        Self::Unauthorized(msg.into())
141    }
142
143    /// Create a rate limit error
144    pub fn rate_limit(msg: impl Into<String>, retry_after: Option<u64>) -> Self {
145        Self::RateLimitExceeded {
146            message: msg.into(),
147            retry_after,
148        }
149    }
150
151    /// Create an invalid request error
152    pub fn invalid_request(msg: impl Into<String>) -> Self {
153        Self::InvalidRequest(msg.into())
154    }
155
156    /// Create an internal error
157    pub fn internal(msg: impl Into<String>) -> Self {
158        Self::Internal(msg.into())
159    }
160
161    /// Create a custom error
162    pub fn custom(code: impl Into<String>, message: impl Into<String>) -> Self {
163        Self::Custom {
164            code: code.into(),
165            message: message.into(),
166        }
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173
174    #[test]
175    fn test_error_display() {
176        let err = MiddlewareError::unauthenticated("Missing token");
177        assert_eq!(err.to_string(), "Authentication required: Missing token");
178
179        let err = MiddlewareError::unauthorized("Insufficient permissions");
180        assert_eq!(err.to_string(), "Unauthorized: Insufficient permissions");
181
182        let err = MiddlewareError::rate_limit("Too many requests", Some(60));
183        assert_eq!(
184            err.to_string(),
185            "Too many requests (retry after 60 seconds)"
186        );
187
188        let err = MiddlewareError::rate_limit("Too many requests", None);
189        assert_eq!(err.to_string(), "Too many requests");
190
191        let err = MiddlewareError::invalid_request("Malformed params");
192        assert_eq!(err.to_string(), "Invalid request: Malformed params");
193
194        let err = MiddlewareError::internal("Database connection failed");
195        assert_eq!(
196            err.to_string(),
197            "Internal middleware error: Database connection failed"
198        );
199
200        let err = MiddlewareError::custom("CUSTOM_ERROR", "Something went wrong");
201        assert_eq!(err.to_string(), "CUSTOM_ERROR: Something went wrong");
202    }
203
204    #[test]
205    fn test_error_equality() {
206        let err1 = MiddlewareError::unauthenticated("test");
207        let err2 = MiddlewareError::unauthenticated("test");
208        assert_eq!(err1, err2);
209
210        let err3 = MiddlewareError::rate_limit("test", Some(60));
211        let err4 = MiddlewareError::rate_limit("test", Some(60));
212        assert_eq!(err3, err4);
213    }
214}