1use std::io;
7use std::path::PathBuf;
8use thiserror::Error;
9
10pub type Result<T> = std::result::Result<T, ScribeError>;
12
13#[derive(Error, Debug)]
15pub enum ScribeError {
16 #[error("I/O error: {message}")]
18 Io {
19 message: String,
20 #[source]
21 source: io::Error,
22 },
23
24 #[error("Path error: {message} (path: {path:?})")]
26 Path {
27 message: String,
28 path: PathBuf,
29 #[source]
30 source: Option<io::Error>,
31 },
32
33 #[error("Git error: {message}")]
35 Git {
36 message: String,
37 #[source]
38 source: Option<Box<dyn std::error::Error + Send + Sync>>,
39 },
40
41 #[error("Configuration error: {message}")]
43 Config {
44 message: String,
45 field: Option<String>,
46 },
47
48 #[error("Analysis error: {message} (file: {file:?})")]
50 Analysis {
51 message: String,
52 file: PathBuf,
53 #[source]
54 source: Option<Box<dyn std::error::Error + Send + Sync>>,
55 },
56
57 #[error("Scoring error: {message}")]
59 Scoring {
60 message: String,
61 context: Option<String>,
62 },
63
64 #[error("Graph error: {message}")]
66 Graph {
67 message: String,
68 details: Option<String>,
69 },
70
71 #[error("Pattern error: {message} (pattern: {pattern})")]
73 Pattern {
74 message: String,
75 pattern: String,
76 #[source]
77 source: Option<Box<dyn std::error::Error + Send + Sync>>,
78 },
79
80 #[error("Serialization error: {message}")]
82 Serialization {
83 message: String,
84 #[source]
85 source: Option<Box<dyn std::error::Error + Send + Sync>>,
86 },
87
88 #[error("Concurrency error: {message}")]
90 Concurrency {
91 message: String,
92 #[source]
93 source: Option<Box<dyn std::error::Error + Send + Sync>>,
94 },
95
96 #[error("Resource limit exceeded: {message} (limit: {limit}, actual: {actual})")]
98 ResourceLimit {
99 message: String,
100 limit: u64,
101 actual: u64,
102 },
103
104 #[error("Invalid operation: {message}")]
106 InvalidOperation {
107 message: String,
108 operation: String,
109 },
110
111 #[error("Parse error: {message} (file: {file:?})")]
113 Parse {
114 message: String,
115 file: Option<PathBuf>,
116 #[source]
117 source: Option<Box<dyn std::error::Error + Send + Sync>>,
118 },
119
120 #[cfg(feature = "scaling")]
122 #[error("Scaling error: {message}")]
123 Scaling {
124 message: String,
125 #[source]
126 source: Option<Box<dyn std::error::Error + Send + Sync>>,
127 },
128
129 #[error("Internal error: {message}")]
131 Internal {
132 message: String,
133 location: Option<String>,
134 },
135}
136
137impl ScribeError {
138 pub fn io<S: Into<String>>(message: S, source: io::Error) -> Self {
140 Self::Io {
141 message: message.into(),
142 source,
143 }
144 }
145
146 pub fn path<S: Into<String>, P: Into<PathBuf>>(message: S, path: P) -> Self {
148 Self::Path {
149 message: message.into(),
150 path: path.into(),
151 source: None,
152 }
153 }
154
155 pub fn path_with_source<S: Into<String>, P: Into<PathBuf>>(
157 message: S,
158 path: P,
159 source: io::Error,
160 ) -> Self {
161 Self::Path {
162 message: message.into(),
163 path: path.into(),
164 source: Some(source),
165 }
166 }
167
168 pub fn git<S: Into<String>>(message: S) -> Self {
170 Self::Git {
171 message: message.into(),
172 source: None,
173 }
174 }
175
176 pub fn config<S: Into<String>>(message: S) -> Self {
178 Self::Config {
179 message: message.into(),
180 field: None,
181 }
182 }
183
184 pub fn config_field<S: Into<String>, F: Into<String>>(message: S, field: F) -> Self {
186 Self::Config {
187 message: message.into(),
188 field: Some(field.into()),
189 }
190 }
191
192 pub fn analysis<S: Into<String>, P: Into<PathBuf>>(message: S, file: P) -> Self {
194 Self::Analysis {
195 message: message.into(),
196 file: file.into(),
197 source: None,
198 }
199 }
200
201 pub fn scoring<S: Into<String>>(message: S) -> Self {
203 Self::Scoring {
204 message: message.into(),
205 context: None,
206 }
207 }
208
209 pub fn scoring_with_context<S: Into<String>, C: Into<String>>(message: S, context: C) -> Self {
211 Self::Scoring {
212 message: message.into(),
213 context: Some(context.into()),
214 }
215 }
216
217 pub fn graph<S: Into<String>>(message: S) -> Self {
219 Self::Graph {
220 message: message.into(),
221 details: None,
222 }
223 }
224
225 pub fn pattern<S: Into<String>, P: Into<String>>(message: S, pattern: P) -> Self {
227 Self::Pattern {
228 message: message.into(),
229 pattern: pattern.into(),
230 source: None,
231 }
232 }
233
234 pub fn resource_limit<S: Into<String>>(message: S, limit: u64, actual: u64) -> Self {
236 Self::ResourceLimit {
237 message: message.into(),
238 limit,
239 actual,
240 }
241 }
242
243 pub fn invalid_operation<S: Into<String>, O: Into<String>>(message: S, operation: O) -> Self {
245 Self::InvalidOperation {
246 message: message.into(),
247 operation: operation.into(),
248 }
249 }
250
251 pub fn internal<S: Into<String>>(message: S) -> Self {
253 Self::Internal {
254 message: message.into(),
255 location: None,
256 }
257 }
258
259 pub fn parse<S: Into<String>>(message: S) -> Self {
261 Self::Parse {
262 message: message.into(),
263 file: None,
264 source: None,
265 }
266 }
267
268 pub fn parse_file<S: Into<String>, P: Into<PathBuf>>(message: S, file: P) -> Self {
270 Self::Parse {
271 message: message.into(),
272 file: Some(file.into()),
273 source: None,
274 }
275 }
276
277 pub fn parse_with_source<S: Into<String>>(
279 message: S,
280 source: Box<dyn std::error::Error + Send + Sync>,
281 ) -> Self {
282 Self::Parse {
283 message: message.into(),
284 file: None,
285 source: Some(source),
286 }
287 }
288
289 #[cfg(feature = "scaling")]
291 pub fn scaling<S: Into<String>>(message: S) -> Self {
292 Self::Scaling {
293 message: message.into(),
294 source: None,
295 }
296 }
297
298 #[cfg(feature = "scaling")]
300 pub fn scaling_with_source<S: Into<String>>(
301 message: S,
302 source: Box<dyn std::error::Error + Send + Sync>,
303 ) -> Self {
304 Self::Scaling {
305 message: message.into(),
306 source: Some(source),
307 }
308 }
309
310 pub fn internal_with_location<S: Into<String>, L: Into<String>>(message: S, location: L) -> Self {
312 Self::Internal {
313 message: message.into(),
314 location: Some(location.into()),
315 }
316 }
317}
318
319impl From<io::Error> for ScribeError {
320 fn from(error: io::Error) -> Self {
321 Self::io("I/O operation failed", error)
322 }
323}
324
325impl From<serde_json::Error> for ScribeError {
326 fn from(error: serde_json::Error) -> Self {
327 Self::Serialization {
328 message: "JSON serialization failed".to_string(),
329 source: Some(Box::new(error)),
330 }
331 }
332}
333
334impl From<globset::Error> for ScribeError {
335 fn from(error: globset::Error) -> Self {
336 Self::Pattern {
337 message: "Glob pattern compilation failed".to_string(),
338 pattern: "unknown".to_string(),
339 source: Some(Box::new(error)),
340 }
341 }
342}
343
344impl From<ignore::Error> for ScribeError {
345 fn from(error: ignore::Error) -> Self {
346 Self::Pattern {
347 message: "Ignore pattern error".to_string(),
348 pattern: "unknown".to_string(),
349 source: Some(Box::new(error)),
350 }
351 }
352}
353
354#[cfg(test)]
355mod tests {
356 use super::*;
357 use std::path::Path;
358
359 #[test]
360 fn test_error_creation() {
361 let err = ScribeError::path("Test path error", Path::new("/test/path"));
362 assert!(err.to_string().contains("Test path error"));
363 assert!(err.to_string().contains("/test/path"));
364 }
365
366 #[test]
367 fn test_io_error_conversion() {
368 let io_err = io::Error::new(io::ErrorKind::NotFound, "File not found");
369 let scribe_err = ScribeError::from(io_err);
370 match scribe_err {
371 ScribeError::Io { message, .. } => {
372 assert_eq!(message, "I/O operation failed");
373 }
374 _ => panic!("Expected Io error variant"),
375 }
376 }
377
378 #[test]
379 fn test_resource_limit_error() {
380 let err = ScribeError::resource_limit("File too large", 1000, 2000);
381 let msg = err.to_string();
382 assert!(msg.contains("limit: 1000"));
383 assert!(msg.contains("actual: 2000"));
384 }
385}