1use crate::{StarError, StarResult};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::fmt;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct ErrorContext {
14 pub source: Option<String>,
16 pub line: Option<usize>,
18 pub column: Option<usize>,
20 pub snippet: Option<String>,
22 pub operation: Option<String>,
24 pub metadata: HashMap<String, String>,
26}
27
28impl ErrorContext {
29 pub fn new() -> Self {
31 Self {
32 source: None,
33 line: None,
34 column: None,
35 snippet: None,
36 operation: None,
37 metadata: HashMap::new(),
38 }
39 }
40
41 pub fn with_source(mut self, source: impl Into<String>) -> Self {
43 self.source = Some(source.into());
44 self
45 }
46
47 pub fn with_position(mut self, line: usize, column: usize) -> Self {
49 self.line = Some(line);
50 self.column = Some(column);
51 self
52 }
53
54 pub fn with_snippet(mut self, snippet: impl Into<String>) -> Self {
56 self.snippet = Some(snippet.into());
57 self
58 }
59
60 pub fn with_operation(mut self, operation: impl Into<String>) -> Self {
62 self.operation = Some(operation.into());
63 self
64 }
65
66 pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
68 self.metadata.insert(key.into(), value.into());
69 self
70 }
71}
72
73impl Default for ErrorContext {
74 fn default() -> Self {
75 Self::new()
76 }
77}
78
79#[derive(Debug)]
81pub struct EnhancedError {
82 pub error: StarError,
84 pub context: Box<ErrorContext>,
86 pub severity: ErrorSeverity,
88 pub category: ErrorCategory,
90 pub suggestions: Box<Vec<String>>,
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
96pub enum ErrorSeverity {
97 Critical,
99 Error,
101 Warning,
103 Info,
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
109pub enum ErrorCategory {
110 Syntax,
112 Semantic,
114 Configuration,
116 Runtime,
118 IO,
120 Network,
122}
123
124impl EnhancedError {
125 pub fn new(error: StarError) -> Self {
127 let (severity, category) = Self::classify_error(&error);
128 Self {
129 error,
130 context: Box::new(ErrorContext::new()),
131 severity,
132 category,
133 suggestions: Box::new(Vec::new()),
134 }
135 }
136
137 pub fn with_context(mut self, context: ErrorContext) -> Self {
139 self.context = Box::new(context);
140 self
141 }
142
143 pub fn with_severity(mut self, severity: ErrorSeverity) -> Self {
145 self.severity = severity;
146 self
147 }
148
149 pub fn with_suggestion(mut self, suggestion: impl Into<String>) -> Self {
151 self.suggestions.push(suggestion.into());
152 self
153 }
154
155 pub fn with_suggestions(mut self, suggestions: Vec<String>) -> Self {
157 self.suggestions.extend(suggestions);
158 self
159 }
160
161 fn classify_error(error: &StarError) -> (ErrorSeverity, ErrorCategory) {
163 match error {
164 StarError::InvalidQuotedTriple { .. } => {
165 (ErrorSeverity::Error, ErrorCategory::Semantic)
166 }
167 StarError::ParseError(_) => (ErrorSeverity::Error, ErrorCategory::Syntax),
168 StarError::SerializationError { .. } => (ErrorSeverity::Error, ErrorCategory::Runtime),
169 StarError::QueryError { .. } => (ErrorSeverity::Error, ErrorCategory::Syntax),
170 StarError::CoreError(_) => (ErrorSeverity::Error, ErrorCategory::Runtime),
171 StarError::ReificationError { .. } => (ErrorSeverity::Error, ErrorCategory::Semantic),
172 StarError::InvalidTermType { .. } => (ErrorSeverity::Error, ErrorCategory::Semantic),
173 StarError::NestingDepthExceeded { .. } => {
174 (ErrorSeverity::Error, ErrorCategory::Configuration)
175 }
176 StarError::UnsupportedFormat { .. } => {
177 (ErrorSeverity::Error, ErrorCategory::Configuration)
178 }
179 StarError::ConfigurationError { .. } => {
180 (ErrorSeverity::Error, ErrorCategory::Configuration)
181 }
182 StarError::InternalError { .. } => (ErrorSeverity::Critical, ErrorCategory::Runtime),
183 }
184 }
185
186 pub fn formatted_message(&self) -> String {
188 let mut message = format!("[{}] {}", self.severity_label(), self.error);
189
190 if let Some(source) = &self.context.source {
192 message.push_str(&format!("\n Source: {source}"));
193 }
194
195 if let (Some(line), Some(column)) = (self.context.line, self.context.column) {
196 message.push_str(&format!("\n Location: line {line}, column {column}"));
197 }
198
199 if let Some(operation) = &self.context.operation {
200 message.push_str(&format!("\n Operation: {operation}"));
201 }
202
203 if let Some(snippet) = &self.context.snippet {
204 message.push_str(&format!(
205 "\n Context:\n {}",
206 snippet.replace('\n', "\n ")
207 ));
208 }
209
210 if !self.context.metadata.is_empty() {
212 message.push_str("\n Details:");
213 for (key, value) in &self.context.metadata {
214 message.push_str(&format!("\n {key}: {value}"));
215 }
216 }
217
218 if !self.suggestions.is_empty() {
220 message.push_str("\n Suggestions:");
221 for (i, suggestion) in self.suggestions.iter().enumerate() {
222 message.push_str(&format!("\n {}. {suggestion}", i + 1));
223 }
224 }
225
226 message
227 }
228
229 fn severity_label(&self) -> &'static str {
231 match self.severity {
232 ErrorSeverity::Critical => "CRITICAL",
233 ErrorSeverity::Error => "ERROR",
234 ErrorSeverity::Warning => "WARNING",
235 ErrorSeverity::Info => "INFO",
236 }
237 }
238
239 pub fn to_json(&self) -> serde_json::Value {
241 serde_json::json!({
242 "severity": self.severity,
243 "category": self.category,
244 "message": self.error.to_string(),
245 "context": {
246 "source": self.context.source,
247 "line": self.context.line,
248 "column": self.context.column,
249 "snippet": self.context.snippet,
250 "operation": self.context.operation,
251 "metadata": self.context.metadata
252 },
253 "suggestions": self.suggestions
254 })
255 }
256}
257
258impl fmt::Display for EnhancedError {
259 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260 write!(f, "{}", self.formatted_message())
261 }
262}
263
264#[derive(Debug, Default)]
266pub struct ErrorAggregator {
267 errors: Vec<EnhancedError>,
268 warnings: Vec<EnhancedError>,
269 max_errors: Option<usize>,
270 max_warnings: Option<usize>,
271}
272
273impl ErrorAggregator {
274 pub fn new() -> Self {
276 Self {
277 errors: Vec::new(),
278 warnings: Vec::new(),
279 max_errors: None,
280 max_warnings: None,
281 }
282 }
283
284 pub fn with_max_errors(mut self, max: usize) -> Self {
286 self.max_errors = Some(max);
287 self
288 }
289
290 pub fn with_max_warnings(mut self, max: usize) -> Self {
292 self.max_warnings = Some(max);
293 self
294 }
295
296 pub fn add_error(&mut self, error: EnhancedError) {
298 match error.severity {
299 ErrorSeverity::Critical | ErrorSeverity::Error => {
300 if let Some(max) = self.max_errors {
301 if self.errors.len() >= max {
302 return;
303 }
304 }
305 self.errors.push(error);
306 }
307 ErrorSeverity::Warning | ErrorSeverity::Info => {
308 if let Some(max) = self.max_warnings {
309 if self.warnings.len() >= max {
310 return;
311 }
312 }
313 self.warnings.push(error);
314 }
315 }
316 }
317
318 pub fn add_star_error(&mut self, error: StarError, context: Option<ErrorContext>) {
320 let enhanced = if let Some(ctx) = context {
321 EnhancedError::new(error).with_context(ctx)
322 } else {
323 EnhancedError::new(error)
324 };
325 self.add_error(enhanced);
326 }
327
328 pub fn has_errors(&self) -> bool {
330 !self.errors.is_empty()
331 }
332
333 pub fn has_warnings(&self) -> bool {
335 !self.warnings.is_empty()
336 }
337
338 pub fn error_count(&self) -> usize {
340 self.errors.len()
341 }
342
343 pub fn warning_count(&self) -> usize {
345 self.warnings.len()
346 }
347
348 pub fn errors(&self) -> &[EnhancedError] {
350 &self.errors
351 }
352
353 pub fn warnings(&self) -> &[EnhancedError] {
355 &self.warnings
356 }
357
358 pub fn generate_report(&self) -> String {
360 let mut report = String::new();
361
362 report.push_str(&format!(
364 "Error Report: {} error(s), {} warning(s)\n",
365 self.errors.len(),
366 self.warnings.len()
367 ));
368 report.push_str("═".repeat(50).as_str());
369 report.push('\n');
370
371 if !self.errors.is_empty() {
373 report.push_str("\nERRORS:\n");
374 for (i, error) in self.errors.iter().enumerate() {
375 report.push_str(&format!("\n{}. {}\n", i + 1, error.formatted_message()));
376 }
377 }
378
379 if !self.warnings.is_empty() {
381 report.push_str("\nWARNINGS:\n");
382 for (i, warning) in self.warnings.iter().enumerate() {
383 report.push_str(&format!("\n{}. {}\n", i + 1, warning.formatted_message()));
384 }
385 }
386
387 report
388 }
389
390 pub fn generate_json_report(&self) -> serde_json::Value {
392 serde_json::json!({
393 "summary": {
394 "error_count": self.errors.len(),
395 "warning_count": self.warnings.len(),
396 "has_errors": self.has_errors()
397 },
398 "errors": self.errors.iter().map(|e| e.to_json()).collect::<Vec<_>>(),
399 "warnings": self.warnings.iter().map(|w| w.to_json()).collect::<Vec<_>>()
400 })
401 }
402
403 pub fn clear(&mut self) {
405 self.errors.clear();
406 self.warnings.clear();
407 }
408}
409
410pub type EnhancedResult<T> = Result<T, EnhancedError>;
412
413pub trait WithErrorContext<T> {
415 fn with_context(self, context: ErrorContext) -> EnhancedResult<T>;
417
418 fn with_context_and_suggestions(
420 self,
421 context: ErrorContext,
422 suggestions: Vec<String>,
423 ) -> EnhancedResult<T>;
424}
425
426impl<T> WithErrorContext<T> for StarResult<T> {
427 fn with_context(self, context: ErrorContext) -> EnhancedResult<T> {
428 self.map_err(|e| EnhancedError::new(e).with_context(context))
429 }
430
431 fn with_context_and_suggestions(
432 self,
433 context: ErrorContext,
434 suggestions: Vec<String>,
435 ) -> EnhancedResult<T> {
436 self.map_err(|e| {
437 EnhancedError::new(e)
438 .with_context(context)
439 .with_suggestions(suggestions)
440 })
441 }
442}
443
444#[cfg(test)]
445mod tests {
446 use super::*;
447
448 #[test]
449 fn test_error_context_builder() {
450 let context = ErrorContext::new()
451 .with_source("test.ttls")
452 .with_position(10, 5)
453 .with_snippet("<< ex:alice ex:knows ex:bob >> ex:certainty 0.9 .")
454 .with_operation("parsing")
455 .with_metadata("format", "turtle-star");
456
457 assert_eq!(context.source, Some("test.ttls".to_string()));
458 assert_eq!(context.line, Some(10));
459 assert_eq!(context.column, Some(5));
460 assert!(context.snippet.is_some());
461 assert_eq!(context.operation, Some("parsing".to_string()));
462 assert_eq!(
463 context.metadata.get("format"),
464 Some(&"turtle-star".to_string())
465 );
466 }
467
468 #[test]
469 fn test_enhanced_error_classification() {
470 let parse_error = StarError::parse_error("Invalid syntax");
471 let enhanced = EnhancedError::new(parse_error);
472
473 assert_eq!(enhanced.severity, ErrorSeverity::Error);
474 assert_eq!(enhanced.category, ErrorCategory::Syntax);
475 }
476
477 #[test]
478 fn test_error_aggregator() {
479 let mut aggregator = ErrorAggregator::new().with_max_errors(2);
480
481 aggregator.add_star_error(StarError::parse_error("Error 1"), None);
482 aggregator.add_star_error(StarError::parse_error("Error 2"), None);
483 aggregator.add_star_error(StarError::parse_error("Error 3"), None); assert_eq!(aggregator.error_count(), 2);
486 assert!(aggregator.has_errors());
487 }
488
489 #[test]
490 fn test_enhanced_error_formatting() {
491 let context = ErrorContext::new()
492 .with_source("test.ttls")
493 .with_position(5, 10);
494
495 let enhanced = EnhancedError::new(StarError::parse_error("Invalid token"))
496 .with_context(context)
497 .with_suggestion("Check syntax around line 5");
498
499 let message = enhanced.formatted_message();
500 assert!(message.contains("ERROR"));
501 assert!(message.contains("test.ttls"));
502 assert!(message.contains("line 5, column 10"));
503 assert!(message.contains("Check syntax"));
504 }
505
506 #[test]
507 fn test_json_report_generation() {
508 let mut aggregator = ErrorAggregator::new();
509 aggregator.add_star_error(StarError::parse_error("Test error"), None);
510
511 let report = aggregator.generate_json_report();
512 assert_eq!(report["summary"]["error_count"], 1);
513 assert!(report["errors"].is_array());
514 }
515}