1use std::fmt;
2use std::path::PathBuf;
3use std::io;
4use std::error::Error as StdError;
5use std::backtrace::Backtrace;
6
7#[cfg(feature = "serde")]
8use serde::Serialize;
9
10pub type Result<T> = std::result::Result<T, crate::error::AppError>;
12
13pub trait ForgeError: std::error::Error + Send + Sync + 'static {
15 fn kind(&self) -> &'static str;
17
18 fn caption(&self) -> &'static str;
20
21 fn is_retryable(&self) -> bool {
23 false
24 }
25
26 fn is_fatal(&self) -> bool {
28 true
29 }
30
31 fn status_code(&self) -> u16 {
33 500
34 }
35
36 fn exit_code(&self) -> i32 {
38 1
39 }
40
41 fn user_message(&self) -> String {
43 self.to_string()
44 }
45
46 fn dev_message(&self) -> String {
48 format!("[{}] {}", self.kind(), self)
49 }
50
51 fn backtrace(&self) -> Option<&Backtrace> {
53 None
54 }
55
56 fn register(&self) {
58 crate::macros::call_error_hook(self.caption(), self.kind(), self.is_fatal(), self.is_retryable());
59 }
60}
61
62#[derive(Debug)]
64#[cfg_attr(feature = "serde", derive(Serialize))]
65pub enum AppError {
66 Config { message: String },
68
69 Filesystem { path: Option<PathBuf>, #[cfg_attr(feature = "serde", serde(skip))] source: io::Error },
71
72 Network { endpoint: String, #[cfg_attr(feature = "serde", serde(skip))] source: Option<Box<dyn StdError + Send + Sync>> },
74
75 Other { message: String },
77}
78
79impl std::fmt::Display for AppError {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 match self {
82 Self::Config { message } => write!(f, "⚙️ Configuration Error: {}", message),
83 Self::Filesystem { path, source } => {
84 if let Some(p) = path {
85 write!(f, "💾 Filesystem Error at {:?}: {}", p, source)
86 } else {
87 write!(f, "💾 Filesystem Error: {}", source)
88 }
89 },
90 Self::Network { endpoint, source } => {
91 if let Some(src) = source {
92 write!(f, "🌐 Network Error on {}: {}", endpoint, src)
93 } else {
94 write!(f, "🌐 Network Error on {}", endpoint)
95 }
96 },
97 Self::Other { message } => write!(f, "🚨 Error: {}", message),
98 }
99 }
100}
101
102impl std::error::Error for AppError {
103 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
104 match self {
105 AppError::Filesystem { source, .. } => Some(source),
106 AppError::Network { source: Some(src), .. } => Some(src.as_ref()),
107 _ => None,
108 }
109 }
110}
111
112impl From<io::Error> for AppError {
113 fn from(e: io::Error) -> Self {
114 AppError::Filesystem { path: None, source: e }
115 }
116}
117
118impl ForgeError for AppError {
119 fn kind(&self) -> &'static str {
120 match self {
121 Self::Config { .. } => "Config",
122 Self::Filesystem { .. } => "Filesystem",
123 Self::Network { .. } => "Network",
124 Self::Other { .. } => "Other",
125 }
126 }
127
128 fn caption(&self) -> &'static str {
129 match self {
130 Self::Config { .. } => "⚙️ Configuration",
131 Self::Filesystem { .. } => "💾 Filesystem",
132 Self::Network { .. } => "🌐 Network",
133 Self::Other { .. } => "🚨 Error",
134 }
135 }
136
137 fn is_retryable(&self) -> bool {
138 matches!(self, Self::Network { .. })
139 }
140
141 fn status_code(&self) -> u16 {
142 match self {
143 Self::Config { .. } => 500,
144 Self::Filesystem { .. } => 500,
145 Self::Network { .. } => 503,
146 Self::Other { .. } => 500,
147 }
148 }
149}
150
151impl AppError {
153 pub fn config(message: impl Into<String>) -> Self {
155 let instance = Self::Config { message: message.into() };
156 crate::macros::call_error_hook(instance.caption(), instance.kind(), instance.is_fatal(), instance.is_retryable());
157 instance
158 }
159
160 pub fn filesystem(path: impl Into<PathBuf>, source: io::Error) -> Self {
162 let instance = Self::Filesystem { path: Some(path.into()), source };
163 crate::macros::call_error_hook(instance.caption(), instance.kind(), instance.is_fatal(), instance.is_retryable());
164 instance
165 }
166
167 pub fn network(endpoint: impl Into<String>, source: Option<Box<dyn StdError + Send + Sync>>) -> Self {
169 let instance = Self::Network { endpoint: endpoint.into(), source };
170 crate::macros::call_error_hook(instance.caption(), instance.kind(), instance.is_fatal(), instance.is_retryable());
171 instance
172 }
173
174 pub fn other(message: impl Into<String>) -> Self {
176 let instance = Self::Other { message: message.into() };
177 crate::macros::call_error_hook(instance.caption(), instance.kind(), instance.is_fatal(), instance.is_retryable());
178 instance
179 }
180}