1use thiserror::Error;
2
3use super::{DomainReason, ErrorCategory, ErrorCode, ErrorIdentityProvider};
4
5#[derive(Debug, Error, PartialEq, Clone)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize))]
9pub enum ConfErrReason {
10 #[error("core config")]
11 Core,
12 #[error("feature config error")]
13 Feature,
14 #[error("dynamic config error")]
15 Dynamic,
16}
17
18#[derive(Debug, Error, PartialEq, Clone)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize))]
27pub enum UvsReason {
28 #[error("validation error")]
31 ValidationError,
32
33 #[error("business logic error")]
35 BusinessError,
36
37 #[error("run rule error")]
39 RunRuleError,
40
41 #[error("not found error")]
43 NotFoundError,
44
45 #[error("permission error")]
47 PermissionError,
48
49 #[error("data error")]
52 DataError,
53
54 #[error("system error")]
56 SystemError,
57
58 #[error("network error")]
60 NetworkError,
61
62 #[error("resource error")]
64 ResourceError,
65
66 #[error("timeout error")]
68 TimeoutError,
69
70 #[error("configuration error << {0}")]
73 ConfigError(ConfErrReason),
74
75 #[error("external service error")]
77 ExternalError,
78
79 #[error("BUG :logic error")]
81 LogicError,
82}
83
84impl DomainReason for UvsReason {}
85
86impl UvsReason {
87 pub fn core_conf() -> Self {
89 Self::ConfigError(ConfErrReason::Core)
90 }
91
92 pub fn feature_conf() -> Self {
93 Self::ConfigError(ConfErrReason::Feature)
94 }
95
96 pub fn dynamic_conf() -> Self {
97 Self::ConfigError(ConfErrReason::Dynamic)
98 }
99
100 pub fn validation_error() -> Self {
102 Self::ValidationError
103 }
104
105 pub fn business_error() -> Self {
106 Self::BusinessError
107 }
108
109 pub fn rule_error() -> Self {
110 Self::RunRuleError
111 }
112
113 pub fn not_found_error() -> Self {
114 Self::NotFoundError
115 }
116
117 pub fn permission_error() -> Self {
118 Self::PermissionError
119 }
120
121 pub fn data_error() -> Self {
123 Self::DataError
124 }
125
126 pub fn system_error() -> Self {
127 Self::SystemError
128 }
129
130 pub fn network_error() -> Self {
131 Self::NetworkError
132 }
133
134 pub fn resource_error() -> Self {
135 Self::ResourceError
136 }
137
138 pub fn timeout_error() -> Self {
139 Self::TimeoutError
140 }
141
142 pub fn external_error() -> Self {
144 Self::ExternalError
145 }
146
147 pub fn logic_error() -> Self {
148 Self::LogicError
149 }
150}
151
152pub trait UvsFrom: From<UvsReason> + Sized {
154 fn from_conf() -> Self {
155 Self::from(UvsReason::core_conf())
156 }
157
158 fn from_conf_reason(reason: ConfErrReason) -> Self {
159 Self::from(UvsReason::ConfigError(reason))
160 }
161
162 fn from_data() -> Self {
163 Self::from(UvsReason::data_error())
164 }
165
166 fn from_sys() -> Self {
167 Self::from(UvsReason::system_error())
168 }
169
170 fn from_biz() -> Self {
171 Self::from(UvsReason::business_error())
172 }
173
174 fn from_logic() -> Self {
175 Self::from(UvsReason::logic_error())
176 }
177
178 fn from_rule() -> Self {
179 Self::from(UvsReason::rule_error())
180 }
181
182 fn from_res() -> Self {
183 Self::from(UvsReason::resource_error())
184 }
185
186 fn from_net() -> Self {
187 Self::from(UvsReason::network_error())
188 }
189
190 fn from_timeout() -> Self {
191 Self::from(UvsReason::timeout_error())
192 }
193
194 fn from_validation() -> Self {
195 Self::from(UvsReason::validation_error())
196 }
197
198 fn from_not_found() -> Self {
199 Self::from(UvsReason::not_found_error())
200 }
201
202 fn from_permission() -> Self {
203 Self::from(UvsReason::permission_error())
204 }
205
206 fn from_external() -> Self {
207 Self::from(UvsReason::external_error())
208 }
209}
210
211impl<T> UvsFrom for T where T: From<UvsReason> {}
212
213impl ErrorCode for UvsReason {
214 fn error_code(&self) -> i32 {
215 match self {
216 UvsReason::ValidationError => 100,
218 UvsReason::BusinessError => 101,
219 UvsReason::NotFoundError => 102,
220 UvsReason::PermissionError => 103,
221 UvsReason::LogicError => 104,
222 UvsReason::RunRuleError => 105,
223
224 UvsReason::DataError => 200,
226 UvsReason::SystemError => 201,
227 UvsReason::NetworkError => 202,
228 UvsReason::ResourceError => 203,
229 UvsReason::TimeoutError => 204,
230
231 UvsReason::ConfigError(_) => 300,
233 UvsReason::ExternalError => 301,
234 }
235 }
236}
237
238impl ErrorIdentityProvider for UvsReason {
239 fn stable_code(&self) -> &'static str {
240 match self {
241 UvsReason::ValidationError => "biz.validation_error",
242 UvsReason::BusinessError => "biz.business_error",
243 UvsReason::RunRuleError => "biz.run_rule_error",
244 UvsReason::NotFoundError => "biz.not_found",
245 UvsReason::PermissionError => "biz.permission_denied",
246 UvsReason::DataError => "sys.data_error",
247 UvsReason::SystemError => "sys.io_error",
248 UvsReason::NetworkError => "sys.network_error",
249 UvsReason::ResourceError => "sys.resource_exhausted",
250 UvsReason::TimeoutError => "sys.timeout",
251 UvsReason::ConfigError(ConfErrReason::Core) => "conf.core_invalid",
252 UvsReason::ConfigError(ConfErrReason::Feature) => "conf.feature_invalid",
253 UvsReason::ConfigError(ConfErrReason::Dynamic) => "conf.dynamic_invalid",
254 UvsReason::ExternalError => "sys.external_service_error",
255 UvsReason::LogicError => "logic.internal_invariant_broken",
256 }
257 }
258
259 fn error_category(&self) -> ErrorCategory {
260 match self {
261 UvsReason::ConfigError(_) => ErrorCategory::Conf,
262 UvsReason::LogicError => ErrorCategory::Logic,
263 UvsReason::ValidationError
264 | UvsReason::BusinessError
265 | UvsReason::RunRuleError
266 | UvsReason::NotFoundError
267 | UvsReason::PermissionError => ErrorCategory::Biz,
268 UvsReason::DataError
269 | UvsReason::SystemError
270 | UvsReason::NetworkError
271 | UvsReason::ResourceError
272 | UvsReason::TimeoutError
273 | UvsReason::ExternalError => ErrorCategory::Sys,
274 }
275 }
276}
277
278impl UvsReason {
279 pub fn is_retryable(&self) -> bool {
282 match self {
283 UvsReason::NetworkError => true,
285 UvsReason::TimeoutError => true,
286 UvsReason::ResourceError => true,
287 UvsReason::SystemError => true,
288 UvsReason::ExternalError => true,
289
290 UvsReason::ValidationError => false,
292 UvsReason::BusinessError => false,
293 UvsReason::RunRuleError => false,
294 UvsReason::NotFoundError => false,
295 UvsReason::PermissionError => false,
296
297 UvsReason::ConfigError(_) => false,
299 UvsReason::DataError => false,
300 UvsReason::LogicError => false,
301 }
302 }
303
304 pub fn is_high_severity(&self) -> bool {
307 match self {
308 UvsReason::SystemError => true,
310 UvsReason::ResourceError => true,
311 UvsReason::ConfigError(_) => true,
312
313 _ => false,
315 }
316 }
317
318 pub fn category_name(&self) -> &'static str {
321 match self {
322 UvsReason::ValidationError => "validation",
323 UvsReason::BusinessError => "business",
324 UvsReason::RunRuleError => "runrule",
325 UvsReason::NotFoundError => "not_found",
326 UvsReason::PermissionError => "permission",
327 UvsReason::DataError => "data",
328 UvsReason::SystemError => "system",
329 UvsReason::NetworkError => "network",
330 UvsReason::ResourceError => "resource",
331 UvsReason::TimeoutError => "timeout",
332 UvsReason::ConfigError(_) => "config",
333 UvsReason::ExternalError => "external",
334 UvsReason::LogicError => "logic",
335 }
336 }
337}
338
339#[cfg(test)]
340mod tests {
341 use super::*;
342
343 #[test]
344 fn test_error_code_ranges() {
345 assert_eq!(UvsReason::validation_error().error_code(), 100);
347 assert_eq!(UvsReason::business_error().error_code(), 101);
348 assert_eq!(UvsReason::not_found_error().error_code(), 102);
349 assert_eq!(UvsReason::permission_error().error_code(), 103);
350
351 assert_eq!(UvsReason::data_error().error_code(), 200);
353 assert_eq!(UvsReason::system_error().error_code(), 201);
354 assert_eq!(UvsReason::network_error().error_code(), 202);
355 assert_eq!(UvsReason::resource_error().error_code(), 203);
356 assert_eq!(UvsReason::timeout_error().error_code(), 204);
357
358 assert_eq!(UvsReason::core_conf().error_code(), 300);
360 assert_eq!(UvsReason::external_error().error_code(), 301);
361 }
362
363 #[test]
364 fn test_retryable_errors() {
365 assert!(UvsReason::network_error().is_retryable());
366 assert!(UvsReason::timeout_error().is_retryable());
367 assert!(!UvsReason::validation_error().is_retryable());
368 assert!(!UvsReason::business_error().is_retryable());
369 }
370
371 #[test]
372 fn test_high_severity_errors() {
373 assert!(UvsReason::system_error().is_high_severity());
374 assert!(UvsReason::resource_error().is_high_severity());
375 assert!(!UvsReason::validation_error().is_high_severity());
376 assert!(!UvsReason::NotFoundError.is_high_severity());
377 }
378
379 #[test]
380 fn test_category_names() {
381 assert_eq!(UvsReason::network_error().category_name(), "network");
382 assert_eq!(UvsReason::business_error().category_name(), "business");
383 assert_eq!(UvsReason::core_conf().category_name(), "config");
384 }
385
386 #[test]
387 fn test_stable_code_values() {
388 assert_eq!(
389 UvsReason::validation_error().stable_code(),
390 "biz.validation_error"
391 );
392 assert_eq!(UvsReason::system_error().stable_code(), "sys.io_error");
393 assert_eq!(UvsReason::core_conf().stable_code(), "conf.core_invalid");
394 assert_eq!(
395 UvsReason::logic_error().stable_code(),
396 "logic.internal_invariant_broken"
397 );
398 }
399
400 #[test]
401 fn test_error_categories() {
402 assert_eq!(
403 UvsReason::validation_error().error_category(),
404 ErrorCategory::Biz
405 );
406 assert_eq!(
407 UvsReason::system_error().error_category(),
408 ErrorCategory::Sys
409 );
410 assert_eq!(UvsReason::core_conf().error_category(), ErrorCategory::Conf);
411 assert_eq!(
412 UvsReason::logic_error().error_category(),
413 ErrorCategory::Logic
414 );
415 assert_eq!(ErrorCategory::Biz.as_str(), "biz");
416 }
417
418 #[test]
419 fn test_trait_implementations() {
420 let reason: UvsReason = <UvsReason as UvsFrom>::from_net();
421 assert_eq!(reason.error_code(), 202);
422
423 let reason: UvsReason = <UvsReason as UvsFrom>::from_validation();
424 assert_eq!(reason.error_code(), 100);
425
426 let reason: UvsReason = <UvsReason as UvsFrom>::from_external();
427 assert_eq!(reason.error_code(), 301);
428 assert_eq!(reason.error_category(), ErrorCategory::Sys);
429 }
430}