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}