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