1use std::backtrace::Backtrace;
2use std::error::Error as StdError;
3use std::fmt;
4use std::io;
5use std::path::PathBuf;
6
7#[cfg(feature = "serde")]
8use serde::Serialize;
9
10pub type AppResult<T> = std::result::Result<T, crate::error::AppError>;
17
18#[deprecated(
25 since = "1.0.0",
26 note = "use `error_forge::AppResult` instead — the unqualified `Result` \
27 name shadows `std::result::Result` in glob imports"
28)]
29pub type Result<T> = AppResult<T>;
30
31pub trait ForgeError: std::error::Error + Send + Sync + 'static {
33 fn kind(&self) -> &'static str;
35
36 fn caption(&self) -> &'static str;
38
39 fn is_retryable(&self) -> bool {
41 false
42 }
43
44 fn is_fatal(&self) -> bool {
46 false
47 }
48
49 fn status_code(&self) -> u16 {
51 500
52 }
53
54 fn exit_code(&self) -> i32 {
56 1
57 }
58
59 fn user_message(&self) -> String {
61 self.to_string()
62 }
63
64 fn dev_message(&self) -> String {
66 format!("[{}] {}", self.kind(), self)
67 }
68
69 fn backtrace(&self) -> Option<&Backtrace> {
71 None
72 }
73
74 fn register(&self) {
76 crate::macros::call_error_hook(
77 self.caption(),
78 self.kind(),
79 self.is_fatal(),
80 self.is_retryable(),
81 );
82 }
83}
84
85#[derive(Debug)]
87#[cfg_attr(feature = "serde", derive(Serialize))]
88pub enum AppError {
89 Config {
91 message: String,
92 retryable: bool,
93 fatal: bool,
94 status: u16,
95 },
96
97 Filesystem {
99 path: Option<PathBuf>,
100 #[cfg_attr(feature = "serde", serde(skip))]
101 source: io::Error,
102 retryable: bool,
103 fatal: bool,
104 status: u16,
105 },
106
107 Network {
109 endpoint: String,
110 #[cfg_attr(feature = "serde", serde(skip))]
111 source: Option<Box<dyn StdError + Send + Sync>>,
112 retryable: bool,
113 fatal: bool,
114 status: u16,
115 },
116
117 Other {
119 message: String,
120 retryable: bool,
121 fatal: bool,
122 status: u16,
123 },
124}
125
126impl std::fmt::Display for AppError {
127 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128 match self {
129 Self::Config { message, .. } => write!(f, "⚙️ Configuration Error: {message}"),
130 Self::Filesystem { path, source, .. } => {
131 if let Some(p) = path {
132 write!(f, "💾 Filesystem Error at {p:?}: {source}")
133 } else {
134 write!(f, "💾 Filesystem Error: {source}")
135 }
136 }
137 Self::Network {
138 endpoint, source, ..
139 } => {
140 if let Some(src) = source {
141 write!(f, "🌐 Network Error on {endpoint}: {src}")
142 } else {
143 write!(f, "🌐 Network Error on {endpoint}")
144 }
145 }
146 Self::Other { message, .. } => write!(f, "🚨 Error: {message}"),
147 }
148 }
149}
150
151impl std::error::Error for AppError {
152 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
153 match self {
154 AppError::Filesystem { source, .. } => Some(source),
155 AppError::Network {
156 source: Some(src), ..
157 } => Some(src.as_ref()),
158 _ => None,
159 }
160 }
161}
162
163impl From<io::Error> for AppError {
164 fn from(e: io::Error) -> Self {
165 AppError::Filesystem {
166 path: None,
167 source: e,
168 retryable: false,
169 fatal: true,
170 status: 500,
171 }
172 }
173}
174
175impl ForgeError for AppError {
176 fn kind(&self) -> &'static str {
177 match self {
178 Self::Config { .. } => "Config",
179 Self::Filesystem { .. } => "Filesystem",
180 Self::Network { .. } => "Network",
181 Self::Other { .. } => "Other",
182 }
183 }
184
185 fn caption(&self) -> &'static str {
186 match self {
187 Self::Config { .. } => "⚙️ Configuration",
188 Self::Filesystem { .. } => "💾 Filesystem",
189 Self::Network { .. } => "🌐 Network",
190 Self::Other { .. } => "🚨 Error",
191 }
192 }
193
194 fn is_retryable(&self) -> bool {
195 match self {
196 Self::Config { retryable, .. } => *retryable,
197 Self::Filesystem { retryable, .. } => *retryable,
198 Self::Network { retryable, .. } => *retryable,
199 Self::Other { retryable, .. } => *retryable,
200 }
201 }
202
203 fn is_fatal(&self) -> bool {
204 match self {
205 Self::Config { fatal, .. } => *fatal,
206 Self::Filesystem { fatal, .. } => *fatal,
207 Self::Network { fatal, .. } => *fatal,
208 Self::Other { fatal, .. } => *fatal,
209 }
210 }
211
212 fn status_code(&self) -> u16 {
213 match self {
214 Self::Config { status, .. } => *status,
215 Self::Filesystem { status, .. } => *status,
216 Self::Network { status, .. } => *status,
217 Self::Other { status, .. } => *status,
218 }
219 }
220}
221
222impl AppError {
224 pub fn config(message: impl Into<String>) -> Self {
226 let instance = Self::Config {
227 message: message.into(),
228 retryable: false,
229 fatal: false,
230 status: 500,
231 };
232 crate::macros::call_error_hook(
233 instance.caption(),
234 instance.kind(),
235 instance.is_fatal(),
236 instance.is_retryable(),
237 );
238 instance
239 }
240
241 pub fn filesystem(path: impl Into<String>, source: impl Into<Option<io::Error>>) -> Self {
243 let source = match source.into() {
245 Some(err) => err,
246 None => io::Error::other("File operation failed"),
247 };
248
249 let instance = Self::Filesystem {
250 path: Some(path.into().into()),
251 source,
252 retryable: false,
253 fatal: false,
254 status: 500,
255 };
256 crate::macros::call_error_hook(
257 instance.caption(),
258 instance.kind(),
259 instance.is_fatal(),
260 instance.is_retryable(),
261 );
262 instance
263 }
264
265 pub fn filesystem_with_source(path: impl Into<PathBuf>, source: io::Error) -> Self {
267 let instance = Self::Filesystem {
268 path: Some(path.into()),
269 source,
270 retryable: false,
271 fatal: false,
272 status: 500,
273 };
274 crate::macros::call_error_hook(
275 instance.caption(),
276 instance.kind(),
277 instance.is_fatal(),
278 instance.is_retryable(),
279 );
280 instance
281 }
282
283 pub fn network(
285 endpoint: impl Into<String>,
286 source: impl Into<Option<Box<dyn StdError + Send + Sync>>>,
287 ) -> Self {
288 let source = source.into();
290
291 let instance = Self::Network {
292 endpoint: endpoint.into(),
293 source,
294 retryable: true,
295 fatal: false,
296 status: 503,
297 };
298 crate::macros::call_error_hook(
299 instance.caption(),
300 instance.kind(),
301 instance.is_fatal(),
302 instance.is_retryable(),
303 );
304 instance
305 }
306
307 pub fn network_with_source(
309 endpoint: impl Into<String>,
310 source: Option<Box<dyn StdError + Send + Sync>>,
311 ) -> Self {
312 let instance = Self::Network {
313 endpoint: endpoint.into(),
314 source,
315 retryable: true,
316 fatal: false,
317 status: 503,
318 };
319 crate::macros::call_error_hook(
320 instance.caption(),
321 instance.kind(),
322 instance.is_fatal(),
323 instance.is_retryable(),
324 );
325 instance
326 }
327
328 pub fn other(message: impl Into<String>) -> Self {
330 let instance = Self::Other {
331 message: message.into(),
332 retryable: false,
333 fatal: false,
334 status: 500,
335 };
336 crate::macros::call_error_hook(
337 instance.caption(),
338 instance.kind(),
339 instance.is_fatal(),
340 instance.is_retryable(),
341 );
342 instance
343 }
344
345 pub fn with_retryable(mut self, retryable: bool) -> Self {
347 match &mut self {
348 Self::Config { retryable: r, .. } => *r = retryable,
349 Self::Filesystem { retryable: r, .. } => *r = retryable,
350 Self::Network { retryable: r, .. } => *r = retryable,
351 Self::Other { retryable: r, .. } => *r = retryable,
352 }
353 self
354 }
355
356 pub fn with_fatal(mut self, fatal: bool) -> Self {
358 match &mut self {
359 Self::Config { fatal: f, .. } => *f = fatal,
360 Self::Filesystem { fatal: f, .. } => *f = fatal,
361 Self::Network { fatal: f, .. } => *f = fatal,
362 Self::Other { fatal: f, .. } => *f = fatal,
363 }
364 self
365 }
366
367 pub fn with_status(mut self, status: u16) -> Self {
369 match &mut self {
370 Self::Config { status: s, .. } => *s = status,
371 Self::Filesystem { status: s, .. } => *s = status,
372 Self::Network { status: s, .. } => *s = status,
373 Self::Other { status: s, .. } => *s = status,
374 }
375 self
376 }
377
378 pub fn with_code(self, code: impl Into<String>) -> crate::registry::CodedError<Self> {
380 crate::registry::CodedError::new(self, code.into())
381 }
382
383 pub fn context<C: std::fmt::Display + std::fmt::Debug + Send + Sync + 'static>(
385 self,
386 context: C,
387 ) -> crate::context::ContextError<Self, C> {
388 crate::context::ContextError::new(self, context)
389 }
390}