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