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