remote_mcp_kernel/
error.rs1use axum::{
8 Json,
9 http::StatusCode,
10 response::{IntoResponse, Response},
11};
12use serde_json::json;
13
14#[derive(Debug, thiserror::Error)]
16pub enum AppError {
17 #[error("Configuration error: {0}")]
18 Config(#[from] crate::config::ConfigError),
19
20 #[error("OAuth error: {0}")]
21 OAuth(String),
22
23 #[error("GitHub API error: {0}")]
24 GitHub(String),
25
26 #[error("Token validation error: {0}")]
27 TokenValidation(String),
28
29 #[error("Authorization error: {0}")]
30 Authorization(String),
31
32 #[error("HTTP client error: {0}")]
33 HttpClient(#[from] reqwest::Error),
34
35 #[error("JSON serialization error: {0}")]
36 Json(#[from] serde_json::Error),
37
38 #[error("Internal server error: {0}")]
39 Internal(String),
40}
41
42impl AppError {
43 pub fn status_code(&self) -> StatusCode {
45 match self {
46 AppError::Config(_) => StatusCode::INTERNAL_SERVER_ERROR,
47 AppError::OAuth(_) => StatusCode::BAD_REQUEST,
48 AppError::GitHub(_) => StatusCode::BAD_GATEWAY,
49 AppError::TokenValidation(_) => StatusCode::UNAUTHORIZED,
50 AppError::Authorization(_) => StatusCode::UNAUTHORIZED,
51 AppError::HttpClient(_) => StatusCode::BAD_GATEWAY,
52 AppError::Json(_) => StatusCode::BAD_REQUEST,
53 AppError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
54 }
55 }
56
57 pub fn error_code(&self) -> &'static str {
59 match self {
60 AppError::Config(_) => "configuration_error",
61 AppError::OAuth(_) => "oauth_error",
62 AppError::GitHub(_) => "github_api_error",
63 AppError::TokenValidation(_) => "invalid_token",
64 AppError::Authorization(_) => "authorization_failed",
65 AppError::HttpClient(_) => "http_client_error",
66 AppError::Json(_) => "json_error",
67 AppError::Internal(_) => "internal_error",
68 }
69 }
70
71 pub fn error_response(&self) -> Json<serde_json::Value> {
73 Json(json!({
74 "error": self.error_code(),
75 "description": self.to_string(),
76 "status": self.status_code().as_u16()
77 }))
78 }
79}
80
81impl IntoResponse for AppError {
82 fn into_response(self) -> Response {
83 let status = self.status_code();
84 let body = self.error_response();
85
86 tracing::error!("Application error: {} (status: {})", self, status);
87
88 (status, body).into_response()
89 }
90}
91
92#[derive(Debug, thiserror::Error)]
94pub enum AuthError {
95 #[error("Missing authorization header")]
96 MissingAuthHeader,
97
98 #[error("Invalid authorization header format")]
99 InvalidAuthFormat,
100
101 #[error("Invalid authorization header encoding")]
102 InvalidAuthEncoding,
103
104 #[error("Missing bearer token")]
105 MissingToken,
106
107 #[error("Empty bearer token")]
108 EmptyToken,
109
110 #[error("Invalid token")]
111 InvalidToken,
112
113 #[error("Token expired")]
114 TokenExpired,
115
116 #[error("Insufficient token scope")]
117 InsufficientScope,
118}
119
120impl AuthError {
121 pub fn into_app_error(self) -> AppError {
123 AppError::TokenValidation(self.to_string())
124 }
125
126 pub fn error_response(&self) -> Json<serde_json::Value> {
128 Json(json!({
129 "error": self.error_code(),
130 "description": self.to_string()
131 }))
132 }
133
134 pub fn error_code(&self) -> &'static str {
136 match self {
137 AuthError::MissingAuthHeader => "missing_authorization",
138 AuthError::InvalidAuthFormat => "invalid_authorization_format",
139 AuthError::InvalidAuthEncoding => "invalid_authorization_header",
140 AuthError::MissingToken => "missing_token",
141 AuthError::EmptyToken => "empty_token",
142 AuthError::InvalidToken => "invalid_token",
143 AuthError::TokenExpired => "token_expired",
144 AuthError::InsufficientScope => "insufficient_scope",
145 }
146 }
147}
148
149impl IntoResponse for AuthError {
150 fn into_response(self) -> Response {
151 let body = self.error_response();
152 (StatusCode::UNAUTHORIZED, body).into_response()
153 }
154}
155
156pub type AppResult<T> = Result<T, AppError>;
158
159pub type AuthResult<T> = Result<T, AuthError>;
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 #[test]
167 fn test_app_error_status_codes() {
168 assert_eq!(
169 AppError::OAuth("test".to_string()).status_code(),
170 StatusCode::BAD_REQUEST
171 );
172 assert_eq!(
173 AppError::TokenValidation("test".to_string()).status_code(),
174 StatusCode::UNAUTHORIZED
175 );
176 assert_eq!(
177 AppError::Internal("test".to_string()).status_code(),
178 StatusCode::INTERNAL_SERVER_ERROR
179 );
180 }
181
182 #[test]
183 fn test_auth_error_codes() {
184 assert_eq!(
185 AuthError::MissingAuthHeader.error_code(),
186 "missing_authorization"
187 );
188 assert_eq!(AuthError::InvalidToken.error_code(), "invalid_token");
189 assert_eq!(AuthError::TokenExpired.error_code(), "token_expired");
190 }
191
192 #[test]
193 fn test_error_response_format() {
194 let error = AppError::OAuth("test error".to_string());
195 let response = error.error_response();
196
197 let value = response.0;
198 assert_eq!(value["error"], "oauth_error");
199 assert_eq!(value["description"], "OAuth error: test error");
200 assert_eq!(value["status"], 400);
201 }
202}