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 #[error("Tokenization error: {message}")]
122 Tokenization {
123 message: String,
124 #[source]
125 source: Option<Box<dyn std::error::Error + Send + Sync>>,
126 },
127
128 #[cfg(feature = "scaling")]
130 #[error("Scaling error: {message}")]
131 Scaling {
132 message: String,
133 #[source]
134 source: Option<Box<dyn std::error::Error + Send + Sync>>,
135 },
136
137 #[error("Internal error: {message}")]
139 Internal {
140 message: String,
141 location: Option<String>,
142 },
143}
144
145impl ScribeError {
146 pub fn io<S: Into<String>>(message: S, source: io::Error) -> Self {
148 Self::Io {
149 message: message.into(),
150 source,
151 }
152 }
153
154 pub fn path<S: Into<String>, P: Into<PathBuf>>(message: S, path: P) -> Self {
156 Self::Path {
157 message: message.into(),
158 path: path.into(),
159 source: None,
160 }
161 }
162
163 pub fn path_with_source<S: Into<String>, P: Into<PathBuf>>(
165 message: S,
166 path: P,
167 source: io::Error,
168 ) -> Self {
169 Self::Path {
170 message: message.into(),
171 path: path.into(),
172 source: Some(source),
173 }
174 }
175
176 pub fn git<S: Into<String>>(message: S) -> Self {
178 Self::Git {
179 message: message.into(),
180 source: None,
181 }
182 }
183
184 pub fn config<S: Into<String>>(message: S) -> Self {
186 Self::Config {
187 message: message.into(),
188 field: None,
189 }
190 }
191
192 pub fn config_field<S: Into<String>, F: Into<String>>(message: S, field: F) -> Self {
194 Self::Config {
195 message: message.into(),
196 field: Some(field.into()),
197 }
198 }
199
200 pub fn analysis<S: Into<String>, P: Into<PathBuf>>(message: S, file: P) -> Self {
202 Self::Analysis {
203 message: message.into(),
204 file: file.into(),
205 source: None,
206 }
207 }
208
209 pub fn scoring<S: Into<String>>(message: S) -> Self {
211 Self::Scoring {
212 message: message.into(),
213 context: None,
214 }
215 }
216
217 pub fn scoring_with_context<S: Into<String>, C: Into<String>>(message: S, context: C) -> Self {
219 Self::Scoring {
220 message: message.into(),
221 context: Some(context.into()),
222 }
223 }
224
225 pub fn graph<S: Into<String>>(message: S) -> Self {
227 Self::Graph {
228 message: message.into(),
229 details: None,
230 }
231 }
232
233 pub fn pattern<S: Into<String>, P: Into<String>>(message: S, pattern: P) -> Self {
235 Self::Pattern {
236 message: message.into(),
237 pattern: pattern.into(),
238 source: None,
239 }
240 }
241
242 pub fn resource_limit<S: Into<String>>(message: S, limit: u64, actual: u64) -> Self {
244 Self::ResourceLimit {
245 message: message.into(),
246 limit,
247 actual,
248 }
249 }
250
251 pub fn invalid_operation<S: Into<String>, O: Into<String>>(message: S, operation: O) -> Self {
253 Self::InvalidOperation {
254 message: message.into(),
255 operation: operation.into(),
256 }
257 }
258
259 pub fn internal<S: Into<String>>(message: S) -> Self {
261 Self::Internal {
262 message: message.into(),
263 location: None,
264 }
265 }
266
267 pub fn parse<S: Into<String>>(message: S) -> Self {
269 Self::Parse {
270 message: message.into(),
271 file: None,
272 source: None,
273 }
274 }
275
276 pub fn parse_file<S: Into<String>, P: Into<PathBuf>>(message: S, file: P) -> Self {
278 Self::Parse {
279 message: message.into(),
280 file: Some(file.into()),
281 source: None,
282 }
283 }
284
285 pub fn parse_with_source<S: Into<String>>(
287 message: S,
288 source: Box<dyn std::error::Error + Send + Sync>,
289 ) -> Self {
290 Self::Parse {
291 message: message.into(),
292 file: None,
293 source: Some(source),
294 }
295 }
296
297 pub fn tokenization<S: Into<String>>(message: S) -> Self {
299 Self::Tokenization {
300 message: message.into(),
301 source: None,
302 }
303 }
304
305 pub fn tokenization_with_source<S: Into<String>>(
307 message: S,
308 source: Box<dyn std::error::Error + Send + Sync>,
309 ) -> Self {
310 Self::Tokenization {
311 message: message.into(),
312 source: Some(source),
313 }
314 }
315
316 #[cfg(feature = "scaling")]
318 pub fn scaling<S: Into<String>>(message: S) -> Self {
319 Self::Scaling {
320 message: message.into(),
321 source: None,
322 }
323 }
324
325 #[cfg(feature = "scaling")]
327 pub fn scaling_with_source<S: Into<String>>(
328 message: S,
329 source: Box<dyn std::error::Error + Send + Sync>,
330 ) -> Self {
331 Self::Scaling {
332 message: message.into(),
333 source: Some(source),
334 }
335 }
336
337 pub fn internal_with_location<S: Into<String>, L: Into<String>>(message: S, location: L) -> Self {
339 Self::Internal {
340 message: message.into(),
341 location: Some(location.into()),
342 }
343 }
344}
345
346impl From<io::Error> for ScribeError {
347 fn from(error: io::Error) -> Self {
348 Self::io("I/O operation failed", error)
349 }
350}
351
352impl From<serde_json::Error> for ScribeError {
353 fn from(error: serde_json::Error) -> Self {
354 Self::Serialization {
355 message: "JSON serialization failed".to_string(),
356 source: Some(Box::new(error)),
357 }
358 }
359}
360
361impl From<globset::Error> for ScribeError {
362 fn from(error: globset::Error) -> Self {
363 Self::Pattern {
364 message: "Glob pattern compilation failed".to_string(),
365 pattern: "unknown".to_string(),
366 source: Some(Box::new(error)),
367 }
368 }
369}
370
371impl From<ignore::Error> for ScribeError {
372 fn from(error: ignore::Error) -> Self {
373 Self::Pattern {
374 message: "Ignore pattern error".to_string(),
375 pattern: "unknown".to_string(),
376 source: Some(Box::new(error)),
377 }
378 }
379}
380
381#[cfg(test)]
382mod tests {
383 use super::*;
384 use std::path::Path;
385
386 #[test]
387 fn test_error_creation() {
388 let err = ScribeError::path("Test path error", Path::new("/test/path"));
389 assert!(err.to_string().contains("Test path error"));
390 assert!(err.to_string().contains("/test/path"));
391 }
392
393 #[test]
394 fn test_io_error_conversion() {
395 let io_err = io::Error::new(io::ErrorKind::NotFound, "File not found");
396 let scribe_err = ScribeError::from(io_err);
397 match scribe_err {
398 ScribeError::Io { message, .. } => {
399 assert_eq!(message, "I/O operation failed");
400 }
401 _ => panic!("Expected Io error variant"),
402 }
403 }
404
405 #[test]
406 fn test_resource_limit_error() {
407 let err = ScribeError::resource_limit("File too large", 1000, 2000);
408 let msg = err.to_string();
409 assert!(msg.contains("limit: 1000"));
410 assert!(msg.contains("actual: 2000"));
411 }
412}