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 UnifiedReason {
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 UnifiedReason {}
85
86impl UnifiedReason {
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
152impl ErrorCode for UnifiedReason {
153 fn error_code(&self) -> i32 {
154 match self {
155 UnifiedReason::ValidationError => 100,
157 UnifiedReason::BusinessError => 101,
158 UnifiedReason::NotFoundError => 102,
159 UnifiedReason::PermissionError => 103,
160 UnifiedReason::LogicError => 104,
161 UnifiedReason::RunRuleError => 105,
162
163 UnifiedReason::DataError => 200,
165 UnifiedReason::SystemError => 201,
166 UnifiedReason::NetworkError => 202,
167 UnifiedReason::ResourceError => 203,
168 UnifiedReason::TimeoutError => 204,
169
170 UnifiedReason::ConfigError(_) => 300,
172 UnifiedReason::ExternalError => 301,
173 }
174 }
175}
176
177impl ErrorIdentityProvider for UnifiedReason {
178 fn stable_code(&self) -> &'static str {
179 match self {
180 UnifiedReason::ValidationError => "biz.validation_error",
181 UnifiedReason::BusinessError => "biz.business_error",
182 UnifiedReason::RunRuleError => "biz.run_rule_error",
183 UnifiedReason::NotFoundError => "biz.not_found",
184 UnifiedReason::PermissionError => "biz.permission_denied",
185 UnifiedReason::DataError => "sys.data_error",
186 UnifiedReason::SystemError => "sys.io_error",
187 UnifiedReason::NetworkError => "sys.network_error",
188 UnifiedReason::ResourceError => "sys.resource_exhausted",
189 UnifiedReason::TimeoutError => "sys.timeout",
190 UnifiedReason::ConfigError(ConfErrReason::Core) => "conf.core_invalid",
191 UnifiedReason::ConfigError(ConfErrReason::Feature) => "conf.feature_invalid",
192 UnifiedReason::ConfigError(ConfErrReason::Dynamic) => "conf.dynamic_invalid",
193 UnifiedReason::ExternalError => "sys.external_service_error",
194 UnifiedReason::LogicError => "logic.internal_invariant_broken",
195 }
196 }
197
198 fn error_category(&self) -> ErrorCategory {
199 match self {
200 UnifiedReason::ConfigError(_) => ErrorCategory::Conf,
201 UnifiedReason::LogicError => ErrorCategory::Logic,
202 UnifiedReason::ValidationError
203 | UnifiedReason::BusinessError
204 | UnifiedReason::RunRuleError
205 | UnifiedReason::NotFoundError
206 | UnifiedReason::PermissionError => ErrorCategory::Biz,
207 UnifiedReason::DataError
208 | UnifiedReason::SystemError
209 | UnifiedReason::NetworkError
210 | UnifiedReason::ResourceError
211 | UnifiedReason::TimeoutError
212 | UnifiedReason::ExternalError => ErrorCategory::Sys,
213 }
214 }
215}
216
217impl UnifiedReason {
218 pub fn is_retryable(&self) -> bool {
221 match self {
222 UnifiedReason::NetworkError => true,
224 UnifiedReason::TimeoutError => true,
225 UnifiedReason::ResourceError => true,
226 UnifiedReason::SystemError => true,
227 UnifiedReason::ExternalError => true,
228
229 UnifiedReason::ValidationError => false,
231 UnifiedReason::BusinessError => false,
232 UnifiedReason::RunRuleError => false,
233 UnifiedReason::NotFoundError => false,
234 UnifiedReason::PermissionError => false,
235
236 UnifiedReason::ConfigError(_) => false,
238 UnifiedReason::DataError => false,
239 UnifiedReason::LogicError => false,
240 }
241 }
242
243 pub fn is_high_severity(&self) -> bool {
246 match self {
247 UnifiedReason::SystemError => true,
249 UnifiedReason::ResourceError => true,
250 UnifiedReason::ConfigError(_) => true,
251
252 _ => false,
254 }
255 }
256
257 pub fn category_name(&self) -> &'static str {
260 match self {
261 UnifiedReason::ValidationError => "validation",
262 UnifiedReason::BusinessError => "business",
263 UnifiedReason::RunRuleError => "runrule",
264 UnifiedReason::NotFoundError => "not_found",
265 UnifiedReason::PermissionError => "permission",
266 UnifiedReason::DataError => "data",
267 UnifiedReason::SystemError => "system",
268 UnifiedReason::NetworkError => "network",
269 UnifiedReason::ResourceError => "resource",
270 UnifiedReason::TimeoutError => "timeout",
271 UnifiedReason::ConfigError(_) => "config",
272 UnifiedReason::ExternalError => "external",
273 UnifiedReason::LogicError => "logic",
274 }
275 }
276}
277
278#[deprecated(since = "0.9.0", note = "renamed to UnifiedReason")]
280#[allow(dead_code)]
281pub type UvsReason = UnifiedReason;
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 #[test]
288 fn test_error_code_ranges() {
289 assert_eq!(UnifiedReason::validation_error().error_code(), 100);
291 assert_eq!(UnifiedReason::business_error().error_code(), 101);
292 assert_eq!(UnifiedReason::not_found_error().error_code(), 102);
293 assert_eq!(UnifiedReason::permission_error().error_code(), 103);
294
295 assert_eq!(UnifiedReason::data_error().error_code(), 200);
297 assert_eq!(UnifiedReason::system_error().error_code(), 201);
298 assert_eq!(UnifiedReason::network_error().error_code(), 202);
299 assert_eq!(UnifiedReason::resource_error().error_code(), 203);
300 assert_eq!(UnifiedReason::timeout_error().error_code(), 204);
301
302 assert_eq!(UnifiedReason::core_conf().error_code(), 300);
304 assert_eq!(UnifiedReason::external_error().error_code(), 301);
305 }
306
307 #[test]
308 fn test_retryable_errors() {
309 assert!(UnifiedReason::network_error().is_retryable());
310 assert!(UnifiedReason::timeout_error().is_retryable());
311 assert!(!UnifiedReason::validation_error().is_retryable());
312 assert!(!UnifiedReason::business_error().is_retryable());
313 }
314
315 #[test]
316 fn test_high_severity_errors() {
317 assert!(UnifiedReason::system_error().is_high_severity());
318 assert!(UnifiedReason::resource_error().is_high_severity());
319 assert!(!UnifiedReason::validation_error().is_high_severity());
320 assert!(!UnifiedReason::NotFoundError.is_high_severity());
321 }
322
323 #[test]
324 fn test_category_names() {
325 assert_eq!(UnifiedReason::network_error().category_name(), "network");
326 assert_eq!(UnifiedReason::business_error().category_name(), "business");
327 assert_eq!(UnifiedReason::core_conf().category_name(), "config");
328 }
329
330 #[test]
331 fn test_stable_code_values() {
332 assert_eq!(
333 UnifiedReason::validation_error().stable_code(),
334 "biz.validation_error"
335 );
336 assert_eq!(UnifiedReason::system_error().stable_code(), "sys.io_error");
337 assert_eq!(
338 UnifiedReason::core_conf().stable_code(),
339 "conf.core_invalid"
340 );
341 assert_eq!(
342 UnifiedReason::logic_error().stable_code(),
343 "logic.internal_invariant_broken"
344 );
345 }
346
347 #[test]
348 fn test_error_categories() {
349 assert_eq!(
350 UnifiedReason::validation_error().error_category(),
351 ErrorCategory::Biz
352 );
353 assert_eq!(
354 UnifiedReason::system_error().error_category(),
355 ErrorCategory::Sys
356 );
357 assert_eq!(
358 UnifiedReason::core_conf().error_category(),
359 ErrorCategory::Conf
360 );
361 assert_eq!(
362 UnifiedReason::logic_error().error_category(),
363 ErrorCategory::Logic
364 );
365 assert_eq!(ErrorCategory::Biz.as_str(), "biz");
366 }
367
368 #[test]
369 fn test_trait_implementations() {
370 let reason = UnifiedReason::network_error();
371 assert_eq!(reason.error_code(), 202);
372
373 let reason = UnifiedReason::validation_error();
374 assert_eq!(reason.error_code(), 100);
375
376 let reason = UnifiedReason::external_error();
377 assert_eq!(reason.error_code(), 301);
378 assert_eq!(reason.error_category(), ErrorCategory::Sys);
379 }
380}