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