1use serde::Serialize;
2use std::fmt::Display;
3use thiserror::Error;
4
5use super::ErrorCode;
6
7#[derive(Debug, Error, PartialEq, Clone, Serialize)]
10pub enum ConfErrReason {
11 #[error("core config > {0}")]
12 Core(String),
13 #[error("feature config error > {0}")]
14 Feature(String),
15 #[error("dynamic config error > {0}")]
16 Dynamic(String),
17}
18
19#[derive(Debug, Error, PartialEq, Clone, Serialize)]
32pub enum UvsReason {
33 #[error("validation error << {0}")]
36 ValidationError(ErrorPayload),
37
38 #[error("business logic error << {0}")]
40 BusinessError(ErrorPayload),
41
42 #[error("not found error << {0}")]
44 NotFoundError(ErrorPayload),
45
46 #[error("permission error << {0}")]
48 PermissionError(ErrorPayload),
49
50 #[error("data error << {0}")]
53 DataError(ErrorPayload, Option<usize>),
54
55 #[error("system error << {0}")]
57 SystemError(ErrorPayload),
58
59 #[error("network error << {0}")]
61 NetworkError(ErrorPayload),
62
63 #[error("resource error << {0}")]
65 ResourceError(ErrorPayload),
66
67 #[error("timeout error << {0}")]
69 TimeoutError(ErrorPayload),
70
71 #[error("configuration error << {0}")]
74 ConfigError(ConfErrReason),
75
76 #[error("external service error << {0}")]
78 ExternalError(ErrorPayload),
79
80 #[error("BUG :logic error << {0}")]
82 LogicError(ErrorPayload),
83}
84
85impl UvsReason {
86 pub fn core_conf<S: Into<String>>(msg: S) -> Self {
88 Self::ConfigError(ConfErrReason::Core(msg.into()))
89 }
90
91 pub fn feature_conf<S: Into<String>>(msg: S) -> Self {
92 Self::ConfigError(ConfErrReason::Feature(msg.into()))
93 }
94
95 pub fn dynamic_conf<S: Into<String>>(msg: S) -> Self {
96 Self::ConfigError(ConfErrReason::Dynamic(msg.into()))
97 }
98
99 pub fn validation_error<S: Into<String>>(msg: S) -> Self {
101 Self::ValidationError(ErrorPayload::new(msg))
102 }
103
104 pub fn business_error<S: Into<String>>(msg: S) -> Self {
105 Self::BusinessError(ErrorPayload::new(msg))
106 }
107
108 pub fn not_found_error<S: Into<String>>(msg: S) -> Self {
109 Self::NotFoundError(ErrorPayload::new(msg))
110 }
111
112 pub fn permission_error<S: Into<String>>(msg: S) -> Self {
113 Self::PermissionError(ErrorPayload::new(msg))
114 }
115
116 pub fn data_error<S: Into<String>>(msg: S, pos: Option<usize>) -> Self {
118 Self::DataError(ErrorPayload::new(msg), pos)
119 }
120
121 pub fn system_error<S: Into<String>>(msg: S) -> Self {
122 Self::SystemError(ErrorPayload::new(msg))
123 }
124
125 pub fn network_error<S: Into<String>>(msg: S) -> Self {
126 Self::NetworkError(ErrorPayload::new(msg))
127 }
128
129 pub fn resource_error<S: Into<String>>(msg: S) -> Self {
130 Self::ResourceError(ErrorPayload::new(msg))
131 }
132
133 pub fn timeout_error<S: Into<String>>(msg: S) -> Self {
134 Self::TimeoutError(ErrorPayload::new(msg))
135 }
136
137 pub fn external_error<S: Into<String>>(msg: S) -> Self {
139 Self::ExternalError(ErrorPayload::new(msg))
140 }
141 pub fn logic_error<S: Into<String>>(msg: S) -> Self {
142 Self::LogicError(ErrorPayload::new(msg))
143 }
144}
145
146pub trait UvsConfFrom<S> {
149 fn from_conf(info: S) -> Self;
150}
151
152pub trait UvsDataFrom<S> {
153 fn from_data(info: S, pos: Option<usize>) -> Self;
154}
155
156pub trait UvsSysFrom<S> {
157 fn from_sys(info: S) -> Self;
158}
159
160pub trait UvsBizFrom<S> {
161 fn from_biz(info: S) -> Self;
162}
163pub trait UvsLogicFrom<S> {
164 fn from_logic(info: S) -> Self;
165}
166
167pub trait UvsResFrom<S> {
168 fn from_res(info: S) -> Self;
169}
170
171pub trait UvsNetFrom<S> {
172 fn from_net(info: S) -> Self;
173}
174
175pub trait UvsTimeoutFrom<S> {
176 fn from_timeout(info: S) -> Self;
177}
178
179pub trait UvsValidationFrom<S> {
181 fn from_validation(info: S) -> Self;
182}
183
184pub trait UvsNotFoundFrom<S> {
185 fn from_not_found(info: S) -> Self;
186}
187
188pub trait UvsPermissionFrom<S> {
189 fn from_permission(info: S) -> Self;
190}
191
192pub trait UvsExternalFrom<S> {
193 fn from_external(info: S) -> Self;
194}
195
196#[derive(Debug, PartialEq, Clone, Serialize)]
199pub struct ErrorPayload(String);
200
201impl ErrorPayload {
202 pub fn new<S: Into<String>>(s: S) -> Self {
203 Self(s.into())
204 }
205
206 pub fn as_str(&self) -> &str {
207 &self.0
208 }
209
210 pub fn into_inner(self) -> String {
211 self.0
212 }
213}
214
215impl Display for ErrorPayload {
216 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217 write!(f, "{:?}", self.0)
218 }
219}
220
221impl From<String> for ErrorPayload {
222 fn from(value: String) -> Self {
223 Self::new(value)
224 }
225}
226
227impl From<&str> for ErrorPayload {
228 fn from(value: &str) -> Self {
229 Self::new(value.to_string())
230 }
231}
232
233impl<T> UvsConfFrom<String> for T
236where
237 T: From<UvsReason>,
238{
239 fn from_conf(reason: String) -> Self {
240 T::from(UvsReason::core_conf(reason))
241 }
242}
243
244impl<T> UvsConfFrom<ConfErrReason> for T
245where
246 T: From<UvsReason>,
247{
248 fn from_conf(reason: ConfErrReason) -> Self {
249 T::from(UvsReason::ConfigError(reason))
250 }
251}
252
253impl<T> UvsDataFrom<String> for T
254where
255 T: From<UvsReason>,
256{
257 fn from_data(info: String, pos: Option<usize>) -> Self {
258 T::from(UvsReason::data_error(info, pos))
259 }
260}
261
262impl<T> UvsSysFrom<String> for T
263where
264 T: From<UvsReason>,
265{
266 fn from_sys(info: String) -> Self {
267 T::from(UvsReason::system_error(info))
268 }
269}
270
271impl<T> UvsBizFrom<String> for T
272where
273 T: From<UvsReason>,
274{
275 fn from_biz(info: String) -> Self {
276 T::from(UvsReason::business_error(info))
277 }
278}
279
280impl<T> UvsResFrom<String> for T
281where
282 T: From<UvsReason>,
283{
284 fn from_res(info: String) -> Self {
285 T::from(UvsReason::resource_error(info))
286 }
287}
288
289impl<T> UvsNetFrom<String> for T
290where
291 T: From<UvsReason>,
292{
293 fn from_net(info: String) -> Self {
294 T::from(UvsReason::network_error(info)) }
296}
297
298impl<T> UvsTimeoutFrom<String> for T
299where
300 T: From<UvsReason>,
301{
302 fn from_timeout(info: String) -> Self {
303 T::from(UvsReason::timeout_error(info))
304 }
305}
306
307impl<T> UvsValidationFrom<String> for T
309where
310 T: From<UvsReason>,
311{
312 fn from_validation(info: String) -> Self {
313 T::from(UvsReason::validation_error(info))
314 }
315}
316
317impl<T> UvsNotFoundFrom<String> for T
318where
319 T: From<UvsReason>,
320{
321 fn from_not_found(info: String) -> Self {
322 T::from(UvsReason::not_found_error(info))
323 }
324}
325
326impl<T> UvsPermissionFrom<String> for T
327where
328 T: From<UvsReason>,
329{
330 fn from_permission(info: String) -> Self {
331 T::from(UvsReason::permission_error(info))
332 }
333}
334
335impl<T> UvsExternalFrom<String> for T
336where
337 T: From<UvsReason>,
338{
339 fn from_external(info: String) -> Self {
340 T::from(UvsReason::external_error(info))
341 }
342}
343
344impl<T> UvsLogicFrom<String> for T
345where
346 T: From<UvsReason>,
347{
348 fn from_logic(info: String) -> Self {
349 T::from(UvsReason::logic_error(info))
350 }
351}
352
353impl ErrorCode for UvsReason {
354 fn error_code(&self) -> i32 {
355 match self {
356 UvsReason::ValidationError(_) => 100,
358 UvsReason::BusinessError(_) => 101,
359 UvsReason::NotFoundError(_) => 102,
360 UvsReason::PermissionError(_) => 103,
361 UvsReason::LogicError(_) => 104,
362
363 UvsReason::DataError(_, _) => 200,
365 UvsReason::SystemError(_) => 201,
366 UvsReason::NetworkError(_) => 202,
367 UvsReason::ResourceError(_) => 203,
368 UvsReason::TimeoutError(_) => 204,
369
370 UvsReason::ConfigError(_) => 300,
372 UvsReason::ExternalError(_) => 301,
373 }
374 }
375}
376
377impl UvsReason {
380 pub fn is_retryable(&self) -> bool {
383 match self {
384 UvsReason::NetworkError(_) => true,
386 UvsReason::TimeoutError(_) => true,
387 UvsReason::ResourceError(_) => true,
388 UvsReason::SystemError(_) => true,
389 UvsReason::ExternalError(_) => true,
390
391 UvsReason::ValidationError(_) => false,
393 UvsReason::BusinessError(_) => false,
394 UvsReason::NotFoundError(_) => false,
395 UvsReason::PermissionError(_) => false,
396
397 UvsReason::ConfigError(_) => false,
399 UvsReason::DataError(_, _) => false,
400 UvsReason::LogicError(_) => false,
401 }
402 }
403
404 pub fn is_high_severity(&self) -> bool {
407 match self {
408 UvsReason::SystemError(_) => true,
410 UvsReason::ResourceError(_) => true,
411 UvsReason::ConfigError(_) => true,
412
413 _ => false,
415 }
416 }
417
418 pub fn category_name(&self) -> &'static str {
421 match self {
422 UvsReason::ValidationError(_) => "validation",
423 UvsReason::BusinessError(_) => "business",
424 UvsReason::NotFoundError(_) => "not_found",
425 UvsReason::PermissionError(_) => "permission",
426 UvsReason::DataError(_, _) => "data",
427 UvsReason::SystemError(_) => "system",
428 UvsReason::NetworkError(_) => "network",
429 UvsReason::ResourceError(_) => "resource",
430 UvsReason::TimeoutError(_) => "timeout",
431 UvsReason::ConfigError(_) => "config",
432 UvsReason::ExternalError(_) => "external",
433 UvsReason::LogicError(_) => "logic",
434 }
435 }
436}
437
438#[cfg(test)]
439mod tests {
440 use super::*;
441
442 #[test]
443 fn test_error_code_ranges() {
444 assert_eq!(UvsReason::validation_error("test").error_code(), 100);
446 assert_eq!(UvsReason::business_error("test").error_code(), 101);
447 assert_eq!(UvsReason::not_found_error("test").error_code(), 102);
448 assert_eq!(UvsReason::permission_error("test").error_code(), 103);
449
450 assert_eq!(UvsReason::data_error("test", None).error_code(), 200);
452 assert_eq!(UvsReason::system_error("test").error_code(), 201);
453 assert_eq!(UvsReason::network_error("test").error_code(), 202);
454 assert_eq!(UvsReason::resource_error("test").error_code(), 203);
455 assert_eq!(UvsReason::timeout_error("test").error_code(), 204);
456
457 assert_eq!(UvsReason::core_conf("test").error_code(), 300);
459 assert_eq!(UvsReason::external_error("test").error_code(), 301);
460 }
461
462 #[test]
463 fn test_retryable_errors() {
464 assert!(UvsReason::network_error("timeout").is_retryable());
465 assert!(UvsReason::timeout_error("request timeout").is_retryable());
466 assert!(!UvsReason::validation_error("invalid input").is_retryable());
467 assert!(!UvsReason::business_error("insufficient funds").is_retryable());
468 }
469
470 #[test]
471 fn test_high_severity_errors() {
472 assert!(UvsReason::system_error("disk full").is_high_severity());
473 assert!(UvsReason::resource_error("out of memory").is_high_severity());
474 assert!(!UvsReason::validation_error("bad format").is_high_severity());
475 assert!(!UvsReason::NotFoundError("user not found".into()).is_high_severity());
476 }
477
478 #[test]
479 fn test_category_names() {
480 assert_eq!(UvsReason::network_error("test").category_name(), "network");
481 assert_eq!(
482 UvsReason::business_error("test").category_name(),
483 "business"
484 );
485 assert_eq!(UvsReason::core_conf("test").category_name(), "config");
486 }
487
488 #[test]
489 fn test_trait_implementations() {
490 let reason: UvsReason = UvsReason::from_net("network error".to_string());
492 assert_eq!(reason.error_code(), 202);
493
494 let reason: UvsReason = UvsReason::from_validation("validation error".to_string());
495 assert_eq!(reason.error_code(), 100);
496
497 let reason: UvsReason = UvsReason::from_external("external error".to_string());
498 assert_eq!(reason.error_code(), 301);
499 }
500}