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("Compliance error: {0}")]
104 Compliance(String),
105
106 #[error("Discovery error: {0}")]
108 Discovery(String),
109
110 #[error("Contract error: {0}")]
112 Contract(String),
113
114 #[error("{0}")]
116 Other(String),
117}
118
119#[derive(Debug, Error)]
124pub enum ConfigError {
125 #[error("Configuration file not found: {path}")]
127 NotFound {
128 path: PathBuf,
130 },
131
132 #[error("Failed to read configuration file '{path}': {source}")]
134 Read {
135 path: PathBuf,
137 #[source]
139 source: std::io::Error,
140 },
141
142 #[error("Failed to parse configuration: {source}")]
144 Parse {
145 #[source]
147 source: serde_yaml::Error,
148 },
149
150 #[error("Invalid configuration: {message}")]
152 Validation {
153 message: String,
155 },
156}
157
158pub type Result<T> = std::result::Result<T, ScopeError>;
173
174#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn test_bca_error_display_invalid_address() {
184 let err = ScopeError::InvalidAddress("not-an-address".into());
185 assert_eq!(err.to_string(), "Invalid address format: not-an-address");
186 }
187
188 #[test]
189 fn test_bca_error_display_invalid_hash() {
190 let err = ScopeError::InvalidHash("bad-hash".into());
191 assert_eq!(err.to_string(), "Invalid transaction hash: bad-hash");
192 }
193
194 #[test]
195 fn test_bca_error_display_chain() {
196 let err = ScopeError::Chain("connection refused".into());
197 assert_eq!(err.to_string(), "Chain client error: connection refused");
198 }
199
200 #[test]
201 fn test_bca_error_display_export() {
202 let err = ScopeError::Export("failed to write CSV".into());
203 assert_eq!(err.to_string(), "Export failed: failed to write CSV");
204 }
205
206 #[test]
207 fn test_config_error_not_found() {
208 let err = ConfigError::NotFound {
209 path: PathBuf::from("/missing/config.yaml"),
210 };
211 assert_eq!(
212 err.to_string(),
213 "Configuration file not found: /missing/config.yaml"
214 );
215 }
216
217 #[test]
218 fn test_config_error_validation() {
219 let err = ConfigError::Validation {
220 message: "ethereum_rpc must be a valid URL".into(),
221 };
222 assert_eq!(
223 err.to_string(),
224 "Invalid configuration: ethereum_rpc must be a valid URL"
225 );
226 }
227
228 #[test]
229 fn test_bca_error_from_config_error() {
230 let config_err = ConfigError::NotFound {
231 path: PathBuf::from("/test"),
232 };
233 let bca_err: ScopeError = config_err.into();
234 assert!(matches!(bca_err, ScopeError::Config(_)));
235 }
236
237 #[test]
238 fn test_bca_error_from_io_error() {
239 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
240 let bca_err: ScopeError = io_err.into();
241 assert!(matches!(bca_err, ScopeError::IoError(_)));
242 }
243
244 #[test]
245 fn test_result_type_alias_ok() {
246 fn returns_ok() -> Result<i32> {
247 Ok(42)
248 }
249 assert_eq!(returns_ok().unwrap(), 42);
250 }
251
252 #[test]
253 fn test_result_type_alias_err() {
254 fn returns_err() -> Result<i32> {
255 Err(ScopeError::Chain("test error".into()))
256 }
257 assert!(returns_err().is_err());
258 }
259
260 #[test]
261 fn test_error_debug_impl() {
262 let err = ScopeError::InvalidAddress("0x123".into());
263 let debug_str = format!("{:?}", err);
264 assert!(debug_str.contains("InvalidAddress"));
265 assert!(debug_str.contains("0x123"));
266 }
267
268 #[test]
269 fn test_config_error_source_chain() {
270 use std::error::Error;
271
272 let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied");
273 let config_err = ConfigError::Read {
274 path: PathBuf::from("/etc/scope/config.yaml"),
275 source: io_err,
276 };
277
278 assert!(config_err.source().is_some());
280 }
281}