1use std::path::PathBuf;
34use thiserror::Error;
35
36#[derive(Debug, Error)]
42pub enum ScopeError {
43 #[error("Configuration error: {0}")]
47 Config(#[from] ConfigError),
48
49 #[error("Chain client error: {0}")]
53 Chain(String),
54
55 #[error("API request failed: {0}")]
59 Request(#[from] reqwest::Error),
60
61 #[error("Invalid address format: {0}")]
66 InvalidAddress(String),
67
68 #[error("Invalid transaction hash: {0}")]
72 InvalidHash(String),
73
74 #[error("I/O error: {0}")]
76 IoError(#[from] std::io::Error),
77
78 #[error("I/O error: {0}")]
80 Io(String),
81
82 #[error("Export failed: {0}")]
84 Export(String),
85
86 #[error("JSON error: {0}")]
88 Json(#[from] serde_json::Error),
89
90 #[error("Network error: {0}")]
92 Network(String),
93
94 #[error("API error: {0}")]
96 Api(String),
97
98 #[error("Not found: {0}")]
100 NotFound(String),
101
102 #[error("{0}")]
104 Other(String),
105}
106
107#[derive(Debug, Error)]
112pub enum ConfigError {
113 #[error("Configuration file not found: {path}")]
115 NotFound {
116 path: PathBuf,
118 },
119
120 #[error("Failed to read configuration file '{path}': {source}")]
122 Read {
123 path: PathBuf,
125 #[source]
127 source: std::io::Error,
128 },
129
130 #[error("Failed to parse configuration: {source}")]
132 Parse {
133 #[source]
135 source: serde_yaml::Error,
136 },
137
138 #[error("Invalid configuration: {message}")]
140 Validation {
141 message: String,
143 },
144}
145
146pub type Result<T> = std::result::Result<T, ScopeError>;
161
162#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn test_bca_error_display_invalid_address() {
172 let err = ScopeError::InvalidAddress("not-an-address".into());
173 assert_eq!(err.to_string(), "Invalid address format: not-an-address");
174 }
175
176 #[test]
177 fn test_bca_error_display_invalid_hash() {
178 let err = ScopeError::InvalidHash("bad-hash".into());
179 assert_eq!(err.to_string(), "Invalid transaction hash: bad-hash");
180 }
181
182 #[test]
183 fn test_bca_error_display_chain() {
184 let err = ScopeError::Chain("connection refused".into());
185 assert_eq!(err.to_string(), "Chain client error: connection refused");
186 }
187
188 #[test]
189 fn test_bca_error_display_export() {
190 let err = ScopeError::Export("failed to write CSV".into());
191 assert_eq!(err.to_string(), "Export failed: failed to write CSV");
192 }
193
194 #[test]
195 fn test_config_error_not_found() {
196 let err = ConfigError::NotFound {
197 path: PathBuf::from("/missing/config.yaml"),
198 };
199 assert_eq!(
200 err.to_string(),
201 "Configuration file not found: /missing/config.yaml"
202 );
203 }
204
205 #[test]
206 fn test_config_error_validation() {
207 let err = ConfigError::Validation {
208 message: "ethereum_rpc must be a valid URL".into(),
209 };
210 assert_eq!(
211 err.to_string(),
212 "Invalid configuration: ethereum_rpc must be a valid URL"
213 );
214 }
215
216 #[test]
217 fn test_bca_error_from_config_error() {
218 let config_err = ConfigError::NotFound {
219 path: PathBuf::from("/test"),
220 };
221 let bca_err: ScopeError = config_err.into();
222 assert!(matches!(bca_err, ScopeError::Config(_)));
223 }
224
225 #[test]
226 fn test_bca_error_from_io_error() {
227 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
228 let bca_err: ScopeError = io_err.into();
229 assert!(matches!(bca_err, ScopeError::IoError(_)));
230 }
231
232 #[test]
233 fn test_result_type_alias_ok() {
234 fn returns_ok() -> Result<i32> {
235 Ok(42)
236 }
237 assert_eq!(returns_ok().unwrap(), 42);
238 }
239
240 #[test]
241 fn test_result_type_alias_err() {
242 fn returns_err() -> Result<i32> {
243 Err(ScopeError::Chain("test error".into()))
244 }
245 assert!(returns_err().is_err());
246 }
247
248 #[test]
249 fn test_error_debug_impl() {
250 let err = ScopeError::InvalidAddress("0x123".into());
251 let debug_str = format!("{:?}", err);
252 assert!(debug_str.contains("InvalidAddress"));
253 assert!(debug_str.contains("0x123"));
254 }
255
256 #[test]
257 fn test_config_error_source_chain() {
258 use std::error::Error;
259
260 let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied");
261 let config_err = ConfigError::Read {
262 path: PathBuf::from("/etc/bca/config.yaml"),
263 source: io_err,
264 };
265
266 assert!(config_err.source().is_some());
268 }
269}