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 { message: String, operation: String },
107
108 #[error("Parse error: {message} (file: {file:?})")]
110 Parse {
111 message: String,
112 file: Option<PathBuf>,
113 #[source]
114 source: Option<Box<dyn std::error::Error + Send + Sync>>,
115 },
116
117 #[error("Tokenization error: {message}")]
119 Tokenization {
120 message: String,
121 #[source]
122 source: Option<Box<dyn std::error::Error + Send + Sync>>,
123 },
124
125 #[cfg(feature = "scaling")]
127 #[error("Scaling error: {message}")]
128 Scaling {
129 message: String,
130 #[source]
131 source: Option<Box<dyn std::error::Error + Send + Sync>>,
132 },
133
134 #[error("Internal error: {message}")]
136 Internal {
137 message: String,
138 location: Option<String>,
139 },
140}
141
142impl ScribeError {
143 pub fn io<S: Into<String>>(message: S, source: io::Error) -> Self {
145 Self::Io {
146 message: message.into(),
147 source,
148 }
149 }
150
151 pub fn path<S: Into<String>, P: Into<PathBuf>>(message: S, path: P) -> Self {
153 Self::Path {
154 message: message.into(),
155 path: path.into(),
156 source: None,
157 }
158 }
159
160 pub fn path_with_source<S: Into<String>, P: Into<PathBuf>>(
162 message: S,
163 path: P,
164 source: io::Error,
165 ) -> Self {
166 Self::Path {
167 message: message.into(),
168 path: path.into(),
169 source: Some(source),
170 }
171 }
172
173 pub fn git<S: Into<String>>(message: S) -> Self {
175 Self::Git {
176 message: message.into(),
177 source: None,
178 }
179 }
180
181 pub fn config<S: Into<String>>(message: S) -> Self {
183 Self::Config {
184 message: message.into(),
185 field: None,
186 }
187 }
188
189 pub fn config_field<S: Into<String>, F: Into<String>>(message: S, field: F) -> Self {
191 Self::Config {
192 message: message.into(),
193 field: Some(field.into()),
194 }
195 }
196
197 pub fn analysis<S: Into<String>, P: Into<PathBuf>>(message: S, file: P) -> Self {
199 Self::Analysis {
200 message: message.into(),
201 file: file.into(),
202 source: None,
203 }
204 }
205
206 pub fn scoring<S: Into<String>>(message: S) -> Self {
208 Self::Scoring {
209 message: message.into(),
210 context: None,
211 }
212 }
213
214 pub fn scoring_with_context<S: Into<String>, C: Into<String>>(message: S, context: C) -> Self {
216 Self::Scoring {
217 message: message.into(),
218 context: Some(context.into()),
219 }
220 }
221
222 pub fn graph<S: Into<String>>(message: S) -> Self {
224 Self::Graph {
225 message: message.into(),
226 details: None,
227 }
228 }
229
230 pub fn pattern<S: Into<String>, P: Into<String>>(message: S, pattern: P) -> Self {
232 Self::Pattern {
233 message: message.into(),
234 pattern: pattern.into(),
235 source: None,
236 }
237 }
238
239 pub fn resource_limit<S: Into<String>>(message: S, limit: u64, actual: u64) -> Self {
241 Self::ResourceLimit {
242 message: message.into(),
243 limit,
244 actual,
245 }
246 }
247
248 pub fn invalid_operation<S: Into<String>, O: Into<String>>(message: S, operation: O) -> Self {
250 Self::InvalidOperation {
251 message: message.into(),
252 operation: operation.into(),
253 }
254 }
255
256 pub fn internal<S: Into<String>>(message: S) -> Self {
258 Self::Internal {
259 message: message.into(),
260 location: None,
261 }
262 }
263
264 pub fn parse<S: Into<String>>(message: S) -> Self {
266 Self::Parse {
267 message: message.into(),
268 file: None,
269 source: None,
270 }
271 }
272
273 pub fn parse_file<S: Into<String>, P: Into<PathBuf>>(message: S, file: P) -> Self {
275 Self::Parse {
276 message: message.into(),
277 file: Some(file.into()),
278 source: None,
279 }
280 }
281
282 pub fn parse_with_source<S: Into<String>>(
284 message: S,
285 source: Box<dyn std::error::Error + Send + Sync>,
286 ) -> Self {
287 Self::Parse {
288 message: message.into(),
289 file: None,
290 source: Some(source),
291 }
292 }
293
294 pub fn tokenization<S: Into<String>>(message: S) -> Self {
296 Self::Tokenization {
297 message: message.into(),
298 source: None,
299 }
300 }
301
302 pub fn tokenization_with_source<S: Into<String>>(
304 message: S,
305 source: Box<dyn std::error::Error + Send + Sync>,
306 ) -> Self {
307 Self::Tokenization {
308 message: message.into(),
309 source: Some(source),
310 }
311 }
312
313 #[cfg(feature = "scaling")]
315 pub fn scaling<S: Into<String>>(message: S) -> Self {
316 Self::Scaling {
317 message: message.into(),
318 source: None,
319 }
320 }
321
322 #[cfg(feature = "scaling")]
324 pub fn scaling_with_source<S: Into<String>>(
325 message: S,
326 source: Box<dyn std::error::Error + Send + Sync>,
327 ) -> Self {
328 Self::Scaling {
329 message: message.into(),
330 source: Some(source),
331 }
332 }
333
334 pub fn internal_with_location<S: Into<String>, L: Into<String>>(
336 message: S,
337 location: L,
338 ) -> 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}