reifydb_sub_server_http/
error.rs1use axum::{
10 Json,
11 http::StatusCode,
12 response::{IntoResponse, Response},
13};
14use reifydb_sub_server::{auth::AuthError, execute::ExecuteError};
15use reifydb_type::error::diagnostic::Diagnostic;
16use serde::Serialize;
17
18#[derive(Debug, Serialize)]
20pub struct ErrorResponse {
21 pub error: String,
23 pub code: String,
25}
26
27impl ErrorResponse {
28 pub fn new(code: impl Into<String>, error: impl Into<String>) -> Self {
29 Self {
30 code: code.into(),
31 error: error.into(),
32 }
33 }
34}
35
36#[derive(Debug, Serialize)]
38pub struct DiagnosticResponse {
39 pub diagnostic: Diagnostic,
41}
42
43#[derive(Debug)]
45pub enum AppError {
46 Auth(AuthError),
48 Execute(ExecuteError),
50 BadRequest(String),
52 Internal(String),
54}
55
56impl From<AuthError> for AppError {
57 fn from(e: AuthError) -> Self {
58 AppError::Auth(e)
59 }
60}
61
62impl From<ExecuteError> for AppError {
63 fn from(e: ExecuteError) -> Self {
64 AppError::Execute(e)
65 }
66}
67
68impl std::fmt::Display for AppError {
69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70 match self {
71 AppError::Auth(e) => write!(f, "Authentication error: {}", e),
72 AppError::Execute(e) => write!(f, "Execution error: {}", e),
73 AppError::BadRequest(msg) => write!(f, "Bad request: {}", msg),
74 AppError::Internal(msg) => write!(f, "Internal error: {}", msg),
75 }
76 }
77}
78
79impl std::error::Error for AppError {}
80
81impl IntoResponse for AppError {
82 fn into_response(self) -> Response {
83 if let AppError::Execute(ExecuteError::Engine {
85 diagnostic,
86 statement,
87 }) = self
88 {
89 tracing::debug!("Engine error: {}", diagnostic.message);
90 let mut diag = (*diagnostic).clone();
92 if diag.statement.is_none() && !statement.is_empty() {
93 diag.with_statement(statement);
94 }
95 let body = Json(DiagnosticResponse {
96 diagnostic: diag,
97 });
98 return (StatusCode::BAD_REQUEST, body).into_response();
99 }
100
101 let (status, code, message) = match &self {
102 AppError::Auth(AuthError::MissingCredentials) => {
103 (StatusCode::UNAUTHORIZED, "AUTH_REQUIRED", "Authentication required")
104 }
105 AppError::Auth(AuthError::InvalidToken) => {
106 (StatusCode::UNAUTHORIZED, "INVALID_TOKEN", "Invalid authentication token")
107 }
108 AppError::Auth(AuthError::Expired) => {
109 (StatusCode::UNAUTHORIZED, "TOKEN_EXPIRED", "Authentication token expired")
110 }
111 AppError::Auth(AuthError::InvalidHeader) => {
112 (StatusCode::BAD_REQUEST, "INVALID_HEADER", "Malformed authorization header")
113 }
114 AppError::Auth(AuthError::InsufficientPermissions) => {
115 (StatusCode::FORBIDDEN, "FORBIDDEN", "Insufficient permissions for this operation")
116 }
117 AppError::Execute(ExecuteError::Timeout) => {
118 (StatusCode::GATEWAY_TIMEOUT, "QUERY_TIMEOUT", "Query execution timed out")
119 }
120 AppError::Execute(ExecuteError::Cancelled) => {
121 (StatusCode::BAD_REQUEST, "QUERY_CANCELLED", "Query was cancelled")
122 }
123 AppError::Execute(ExecuteError::Disconnected) => {
124 tracing::error!("Query stream disconnected unexpectedly");
125 (StatusCode::INTERNAL_SERVER_ERROR, "INTERNAL_ERROR", "Internal server error")
126 }
127 AppError::Execute(ExecuteError::Engine {
128 ..
129 }) => {
130 unreachable!()
132 }
133 AppError::BadRequest(msg) => {
134 let body = Json(ErrorResponse::new("BAD_REQUEST", msg.clone()));
135 return (StatusCode::BAD_REQUEST, body).into_response();
136 }
137 AppError::Internal(msg) => {
138 tracing::error!("Internal error: {}", msg);
139 (StatusCode::INTERNAL_SERVER_ERROR, "INTERNAL_ERROR", "Internal server error")
140 }
141 };
142
143 let body = Json(ErrorResponse::new(code, message));
144 (status, body).into_response()
145 }
146}
147
148#[cfg(test)]
149pub mod tests {
150 use super::*;
151
152 #[test]
153 fn test_error_response_serialization() {
154 let resp = ErrorResponse::new("TEST_CODE", "Test error message");
155 let json = serde_json::to_string(&resp).unwrap();
156 assert!(json.contains("TEST_CODE"));
157 assert!(json.contains("Test error message"));
158 }
159
160 #[test]
161 fn test_app_error_display() {
162 let err = AppError::BadRequest("Invalid JSON".to_string());
163 assert_eq!(err.to_string(), "Bad request: Invalid JSON");
164 }
165}