decrust_core/
reporter.rs

1/* src/reporter.rs */
2#![warn(missing_docs)]
3//! **Brief:** Error reporting utilities for structured error displays.
4// ~=####====A===r===c===M===o===o===n====S===t===u===d===i===o===s====X|0|$>
5//! + [Error Handling Framework]
6//!  - [Error Reporting]
7//!  - [Formatted Output]
8//!  - [Diagnostic Presentation]
9// ~=####====A===r===c===M===o===o===n====S===t===u===d===i===o===s====X|0|$>
10// **GitHub:** [ArcMoon Studios](https://github.com/arcmoonstudios)
11// **Copyright:** (c) 2025 ArcMoon Studios
12// **Author:** Lord Xyn
13// **License:** Business Source License 1.1 (BSL-1.1)
14// **License File:** /LICENSE
15// **License Terms:** Non-production use only; commercial/production use requires a paid license.
16// **Change Date:** 2029-05-25 | **Change License:** GPL v3
17// **Contact:** LordXyn@proton.me
18
19use super::types::ErrorReportFormat;
20use std::io::{self, Write};
21
22/// Configuration for the error reporter
23#[derive(Debug, Clone)]
24pub struct ErrorReportConfig {
25    /// Whether to include the main error message in the report
26    pub include_message: bool,
27    /// Whether to include the chain of source errors in the report
28    pub include_source_chain: bool,
29    /// Whether to include backtrace information in the report
30    pub include_backtrace: bool,
31    /// Whether to include rich context information in the report
32    pub include_rich_context: bool,
33    /// Whether to include source code location information in the report
34    pub include_source_location: bool,
35    /// Whether to include error severity information in the report
36    pub include_severity: bool,
37    /// The output format for the error report
38    pub format: ErrorReportFormat,
39    /// Maximum depth of the error source chain to include (None for unlimited)
40    pub max_chain_depth: Option<usize>,
41    /// Whether to format JSON output with indentation and line breaks
42    pub pretty_print_json: bool,
43    /// Whether to include diagnostic information in the report
44    pub include_diagnostics: bool,
45}
46
47impl Default for ErrorReportConfig {
48    fn default() -> Self {
49        Self {
50            include_message: true,
51            include_source_chain: true,
52            include_backtrace: true,
53            include_rich_context: true,
54            include_source_location: true,
55            include_severity: true,
56            format: ErrorReportFormat::Plain,
57            max_chain_depth: None,
58            pretty_print_json: true,
59            include_diagnostics: true,
60        }
61    }
62}
63
64/// Utility for generating formatted error reports
65#[derive(Debug, Default)]
66pub struct ErrorReporter;
67
68impl ErrorReporter {
69    /// Creates a new ErrorReporter instance
70    ///
71    /// This is a simple constructor that returns a new instance of the ErrorReporter.
72    /// Since ErrorReporter has no state, this is equivalent to using the Default implementation.
73    pub fn new() -> Self {
74        Self
75    }
76
77    /// Report an error to a writer using the provided configuration
78    pub fn report<W, E>(
79        &self,
80        error: &E,
81        config: &ErrorReportConfig,
82        writer: &mut W,
83    ) -> io::Result<()>
84    where
85        W: Write,
86        E: std::error::Error,
87    {
88        match config.format {
89            ErrorReportFormat::Plain => self.report_plain(error, config, writer),
90            ErrorReportFormat::Json => self.report_json(error, config, writer),
91            ErrorReportFormat::Markdown => self.report_markdown(error, config, writer),
92            ErrorReportFormat::Html => self.report_html(error, config, writer),
93        }
94    }
95
96    /// Report an error with syntax highlighting and AST-aware formatting
97    ///
98    /// This method provides enhanced error reporting with syntax highlighting,
99    /// code snippets, and AST-aware formatting for better readability.
100    ///
101    /// # Parameters
102    /// * `error` - The error to report
103    /// * `config` - Configuration for the error report
104    /// * `source_code` - Optional source code context
105    /// * `writer` - The writer to output the report to
106    ///
107    /// # Returns
108    /// IO result indicating success or failure
109    pub fn report_with_syntax<W, E>(
110        &self,
111        error: &E,
112        config: &ErrorReportConfig,
113        source_code: Option<&str>,
114        writer: &mut W,
115    ) -> io::Result<()>
116    where
117        W: Write,
118        E: std::error::Error,
119    {
120        // First write the standard error report
121        self.report(error, config, writer)?;
122
123        // If we have source code context, add syntax-highlighted code snippets
124        if let Some(code) = source_code {
125            match config.format {
126                ErrorReportFormat::Plain => {
127                    writeln!(writer, "\nSource Code Context:")?;
128                    writeln!(writer, "-------------------")?;
129
130                    // Simple line-by-line output for plain text
131                    for (i, line) in code.lines().enumerate() {
132                        writeln!(writer, "{:4} | {}", i + 1, line)?;
133                    }
134                }
135                ErrorReportFormat::Markdown => {
136                    writeln!(writer, "\n### Source Code Context\n")?;
137                    writeln!(writer, "```rust")?;
138                    writeln!(writer, "{}", code)?;
139                    writeln!(writer, "```")?;
140                }
141                ErrorReportFormat::Html => {
142                    writeln!(writer, "<h3>Source Code Context</h3>")?;
143                    writeln!(writer, "<pre class=\"code rust\">")?;
144
145                    // Escape HTML special characters
146                    let escaped_code = code
147                        .replace("&", "&amp;")
148                        .replace("<", "&lt;")
149                        .replace(">", "&gt;");
150
151                    writeln!(writer, "{}", escaped_code)?;
152                    writeln!(writer, "</pre>")?;
153                }
154                ErrorReportFormat::Json => {
155                    // For JSON, we need to modify the existing JSON output
156                    // This is a simplified approach - in a real implementation,
157                    // we would use a proper JSON library
158                    let escaped_code = code.replace("\"", "\\\"").replace("\n", "\\n");
159                    writeln!(writer, "{{ \"source_code\": \"{}\" }}", escaped_code)?;
160                }
161            }
162        }
163
164        Ok(())
165    }
166
167    /// Report an error as a string using the provided configuration
168    pub fn report_to_string<E>(&self, error: &E, config: &ErrorReportConfig) -> String
169    where
170        E: std::error::Error,
171    {
172        let mut buffer = Vec::new();
173        let _ = self.report(error, config, &mut buffer);
174        String::from_utf8_lossy(&buffer).to_string()
175    }
176
177    /// Report an error as a string with syntax highlighting and AST-aware formatting
178    ///
179    /// # Parameters
180    /// * `error` - The error to report
181    /// * `config` - Configuration for the error report
182    /// * `source_code` - Optional source code context
183    ///
184    /// # Returns
185    /// The formatted error report as a string
186    pub fn report_to_string_with_syntax<E>(
187        &self,
188        error: &E,
189        config: &ErrorReportConfig,
190        source_code: Option<&str>,
191    ) -> String
192    where
193        E: std::error::Error,
194    {
195        let mut buffer = Vec::new();
196        let _ = self.report_with_syntax(error, config, source_code, &mut buffer);
197        String::from_utf8_lossy(&buffer).to_string()
198    }
199
200    fn report_plain<W, E>(
201        &self,
202        error: &E,
203        config: &ErrorReportConfig,
204        writer: &mut W,
205    ) -> io::Result<()>
206    where
207        W: Write,
208        E: std::error::Error,
209    {
210        // Implementation of plain text error reporting
211        // This would use the Display or Debug implementations for errors
212        // and format according to the config options
213        writeln!(writer, "Error: {}", error)?;
214
215        // If error supports source(), we can get the cause chain
216        if config.include_source_chain {
217            let mut source = error.source();
218            let mut depth = 0;
219
220            while let Some(err) = source {
221                if let Some(max_depth) = config.max_chain_depth {
222                    if depth >= max_depth {
223                        writeln!(writer, "... (more causes hidden)")?;
224                        break;
225                    }
226                }
227
228                writeln!(writer, "Caused by: {}", err)?;
229                source = err.source();
230                depth += 1;
231            }
232        }
233
234        // If the error has backtrace support (via ErrorCompat trait)
235        // we would include it here
236        if config.include_backtrace {
237            // Placeholder for backtrace implementation
238            // This would be implemented based on how your errors provide backtrace information
239        }
240
241        Ok(())
242    }
243
244    fn report_json<W, E>(
245        &self,
246        error: &E,
247        config: &ErrorReportConfig,
248        writer: &mut W,
249    ) -> io::Result<()>
250    where
251        W: Write,
252        E: std::error::Error,
253    {
254        // Implementation of JSON error reporting
255        let mut json = String::from("{");
256
257        // Add the main error message
258        json.push_str(&format!(
259            "\"error\": \"{}\"",
260            error.to_string().replace("\"", "\\\"")
261        ));
262
263        // Add source chain if configured
264        if config.include_source_chain {
265            json.push_str(", \"causes\": [");
266            let mut source = error.source();
267            let mut is_first = true;
268            let mut depth = 0;
269
270            while let Some(err) = source {
271                if let Some(max_depth) = config.max_chain_depth {
272                    if depth >= max_depth {
273                        break;
274                    }
275                }
276
277                if !is_first {
278                    json.push_str(", ");
279                }
280                json.push_str(&format!("\"{}\"", err.to_string().replace("\"", "\\\"")));
281
282                source = err.source();
283                is_first = false;
284                depth += 1;
285            }
286            json.push_str("]");
287        }
288
289        json.push_str("}");
290
291        // Pretty print if configured
292        if config.pretty_print_json {
293            // This is a very simple pretty print - a real implementation would use a JSON library
294            json = json
295                .replace("{", "{\n  ")
296                .replace("}", "\n}")
297                .replace(", ", ",\n  ");
298        }
299
300        writeln!(writer, "{}", json)?;
301        Ok(())
302    }
303
304    fn report_markdown<W, E>(
305        &self,
306        error: &E,
307        config: &ErrorReportConfig,
308        writer: &mut W,
309    ) -> io::Result<()>
310    where
311        W: Write,
312        E: std::error::Error,
313    {
314        // Implementation of Markdown error reporting
315        writeln!(writer, "## Error\n\n```")?;
316        writeln!(writer, "{}", error)?;
317        writeln!(writer, "```")?;
318
319        // Add source chain if configured
320        if config.include_source_chain {
321            writeln!(writer, "\n### Causes\n")?;
322            let mut source = error.source();
323            let mut depth = 0;
324
325            while let Some(err) = source {
326                if let Some(max_depth) = config.max_chain_depth {
327                    if depth >= max_depth {
328                        writeln!(writer, "... (more causes hidden)")?;
329                        break;
330                    }
331                }
332
333                writeln!(writer, "- {}", err)?;
334                source = err.source();
335                depth += 1;
336            }
337        }
338
339        Ok(())
340    }
341
342    fn report_html<W, E>(
343        &self,
344        error: &E,
345        config: &ErrorReportConfig,
346        writer: &mut W,
347    ) -> io::Result<()>
348    where
349        W: Write,
350        E: std::error::Error,
351    {
352        // Implementation of HTML error reporting
353        writeln!(writer, "<div class=\"error\">")?;
354        writeln!(writer, "  <h3>Error</h3>")?;
355        writeln!(
356            writer,
357            "  <pre>{}</pre>",
358            error.to_string().replace("<", "&lt;").replace(">", "&gt;")
359        )?;
360
361        // Add source chain if configured
362        if config.include_source_chain {
363            writeln!(writer, "  <h4>Causes</h4>")?;
364            writeln!(writer, "  <ul>")?;
365
366            let mut source = error.source();
367            let mut depth = 0;
368
369            while let Some(err) = source {
370                if let Some(max_depth) = config.max_chain_depth {
371                    if depth >= max_depth {
372                        writeln!(writer, "    <li>... (more causes hidden)</li>")?;
373                        break;
374                    }
375                }
376
377                writeln!(
378                    writer,
379                    "    <li>{}</li>",
380                    err.to_string().replace("<", "&lt;").replace(">", "&gt;")
381                )?;
382
383                source = err.source();
384                depth += 1;
385            }
386
387            writeln!(writer, "  </ul>")?;
388        }
389
390        writeln!(writer, "</div>")?;
391        Ok(())
392    }
393}
394
395#[cfg(test)]
396mod tests {
397    use super::*;
398    use std::error::Error;
399    use std::fmt;
400
401    // Simple error type for testing
402    #[derive(Debug)]
403    struct TestError {
404        message: String,
405        source: Option<Box<dyn Error + Send + Sync>>,
406    }
407
408    impl fmt::Display for TestError {
409        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
410            write!(f, "{}", self.message)
411        }
412    }
413
414    impl Error for TestError {
415        fn source(&self) -> Option<&(dyn Error + 'static)> {
416            self.source
417                .as_ref()
418                .map(|s| s.as_ref() as &(dyn Error + 'static))
419        }
420    }
421
422    #[test]
423    fn test_error_reporter_plain_format() {
424        // Create a test error
425        let error = TestError {
426            message: "Test error message".to_string(),
427            source: None,
428        };
429
430        // Create reporter and config
431        let reporter = ErrorReporter::new();
432        let config = ErrorReportConfig {
433            include_message: true,
434            include_source_chain: true,
435            include_backtrace: false,
436            include_rich_context: false,
437            include_source_location: false,
438            include_severity: false,
439            format: ErrorReportFormat::Plain,
440            max_chain_depth: None,
441            pretty_print_json: false,
442            include_diagnostics: false,
443        };
444
445        // Generate report as string
446        let report = reporter.report_to_string(&error, &config);
447
448        // Verify report contains error message
449        assert!(report.contains("Test error message"));
450    }
451
452    #[test]
453    fn test_error_reporter_with_source() {
454        // Create a nested error
455        let source_error = TestError {
456            message: "Source error".to_string(),
457            source: None,
458        };
459
460        let error = TestError {
461            message: "Main error".to_string(),
462            source: Some(Box::new(source_error)),
463        };
464
465        // Create reporter and config
466        let reporter = ErrorReporter::new();
467        let config = ErrorReportConfig {
468            include_message: true,
469            include_source_chain: true,
470            include_backtrace: false,
471            include_rich_context: false,
472            include_source_location: false,
473            include_severity: false,
474            format: ErrorReportFormat::Plain,
475            max_chain_depth: None,
476            pretty_print_json: false,
477            include_diagnostics: false,
478        };
479
480        // Generate report as string
481        let report = reporter.report_to_string(&error, &config);
482
483        // Verify report contains both error messages
484        assert!(report.contains("Main error"));
485        assert!(report.contains("Source error"));
486    }
487
488    #[test]
489    fn test_error_reporter_json_format() {
490        // Create a test error
491        let error = TestError {
492            message: "JSON test error".to_string(),
493            source: None,
494        };
495
496        // Create reporter and config
497        let reporter = ErrorReporter::new();
498        let config = ErrorReportConfig {
499            format: ErrorReportFormat::Json,
500            ..Default::default()
501        };
502
503        // Generate report as string
504        let report = reporter.report_to_string(&error, &config);
505
506        // Verify report is JSON formatted
507        assert!(report.starts_with("{"));
508        assert!(report.ends_with("}\n") || report.ends_with("}"));
509        assert!(report.contains("\"error\""));
510        assert!(report.contains("JSON test error"));
511    }
512
513    #[test]
514    fn test_error_reporter_with_syntax() {
515        // Create a test error
516        let error = TestError {
517            message: "Syntax error in code".to_string(),
518            source: None,
519        };
520
521        // Sample source code
522        let source_code = r#"
523fn main() {
524    let x: i32 = "not an integer"; // Type mismatch error
525    println!("Value: {}", x);
526}
527"#;
528
529        // Create reporter and config
530        let reporter = ErrorReporter::new();
531        let config = ErrorReportConfig {
532            format: ErrorReportFormat::Markdown,
533            ..Default::default()
534        };
535
536        // Generate report as string with syntax highlighting
537        let report = reporter.report_to_string_with_syntax(&error, &config, Some(source_code));
538
539        // Verify report contains both error message and source code
540        assert!(report.contains("Syntax error in code"));
541        assert!(report.contains("Source Code Context"));
542        assert!(report.contains("```rust"));
543        assert!(report.contains("let x: i32 = \"not an integer\";"));
544    }
545}