devalang_wasm/tools/logger/
structured_error.rs

1/// Structured error information with details and suggestions
2#[derive(Debug, Clone)]
3pub struct StructuredError {
4    /// Main error message
5    pub message: String,
6    /// File path where the error occurred
7    pub file_path: Option<String>,
8    /// Line number in the file
9    pub line: Option<usize>,
10    /// Column number in the file
11    pub column: Option<usize>,
12    /// Error type/category (e.g., "SyntaxError", "UnknownStatement", "RuntimeError")
13    pub error_type: Option<String>,
14    /// Optional "Did you mean ... ?" suggestion
15    pub suggestion: Option<String>,
16    /// Optional stack trace information
17    pub stacktrace: Vec<String>,
18}
19
20impl StructuredError {
21    /// Create a new structured error
22    pub fn new(message: impl Into<String>) -> Self {
23        Self {
24            message: message.into(),
25            file_path: None,
26            line: None,
27            column: None,
28            error_type: None,
29            suggestion: None,
30            stacktrace: Vec::new(),
31        }
32    }
33
34    /// Set file path
35    pub fn with_file(mut self, path: impl Into<String>) -> Self {
36        self.file_path = Some(path.into());
37        self
38    }
39
40    /// Set line and column
41    pub fn with_location(mut self, line: usize, column: usize) -> Self {
42        self.line = Some(line);
43        self.column = Some(column);
44        self
45    }
46
47    /// Set error type
48    pub fn with_type(mut self, error_type: impl Into<String>) -> Self {
49        self.error_type = Some(error_type.into());
50        self
51    }
52
53    /// Set suggestion
54    pub fn with_suggestion(mut self, suggestion: impl Into<String>) -> Self {
55        self.suggestion = Some(suggestion.into());
56        self
57    }
58
59    /// Add a stack trace entry
60    pub fn add_stacktrace(mut self, entry: impl Into<String>) -> Self {
61        self.stacktrace.push(entry.into());
62        self
63    }
64
65    /// Build the formatted error details for display (old format, kept for compatibility)
66    pub fn build_details(&self) -> Vec<String> {
67        let mut details = Vec::new();
68
69        // Location: file:line:column with "path" label
70        if let (Some(file), Some(line), Some(col)) = (&self.file_path, self.line, self.column) {
71            details.push(format!("path: {file}:{line}:{col}"));
72        } else if let (Some(file), Some(line)) = (&self.file_path, self.line) {
73            details.push(format!("path: {file}:{line}"));
74        } else if let Some(file) = &self.file_path {
75            details.push(format!("path: {file}"));
76        }
77
78        // Error type with "code" label
79        if let Some(ref error_type) = self.error_type {
80            details.push(format!("code: {error_type}"));
81        }
82
83        // Stack trace entries
84        for entry in &self.stacktrace {
85            details.push(entry.clone());
86        }
87
88        // Suggestion with "help" label
89        if let Some(ref suggestion) = self.suggestion {
90            details.push(format!("help: {suggestion}"));
91        }
92
93        details
94    }
95
96    /// Build the formatted error details for colored display
97    /// Returns tuples of (label, content) for the logger to format with colors
98    pub fn build_colored_details(&self) -> Vec<(String, String)> {
99        let mut details = Vec::new();
100
101        // Location: file:line:column with "path" label
102        if let (Some(file), Some(line), Some(col)) = (&self.file_path, self.line, self.column) {
103            details.push(("path".to_string(), format!("{file}:{line}:{col}")));
104        } else if let (Some(file), Some(line)) = (&self.file_path, self.line) {
105            details.push(("path".to_string(), format!("{file}:{line}")));
106        } else if let Some(file) = &self.file_path {
107            details.push(("path".to_string(), file.clone()));
108        }
109
110        // Error type with "code" label
111        if let Some(ref error_type) = self.error_type {
112            details.push(("code".to_string(), error_type.clone()));
113        }
114
115        // Stack trace entries (without label)
116        for entry in &self.stacktrace {
117            details.push(("trace".to_string(), entry.clone()));
118        }
119
120        // Suggestion with "help" label
121        if let Some(ref suggestion) = self.suggestion {
122            details.push(("help".to_string(), suggestion.clone()));
123        }
124
125        details
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132
133    #[test]
134    fn test_structured_error_builder() {
135        let error = StructuredError::new("Unknown statement")
136            .with_file("test.deva")
137            .with_location(5, 3)
138            .with_type("SyntaxError")
139            .with_suggestion("Did you mean 'sleep' ?");
140
141        assert_eq!(error.message, "Unknown statement");
142        assert_eq!(error.file_path, Some("test.deva".to_string()));
143        assert_eq!(error.line, Some(5));
144        assert_eq!(error.column, Some(3));
145        assert_eq!(error.error_type, Some("SyntaxError".to_string()));
146        assert_eq!(error.suggestion, Some("Did you mean 'sleep' ?".to_string()));
147    }
148
149    #[test]
150    fn test_build_details() {
151        let error = StructuredError::new("Test error")
152            .with_file("test.deva")
153            .with_location(10, 5)
154            .with_type("RuntimeError")
155            .add_stacktrace("at collector.rs:123")
156            .with_suggestion("Did you mean 'print' ?");
157
158        let details = error.build_details();
159        assert!(details.iter().any(|d| d.contains("at:")));
160        assert!(details.iter().any(|d| d.contains("code:")));
161        assert!(details.iter().any(|d| d.contains("help:")));
162    }
163
164    #[test]
165    fn test_build_colored_details() {
166        let error = StructuredError::new("Test error")
167            .with_file("test.deva")
168            .with_location(10, 5)
169            .with_type("RuntimeError")
170            .with_suggestion("Did you mean 'print' ?");
171
172        let details = error.build_colored_details();
173        assert_eq!(details.len(), 3); // at, code, help
174        assert_eq!(details[0].0, "at");
175        assert_eq!(details[1].0, "code");
176        assert_eq!(details[2].0, "help");
177    }
178}