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