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