Skip to main content

diagnostic_collector/
diagnostic_collector.rs

1//! Diagnostic collector pattern example.
2//!
3//! This example shows how to use DiagnosticCollector to accumulate multiple errors
4//! during processing - a common pattern in Quarto subsystems like YAML validation
5//! and markdown parsing.
6//!
7//! Note: DiagnosticCollector is in pampa, so this example shows
8//! the pattern manually. In real code, use the DiagnosticCollector utility.
9
10use quarto_error_reporting::{DiagnosticKind, DiagnosticMessage, DiagnosticMessageBuilder};
11use quarto_source_map::{SourceContext, SourceInfo};
12
13/// Simple collector for accumulating diagnostic messages
14struct SimpleCollector {
15    diagnostics: Vec<DiagnosticMessage>,
16}
17
18impl SimpleCollector {
19    fn new() -> Self {
20        Self {
21            diagnostics: Vec::new(),
22        }
23    }
24
25    fn add(&mut self, diagnostic: DiagnosticMessage) {
26        self.diagnostics.push(diagnostic);
27    }
28
29    fn error(&mut self, message: impl Into<String>) {
30        self.add(DiagnosticMessage::error(message.into()));
31    }
32
33    fn warn(&mut self, message: impl Into<String>) {
34        self.add(DiagnosticMessage::warning(message.into()));
35    }
36
37    fn error_at(&mut self, message: impl Into<String>, location: SourceInfo) {
38        self.add(
39            DiagnosticMessageBuilder::error(message.into())
40                .with_location(location)
41                .build(),
42        );
43    }
44
45    fn has_errors(&self) -> bool {
46        self.diagnostics
47            .iter()
48            .any(|d| d.kind == DiagnosticKind::Error)
49    }
50
51    fn diagnostics(&self) -> &[DiagnosticMessage] {
52        &self.diagnostics
53    }
54
55    fn to_text(&self, ctx: Option<&SourceContext>) -> Vec<String> {
56        self.diagnostics.iter().map(|d| d.to_text(ctx)).collect()
57    }
58}
59
60fn main() {
61    println!("=== Example 1: Accumulating multiple errors ===\n");
62
63    let mut collector = SimpleCollector::new();
64
65    // Simulate validating a YAML file
66    collector.error("Missing required field 'title'");
67    collector.warn("Field 'description' is deprecated");
68    collector.error("Invalid value for 'format': expected string, got number");
69
70    if collector.has_errors() {
71        println!(
72            "Validation failed with {} diagnostics:",
73            collector.diagnostics().len()
74        );
75        for text in collector.to_text(None) {
76            println!("{}", text);
77        }
78    }
79
80    println!("\n=== Example 2: Errors with source locations ===\n");
81
82    let mut ctx = SourceContext::new();
83    let file_id = ctx.add_file(
84        "config.yml".to_string(),
85        Some("title: 123\nformat: html\nauthor: John\n".to_string()),
86    );
87
88    let mut collector2 = SimpleCollector::new();
89
90    // Error in "title: 123" (offsets 7-10)
91    let loc1 = SourceInfo::original(file_id, 7, 10);
92    collector2.error_at("Title must be a string", loc1);
93
94    // Warning at "John" (offsets 33-37)
95    let loc2 = SourceInfo::original(file_id, 33, 37);
96    let warning = DiagnosticMessageBuilder::warning("Author field should include email")
97        .with_location(loc2)
98        .add_hint("Use format: 'Name <email@example.com>'")
99        .build();
100    collector2.add(warning);
101
102    println!("Collected diagnostics:");
103    for text in collector2.to_text(Some(&ctx)) {
104        println!("{}", text);
105        println!();
106    }
107
108    println!("=== Example 3: JSON output for all diagnostics ===\n");
109
110    let json_array: Vec<_> = collector2
111        .diagnostics()
112        .iter()
113        .map(|d| d.to_json())
114        .collect();
115
116    println!("{}", serde_json::to_string_pretty(&json_array).unwrap());
117
118    println!("\n=== Example 4: Continuing vs. failing fast ===\n");
119
120    let mut collector3 = SimpleCollector::new();
121
122    // In some subsystems, we collect all errors before failing
123    for i in 1..=3 {
124        collector3.error(format!("Error in item {}", i));
125    }
126
127    // Check at the end
128    if collector3.has_errors() {
129        eprintln!(
130            "Processing failed with {} errors",
131            collector3.diagnostics().len()
132        );
133        eprintln!("\nErrors:");
134        for diag in collector3.diagnostics() {
135            eprintln!("  - {}", diag.title);
136        }
137    }
138}