use crate::{StarError, StarResult};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorContext {
pub source: Option<String>,
pub line: Option<usize>,
pub column: Option<usize>,
pub snippet: Option<String>,
pub operation: Option<String>,
pub metadata: HashMap<String, String>,
}
impl ErrorContext {
pub fn new() -> Self {
Self {
source: None,
line: None,
column: None,
snippet: None,
operation: None,
metadata: HashMap::new(),
}
}
pub fn with_source(mut self, source: impl Into<String>) -> Self {
self.source = Some(source.into());
self
}
pub fn with_position(mut self, line: usize, column: usize) -> Self {
self.line = Some(line);
self.column = Some(column);
self
}
pub fn with_snippet(mut self, snippet: impl Into<String>) -> Self {
self.snippet = Some(snippet.into());
self
}
pub fn with_operation(mut self, operation: impl Into<String>) -> Self {
self.operation = Some(operation.into());
self
}
pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
}
impl Default for ErrorContext {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct EnhancedError {
pub error: StarError,
pub context: Box<ErrorContext>,
pub severity: ErrorSeverity,
pub category: ErrorCategory,
pub suggestions: Box<Vec<String>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ErrorSeverity {
Critical,
Error,
Warning,
Info,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ErrorCategory {
Syntax,
Semantic,
Configuration,
Runtime,
IO,
Network,
}
impl EnhancedError {
pub fn new(error: StarError) -> Self {
let (severity, category) = Self::classify_error(&error);
Self {
error,
context: Box::new(ErrorContext::new()),
severity,
category,
suggestions: Box::new(Vec::new()),
}
}
pub fn with_context(mut self, context: ErrorContext) -> Self {
self.context = Box::new(context);
self
}
pub fn with_severity(mut self, severity: ErrorSeverity) -> Self {
self.severity = severity;
self
}
pub fn with_suggestion(mut self, suggestion: impl Into<String>) -> Self {
self.suggestions.push(suggestion.into());
self
}
pub fn with_suggestions(mut self, suggestions: Vec<String>) -> Self {
self.suggestions.extend(suggestions);
self
}
fn classify_error(error: &StarError) -> (ErrorSeverity, ErrorCategory) {
match error {
StarError::InvalidQuotedTriple { .. } => {
(ErrorSeverity::Error, ErrorCategory::Semantic)
}
StarError::ParseError(_) => (ErrorSeverity::Error, ErrorCategory::Syntax),
StarError::SerializationError { .. } => (ErrorSeverity::Error, ErrorCategory::Runtime),
StarError::QueryError { .. } => (ErrorSeverity::Error, ErrorCategory::Syntax),
StarError::CoreError(_) => (ErrorSeverity::Error, ErrorCategory::Runtime),
StarError::ReificationError { .. } => (ErrorSeverity::Error, ErrorCategory::Semantic),
StarError::InvalidTermType { .. } => (ErrorSeverity::Error, ErrorCategory::Semantic),
StarError::NestingDepthExceeded { .. } => {
(ErrorSeverity::Error, ErrorCategory::Configuration)
}
StarError::UnsupportedFormat { .. } => {
(ErrorSeverity::Error, ErrorCategory::Configuration)
}
StarError::ConfigurationError { .. } => {
(ErrorSeverity::Error, ErrorCategory::Configuration)
}
StarError::InternalError { .. } => (ErrorSeverity::Critical, ErrorCategory::Runtime),
}
}
pub fn formatted_message(&self) -> String {
let mut message = format!("[{}] {}", self.severity_label(), self.error);
if let Some(source) = &self.context.source {
message.push_str(&format!("\n Source: {source}"));
}
if let (Some(line), Some(column)) = (self.context.line, self.context.column) {
message.push_str(&format!("\n Location: line {line}, column {column}"));
}
if let Some(operation) = &self.context.operation {
message.push_str(&format!("\n Operation: {operation}"));
}
if let Some(snippet) = &self.context.snippet {
message.push_str(&format!(
"\n Context:\n {}",
snippet.replace('\n', "\n ")
));
}
if !self.context.metadata.is_empty() {
message.push_str("\n Details:");
for (key, value) in &self.context.metadata {
message.push_str(&format!("\n {key}: {value}"));
}
}
if !self.suggestions.is_empty() {
message.push_str("\n Suggestions:");
for (i, suggestion) in self.suggestions.iter().enumerate() {
message.push_str(&format!("\n {}. {suggestion}", i + 1));
}
}
message
}
fn severity_label(&self) -> &'static str {
match self.severity {
ErrorSeverity::Critical => "CRITICAL",
ErrorSeverity::Error => "ERROR",
ErrorSeverity::Warning => "WARNING",
ErrorSeverity::Info => "INFO",
}
}
pub fn to_json(&self) -> serde_json::Value {
serde_json::json!({
"severity": self.severity,
"category": self.category,
"message": self.error.to_string(),
"context": {
"source": self.context.source,
"line": self.context.line,
"column": self.context.column,
"snippet": self.context.snippet,
"operation": self.context.operation,
"metadata": self.context.metadata
},
"suggestions": self.suggestions
})
}
}
impl fmt::Display for EnhancedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.formatted_message())
}
}
#[derive(Debug, Default)]
pub struct ErrorAggregator {
errors: Vec<EnhancedError>,
warnings: Vec<EnhancedError>,
max_errors: Option<usize>,
max_warnings: Option<usize>,
}
impl ErrorAggregator {
pub fn new() -> Self {
Self {
errors: Vec::new(),
warnings: Vec::new(),
max_errors: None,
max_warnings: None,
}
}
pub fn with_max_errors(mut self, max: usize) -> Self {
self.max_errors = Some(max);
self
}
pub fn with_max_warnings(mut self, max: usize) -> Self {
self.max_warnings = Some(max);
self
}
pub fn add_error(&mut self, error: EnhancedError) {
match error.severity {
ErrorSeverity::Critical | ErrorSeverity::Error => {
if let Some(max) = self.max_errors {
if self.errors.len() >= max {
return;
}
}
self.errors.push(error);
}
ErrorSeverity::Warning | ErrorSeverity::Info => {
if let Some(max) = self.max_warnings {
if self.warnings.len() >= max {
return;
}
}
self.warnings.push(error);
}
}
}
pub fn add_star_error(&mut self, error: StarError, context: Option<ErrorContext>) {
let enhanced = if let Some(ctx) = context {
EnhancedError::new(error).with_context(ctx)
} else {
EnhancedError::new(error)
};
self.add_error(enhanced);
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
pub fn has_warnings(&self) -> bool {
!self.warnings.is_empty()
}
pub fn error_count(&self) -> usize {
self.errors.len()
}
pub fn warning_count(&self) -> usize {
self.warnings.len()
}
pub fn errors(&self) -> &[EnhancedError] {
&self.errors
}
pub fn warnings(&self) -> &[EnhancedError] {
&self.warnings
}
pub fn generate_report(&self) -> String {
let mut report = String::new();
report.push_str(&format!(
"Error Report: {} error(s), {} warning(s)\n",
self.errors.len(),
self.warnings.len()
));
report.push_str("═".repeat(50).as_str());
report.push('\n');
if !self.errors.is_empty() {
report.push_str("\nERRORS:\n");
for (i, error) in self.errors.iter().enumerate() {
report.push_str(&format!("\n{}. {}\n", i + 1, error.formatted_message()));
}
}
if !self.warnings.is_empty() {
report.push_str("\nWARNINGS:\n");
for (i, warning) in self.warnings.iter().enumerate() {
report.push_str(&format!("\n{}. {}\n", i + 1, warning.formatted_message()));
}
}
report
}
pub fn generate_json_report(&self) -> serde_json::Value {
serde_json::json!({
"summary": {
"error_count": self.errors.len(),
"warning_count": self.warnings.len(),
"has_errors": self.has_errors()
},
"errors": self.errors.iter().map(|e| e.to_json()).collect::<Vec<_>>(),
"warnings": self.warnings.iter().map(|w| w.to_json()).collect::<Vec<_>>()
})
}
pub fn clear(&mut self) {
self.errors.clear();
self.warnings.clear();
}
}
pub type EnhancedResult<T> = Result<T, EnhancedError>;
pub trait WithErrorContext<T> {
fn with_context(self, context: ErrorContext) -> EnhancedResult<T>;
fn with_context_and_suggestions(
self,
context: ErrorContext,
suggestions: Vec<String>,
) -> EnhancedResult<T>;
}
impl<T> WithErrorContext<T> for StarResult<T> {
fn with_context(self, context: ErrorContext) -> EnhancedResult<T> {
self.map_err(|e| EnhancedError::new(e).with_context(context))
}
fn with_context_and_suggestions(
self,
context: ErrorContext,
suggestions: Vec<String>,
) -> EnhancedResult<T> {
self.map_err(|e| {
EnhancedError::new(e)
.with_context(context)
.with_suggestions(suggestions)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_context_builder() {
let context = ErrorContext::new()
.with_source("test.ttls")
.with_position(10, 5)
.with_snippet("<< ex:alice ex:knows ex:bob >> ex:certainty 0.9 .")
.with_operation("parsing")
.with_metadata("format", "turtle-star");
assert_eq!(context.source, Some("test.ttls".to_string()));
assert_eq!(context.line, Some(10));
assert_eq!(context.column, Some(5));
assert!(context.snippet.is_some());
assert_eq!(context.operation, Some("parsing".to_string()));
assert_eq!(
context.metadata.get("format"),
Some(&"turtle-star".to_string())
);
}
#[test]
fn test_enhanced_error_classification() {
let parse_error = StarError::parse_error("Invalid syntax");
let enhanced = EnhancedError::new(parse_error);
assert_eq!(enhanced.severity, ErrorSeverity::Error);
assert_eq!(enhanced.category, ErrorCategory::Syntax);
}
#[test]
fn test_error_aggregator() {
let mut aggregator = ErrorAggregator::new().with_max_errors(2);
aggregator.add_star_error(StarError::parse_error("Error 1"), None);
aggregator.add_star_error(StarError::parse_error("Error 2"), None);
aggregator.add_star_error(StarError::parse_error("Error 3"), None);
assert_eq!(aggregator.error_count(), 2);
assert!(aggregator.has_errors());
}
#[test]
fn test_enhanced_error_formatting() {
let context = ErrorContext::new()
.with_source("test.ttls")
.with_position(5, 10);
let enhanced = EnhancedError::new(StarError::parse_error("Invalid token"))
.with_context(context)
.with_suggestion("Check syntax around line 5");
let message = enhanced.formatted_message();
assert!(message.contains("ERROR"));
assert!(message.contains("test.ttls"));
assert!(message.contains("line 5, column 10"));
assert!(message.contains("Check syntax"));
}
#[test]
fn test_json_report_generation() {
let mut aggregator = ErrorAggregator::new();
aggregator.add_star_error(StarError::parse_error("Test error"), None);
let report = aggregator.generate_json_report();
assert_eq!(report["summary"]["error_count"], 1);
assert!(report["errors"].is_array());
}
}