1use axum::{Json, http::StatusCode, response::IntoResponse};
7use tracing::warn;
8
9use crate::types::ErrorResponse;
10
11pub type ClResult<T> = std::result::Result<T, Error>;
12
13#[derive(Debug)]
14pub enum Error {
15 NotFound,
17 Gone,
22 PermissionDenied,
23 Unauthorized, DbError,
25 Parse,
26
27 ValidationError(String), Conflict(String), PreconditionRequired(String), SettingNotFound(String),
37
38 NetworkError(String), Timeout, ConfigError(String), ServiceUnavailable(String), Internal(String), ImageError(String), CryptoError(String), Io(std::io::Error),
53}
54
55impl From<std::io::Error> for Error {
56 fn from(err: std::io::Error) -> Self {
57 warn!("io error: {}", err);
58 Self::Io(err)
59 }
60}
61
62impl std::fmt::Display for Error {
63 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
64 write!(f, "{:?}", self)
65 }
66}
67
68impl std::error::Error for Error {}
69
70impl IntoResponse for Error {
71 fn into_response(self) -> axum::response::Response {
72 let (status, code, message) = match self {
73 Error::NotFound => (
74 StatusCode::NOT_FOUND,
75 "E-CORE-NOTFOUND".to_string(),
76 "Resource not found".to_string(),
77 ),
78 Error::Gone => {
79 (StatusCode::GONE, "E-CORE-GONE".to_string(), "Resource is gone".to_string())
80 }
81 Error::PermissionDenied => (
82 StatusCode::FORBIDDEN,
83 "E-AUTH-NOPERM".to_string(),
84 "You do not have permission to access this resource".to_string(),
85 ),
86 Error::Unauthorized => (
87 StatusCode::UNAUTHORIZED,
88 "E-AUTH-UNAUTH".to_string(),
89 "Authentication required or invalid token".to_string(),
90 ),
91 Error::ValidationError(msg) => (
92 StatusCode::BAD_REQUEST,
93 "E-VAL-INVALID".to_string(),
94 format!("Request validation failed: {}", msg),
95 ),
96 Error::Conflict(msg) => (
97 StatusCode::CONFLICT,
98 "E-CORE-CONFLICT".to_string(),
99 format!("Resource conflict: {}", msg),
100 ),
101 Error::PreconditionRequired(msg) => (
102 StatusCode::PRECONDITION_REQUIRED,
103 "E-POW-REQUIRED".to_string(),
104 format!("Precondition required: {}", msg),
105 ),
106 Error::SettingNotFound(msg) => (
107 StatusCode::NOT_FOUND,
108 "E-SET-NOTFOUND".to_string(),
109 format!("Setting not configured: {}", msg),
110 ),
111 Error::Timeout => (
112 StatusCode::REQUEST_TIMEOUT,
113 "E-NET-TIMEOUT".to_string(),
114 "Request timeout".to_string(),
115 ),
116 Error::ServiceUnavailable(msg) => (
117 StatusCode::SERVICE_UNAVAILABLE,
118 "E-SYS-UNAVAIL".to_string(),
119 format!("Service temporarily unavailable: {}", msg),
120 ),
121 Error::DbError => (
123 StatusCode::INTERNAL_SERVER_ERROR,
124 "E-CORE-DBERR".to_string(),
125 "Internal server error".to_string(),
126 ),
127 Error::Internal(msg) => {
128 warn!("internal error: {}", msg);
129 (
130 StatusCode::INTERNAL_SERVER_ERROR,
131 "E-CORE-INTERNAL".to_string(),
132 "Internal server error".to_string(),
133 )
134 }
135 Error::Parse => (
136 StatusCode::INTERNAL_SERVER_ERROR,
137 "E-CORE-PARSE".to_string(),
138 "Internal server error".to_string(),
139 ),
140 Error::Io(_) => (
141 StatusCode::INTERNAL_SERVER_ERROR,
142 "E-SYS-IO".to_string(),
143 "Internal server error".to_string(),
144 ),
145 Error::NetworkError(_) => (
146 StatusCode::INTERNAL_SERVER_ERROR,
147 "E-NET-ERROR".to_string(),
148 "Internal server error".to_string(),
149 ),
150 Error::ImageError(_) => (
151 StatusCode::INTERNAL_SERVER_ERROR,
152 "E-IMG-PROCFAIL".to_string(),
153 "Internal server error".to_string(),
154 ),
155 Error::CryptoError(_) => (
156 StatusCode::INTERNAL_SERVER_ERROR,
157 "E-CRYPT-FAIL".to_string(),
158 "Internal server error".to_string(),
159 ),
160 Error::ConfigError(_) => (
161 StatusCode::INTERNAL_SERVER_ERROR,
162 "E-CONF-CFGERR".to_string(),
163 "Internal server error".to_string(),
164 ),
165 };
166
167 let error_response = ErrorResponse::new(code, message);
168 (status, Json(error_response)).into_response()
169 }
170}
171
172impl From<std::num::ParseIntError> for Error {
173 fn from(err: std::num::ParseIntError) -> Self {
174 warn!("parse int error: {}", err);
175 Error::Parse
176 }
177}
178
179impl From<std::time::SystemTimeError> for Error {
180 fn from(err: std::time::SystemTimeError) -> Self {
181 warn!("system time error: {}", err);
182 Error::ServiceUnavailable("system time error".into())
183 }
184}
185
186impl From<axum::Error> for Error {
187 fn from(err: axum::Error) -> Self {
188 warn!("axum error: {}", err);
189 Error::NetworkError("axum error".into())
190 }
191}
192
193impl From<axum::http::Error> for Error {
194 fn from(err: axum::http::Error) -> Self {
195 warn!("http error: {}", err);
196 Error::NetworkError("http error".into())
197 }
198}
199
200impl From<axum::http::header::ToStrError> for Error {
201 fn from(err: axum::http::header::ToStrError) -> Self {
202 warn!("header to str error: {}", err);
203 Error::Parse
204 }
205}
206
207impl From<serde_json::Error> for Error {
208 fn from(err: serde_json::Error) -> Self {
209 warn!("json error: {}", err);
210 Error::Parse
211 }
212}
213
214impl From<tokio::task::JoinError> for Error {
215 fn from(err: tokio::task::JoinError) -> Self {
216 warn!("tokio join error: {}", err);
217 Error::ServiceUnavailable("task execution failed".into())
218 }
219}
220
221#[cfg(feature = "server")]
224impl From<instant_acme::Error> for Error {
225 fn from(err: instant_acme::Error) -> Self {
226 warn!("acme error: {}", err);
227 Error::ConfigError("ACME certificate error".into())
228 }
229}
230
231#[cfg(feature = "server")]
232impl From<pem::PemError> for Error {
233 fn from(err: pem::PemError) -> Self {
234 warn!("pem error: {}", err);
235 Error::CryptoError("PEM parsing error".into())
236 }
237}
238
239#[cfg(feature = "server")]
240impl From<jsonwebtoken::errors::Error> for Error {
241 fn from(err: jsonwebtoken::errors::Error) -> Self {
242 warn!("jwt error: {}", err);
243 Error::Unauthorized
244 }
245}
246
247#[cfg(feature = "server")]
248impl From<x509_parser::asn1_rs::Err<x509_parser::error::X509Error>> for Error {
249 fn from(err: x509_parser::asn1_rs::Err<x509_parser::error::X509Error>) -> Self {
250 warn!("x509 error: {}", err);
251 Error::CryptoError("X.509 certificate error".into())
252 }
253}
254
255#[cfg(feature = "server")]
256impl From<rustls::Error> for Error {
257 fn from(err: rustls::Error) -> Self {
258 warn!("rustls error: {}", err);
259 Error::CryptoError("TLS error".into())
260 }
261}
262
263#[cfg(feature = "server")]
264impl From<rustls_pki_types::pem::Error> for Error {
265 fn from(err: rustls_pki_types::pem::Error) -> Self {
266 warn!("pem error: {}", err);
267 Error::CryptoError("PEM parsing error".into())
268 }
269}
270
271#[cfg(feature = "server")]
272impl From<hyper::Error> for Error {
273 fn from(err: hyper::Error) -> Self {
274 warn!("hyper error: {}", err);
275 Error::NetworkError("HTTP client error".into())
276 }
277}
278
279#[cfg(feature = "server")]
280impl From<hyper_util::client::legacy::Error> for Error {
281 fn from(err: hyper_util::client::legacy::Error) -> Self {
282 warn!("hyper error: {}", err);
283 Error::NetworkError("HTTP client error".into())
284 }
285}
286
287#[cfg(feature = "server")]
288impl From<image::error::ImageError> for Error {
289 fn from(err: image::error::ImageError) -> Self {
290 warn!("image error: {:?}", err);
291 Error::ImageError("Image processing failed".into())
292 }
293}
294
295#[macro_export]
319macro_rules! lock {
320 ($mutex:expr) => {
322 $mutex
323 .lock()
324 .map_err(|_| $crate::error::Error::Internal("mutex poisoned".into()))
325 };
326 ($mutex:expr, $context:expr) => {
328 $mutex
329 .lock()
330 .map_err(|_| $crate::error::Error::Internal(format!("mutex poisoned: {}", $context)))
331 };
332}
333
334