use std::fmt;
pub mod error_codes {
pub const UNAUTHENTICATED: i64 = -32001;
pub const UNAUTHORIZED: i64 = -32002;
pub const RATE_LIMIT_EXCEEDED: i64 = -32003;
pub const INVALID_REQUEST: i64 = -32600;
pub const INTERNAL_ERROR: i64 = -32603;
}
#[derive(Debug, Clone, PartialEq)]
pub enum MiddlewareError {
Unauthenticated(String),
Unauthorized(String),
RateLimitExceeded {
message: String,
retry_after: Option<u64>,
},
InvalidRequest(String),
Internal(String),
Custom {
code: String,
message: String,
},
HttpChallenge {
status: u16,
www_authenticate: String,
body: Option<String>,
},
}
impl fmt::Display for MiddlewareError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Unauthenticated(msg) => write!(f, "Authentication required: {}", msg),
Self::Unauthorized(msg) => write!(f, "Unauthorized: {}", msg),
Self::RateLimitExceeded {
message,
retry_after,
} => {
if let Some(seconds) = retry_after {
write!(f, "{} (retry after {} seconds)", message, seconds)
} else {
write!(f, "{}", message)
}
}
Self::InvalidRequest(msg) => write!(f, "Invalid request: {}", msg),
Self::Internal(msg) => write!(f, "Internal middleware error: {}", msg),
Self::Custom { code, message } => write!(f, "{}: {}", code, message),
Self::HttpChallenge {
status,
www_authenticate,
..
} => write!(f, "HTTP {} WWW-Authenticate: {}", status, www_authenticate),
}
}
}
impl std::error::Error for MiddlewareError {}
impl MiddlewareError {
pub fn unauthenticated(msg: impl Into<String>) -> Self {
Self::Unauthenticated(msg.into())
}
pub fn unauthorized(msg: impl Into<String>) -> Self {
Self::Unauthorized(msg.into())
}
pub fn rate_limit(msg: impl Into<String>, retry_after: Option<u64>) -> Self {
Self::RateLimitExceeded {
message: msg.into(),
retry_after,
}
}
pub fn invalid_request(msg: impl Into<String>) -> Self {
Self::InvalidRequest(msg.into())
}
pub fn internal(msg: impl Into<String>) -> Self {
Self::Internal(msg.into())
}
pub fn custom(code: impl Into<String>, message: impl Into<String>) -> Self {
Self::Custom {
code: code.into(),
message: message.into(),
}
}
pub fn http_challenge(status: u16, www_authenticate: impl Into<String>) -> Self {
Self::HttpChallenge {
status,
www_authenticate: www_authenticate.into(),
body: None,
}
}
pub fn http_challenge_with_body(
status: u16,
www_authenticate: impl Into<String>,
body: impl Into<String>,
) -> Self {
Self::HttpChallenge {
status,
www_authenticate: www_authenticate.into(),
body: Some(body.into()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = MiddlewareError::unauthenticated("Missing token");
assert_eq!(err.to_string(), "Authentication required: Missing token");
let err = MiddlewareError::unauthorized("Insufficient permissions");
assert_eq!(err.to_string(), "Unauthorized: Insufficient permissions");
let err = MiddlewareError::rate_limit("Too many requests", Some(60));
assert_eq!(
err.to_string(),
"Too many requests (retry after 60 seconds)"
);
let err = MiddlewareError::rate_limit("Too many requests", None);
assert_eq!(err.to_string(), "Too many requests");
let err = MiddlewareError::invalid_request("Malformed params");
assert_eq!(err.to_string(), "Invalid request: Malformed params");
let err = MiddlewareError::internal("Database connection failed");
assert_eq!(
err.to_string(),
"Internal middleware error: Database connection failed"
);
let err = MiddlewareError::custom("CUSTOM_ERROR", "Something went wrong");
assert_eq!(err.to_string(), "CUSTOM_ERROR: Something went wrong");
}
#[test]
fn test_error_equality() {
let err1 = MiddlewareError::unauthenticated("test");
let err2 = MiddlewareError::unauthenticated("test");
assert_eq!(err1, err2);
let err3 = MiddlewareError::rate_limit("test", Some(60));
let err4 = MiddlewareError::rate_limit("test", Some(60));
assert_eq!(err3, err4);
}
#[test]
fn test_http_challenge_variant_display() {
let err = MiddlewareError::http_challenge(401, "Bearer realm=\"mcp\"");
assert_eq!(
err.to_string(),
"HTTP 401 WWW-Authenticate: Bearer realm=\"mcp\""
);
let err = MiddlewareError::http_challenge(403, "Bearer error=\"insufficient_scope\"");
assert_eq!(
err.to_string(),
"HTTP 403 WWW-Authenticate: Bearer error=\"insufficient_scope\""
);
}
#[test]
fn test_http_challenge_constructor() {
let err = MiddlewareError::http_challenge(401, "Bearer realm=\"mcp\"");
match &err {
MiddlewareError::HttpChallenge {
status,
www_authenticate,
body,
} => {
assert_eq!(*status, 401);
assert_eq!(www_authenticate, "Bearer realm=\"mcp\"");
assert!(body.is_none());
}
_ => panic!("Expected HttpChallenge variant"),
}
let err_with_body = MiddlewareError::http_challenge_with_body(
401,
"Bearer realm=\"mcp\"",
r#"{"error":"unauthorized"}"#,
);
match &err_with_body {
MiddlewareError::HttpChallenge {
status,
www_authenticate,
body,
} => {
assert_eq!(*status, 401);
assert_eq!(www_authenticate, "Bearer realm=\"mcp\"");
assert_eq!(body.as_deref(), Some(r#"{"error":"unauthorized"}"#));
}
_ => panic!("Expected HttpChallenge variant"),
}
}
#[test]
fn test_http_challenge_roundtrip_equality() {
let err1 = MiddlewareError::http_challenge(401, "Bearer realm=\"mcp\"");
let err2 = MiddlewareError::http_challenge(401, "Bearer realm=\"mcp\"");
assert_eq!(err1, err2);
let err3 = MiddlewareError::http_challenge(401, "Bearer realm=\"mcp\"");
let err4 = MiddlewareError::http_challenge(403, "Bearer realm=\"mcp\"");
assert_ne!(err3, err4);
}
}