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