1use axum::{
2 Json,
3 http::StatusCode,
4 response::{IntoResponse, Response},
5};
6use serde::{Deserialize, Serialize};
7use std::backtrace::Backtrace;
8use std::env;
9use std::fmt;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct ErrorResponse {
14 pub error: String,
16
17 pub message: String,
19
20 pub details: Option<String>,
22}
23
24#[derive(Debug)]
30pub enum NovaError {
31 DatabaseError(String),
33
34 ValidationError(String),
36
37 AuthenticationError(String),
39
40 AuthorizationError(String),
42
43 NotFound(String),
45
46 Conflict(String),
48
49 InternalError(String),
51
52 BadRequest(String),
54
55 Custom {
57 status: StatusCode,
58 error: String,
59 message: String,
60 },
61}
62
63impl fmt::Display for NovaError {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 match self {
66 NovaError::DatabaseError(msg) => write!(f, "Database error: {}", msg),
67 NovaError::ValidationError(msg) => write!(f, "Validation error: {}", msg),
68 NovaError::AuthenticationError(msg) => write!(f, "Authentication error: {}", msg),
69 NovaError::AuthorizationError(msg) => write!(f, "Authorization error: {}", msg),
70 NovaError::NotFound(msg) => write!(f, "Not found: {}", msg),
71 NovaError::Conflict(msg) => write!(f, "Conflict: {}", msg),
72 NovaError::InternalError(msg) => write!(f, "Internal error: {}", msg),
73 NovaError::BadRequest(msg) => write!(f, "Bad request: {}", msg),
74 NovaError::Custom { message, .. } => write!(f, "{}", message),
75 }
76 }
77}
78
79impl std::error::Error for NovaError {}
80
81#[derive(Debug, Clone)]
83pub enum DiscoveryError {
84 NotFound(String),
85 Backend(String),
86 ConnectionFailed(String),
87}
88
89impl fmt::Display for DiscoveryError {
90 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 match self {
92 Self::NotFound(msg) => write!(f, "Discovery not found: {}", msg),
93 Self::Backend(msg) => write!(f, "Discovery backend error: {}", msg),
94 Self::ConnectionFailed(msg) => write!(f, "Discovery connection failed: {}", msg),
95 }
96 }
97}
98
99impl std::error::Error for DiscoveryError {}
100
101impl NovaError {
102 pub fn status_code(&self) -> StatusCode {
104 match self {
105 NovaError::DatabaseError(_) => StatusCode::INTERNAL_SERVER_ERROR,
106 NovaError::ValidationError(_) => StatusCode::BAD_REQUEST,
107 NovaError::AuthenticationError(_) => StatusCode::UNAUTHORIZED,
108 NovaError::AuthorizationError(_) => StatusCode::FORBIDDEN,
109 NovaError::NotFound(_) => StatusCode::NOT_FOUND,
110 NovaError::Conflict(_) => StatusCode::CONFLICT,
111 NovaError::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR,
112 NovaError::BadRequest(_) => StatusCode::BAD_REQUEST,
113 NovaError::Custom { status, .. } => *status,
114 }
115 }
116
117 pub fn error_type(&self) -> &str {
119 match self {
120 NovaError::DatabaseError(_) => "DatabaseError",
121 NovaError::ValidationError(_) => "ValidationError",
122 NovaError::AuthenticationError(_) => "AuthenticationError",
123 NovaError::AuthorizationError(_) => "AuthorizationError",
124 NovaError::NotFound(_) => "NotFound",
125 NovaError::Conflict(_) => "Conflict",
126 NovaError::InternalError(_) => "InternalError",
127 NovaError::BadRequest(_) => "BadRequest",
128 NovaError::Custom { error, .. } => error,
129 }
130 }
131
132 pub fn message(&self) -> String {
134 match self {
135 NovaError::Custom { message, .. } => message.clone(),
136 err => err.to_string(),
137 }
138 }
139}
140
141impl IntoResponse for NovaError {
142 fn into_response(self) -> Response {
143 let status = self.status_code();
144 let details = match env::var("NOVA_DEBUG") {
149 Ok(val) if val.eq_ignore_ascii_case("true") || val == "1" => {
150 Some(format!("{:?}", Backtrace::capture()))
151 }
152 _ => None,
153 };
154
155 let body = Json(ErrorResponse {
156 error: self.error_type().to_string(),
157 message: self.message(),
158 details,
159 });
160
161 (status, body).into_response()
162 }
163}
164
165pub type NovaResult<T> = Result<T, NovaError>;
167
168impl From<std::io::Error> for NovaError {
170 fn from(err: std::io::Error) -> Self {
171 NovaError::InternalError(err.to_string())
172 }
173}
174
175impl From<serde_json::Error> for NovaError {
176 fn from(err: serde_json::Error) -> Self {
177 NovaError::BadRequest(format!("Invalid JSON: {}", err))
178 }
179}
180
181impl From<nova_boot_resilience_store::ResilienceError> for NovaError {
182 fn from(err: nova_boot_resilience_store::ResilienceError) -> Self {
183 NovaError::InternalError(err.to_string())
184 }
185}
186
187#[cfg(feature = "database")]
188impl From<sea_orm::DbErr> for NovaError {
189 fn from(err: sea_orm::DbErr) -> Self {
190 NovaError::DatabaseError(err.to_string())
191 }
192}