pub struct DiagnosticMessageBuilder { /* private fields */ }Expand description
Builder for creating diagnostic messages following tidyverse guidelines.
The builder API naturally encourages the tidyverse four-part error structure:
- Title: Brief error message (via
.error(),.warning(), etc.) - Problem: What went wrong - the “must” or “can’t” statement (via
.problem()) - Details: Specific information - max 5 bulleted items (via
.add_detail(),.add_info()) - Hints: Optional guidance (via
.add_hint())
§Example
use quarto_error_reporting::DiagnosticMessageBuilder;
let error = DiagnosticMessageBuilder::error("Incompatible types")
.with_code("Q-1-2") // quarto-error-code-audit-ignore
.problem("Cannot combine date and datetime types")
.add_detail("`x`{.arg} has type `date`{.type}")
.add_detail("`y`{.arg} has type `datetime`{.type}")
.add_hint("Convert both to the same type?")
.build();
assert_eq!(error.title, "Incompatible types");
assert_eq!(error.code, Some("Q-1-2".to_string())); // quarto-error-code-audit-ignore
assert!(error.problem.is_some());
assert_eq!(error.details.len(), 2);
assert_eq!(error.hints.len(), 1);Implementations§
Source§impl DiagnosticMessageBuilder
impl DiagnosticMessageBuilder
Sourcepub fn new(kind: DiagnosticKind, title: impl Into<String>) -> Self
pub fn new(kind: DiagnosticKind, title: impl Into<String>) -> Self
Create a new builder with the specified kind and title.
Most code should use the convenience methods .error(), .warning(), or .info()
instead of calling this directly.
Sourcepub fn error(title: impl Into<String>) -> Self
pub fn error(title: impl Into<String>) -> Self
Create an error diagnostic builder.
§Example
use quarto_error_reporting::DiagnosticMessageBuilder;
let error = DiagnosticMessageBuilder::error("YAML Syntax Error")
.build();Examples found in repository?
More examples
18fn main() {
19 let mut ctx = SourceContext::new();
20 let source = "title: My Document\nformat:\n html:\n theme: nosuchtheme\n";
21 let file_id = ctx.add_file("_quarto.yml".to_string(), Some(source.to_string()));
22
23 // Point at `nosuchtheme` on line 4 (byte offsets into `source`).
24 let start = source.find("nosuchtheme").unwrap();
25 let location = SourceInfo::original(file_id, start, start + "nosuchtheme".len());
26
27 let diag = DiagnosticMessageBuilder::error("Unknown theme")
28 .with_code("Q-14-1")
29 .with_location(location)
30 .problem("`nosuchtheme` is not a known Quarto theme")
31 .add_hint("Did you mean `cosmo`, `darkly`, or `flatly`?")
32 .build();
33
34 // Disable hyperlinks so the output is path-stable in a demo.
35 let opts = TextRenderOptions {
36 enable_hyperlinks: false,
37 };
38
39 // `None` uses the default renderer for the enabled features.
40 println!("=== default renderer (None) ===\n");
41 println!("{}", diag.to_text_with_renderer(Some(&ctx), &opts, None));
42
43 #[cfg(feature = "ariadne")]
44 {
45 println!("=== SourceRenderer::Ariadne ===\n");
46 println!(
47 "{}",
48 diag.to_text_with_renderer(Some(&ctx), &opts, Some(SourceRenderer::Ariadne))
49 );
50 }
51
52 #[cfg(feature = "annotate-snippets")]
53 {
54 println!("=== SourceRenderer::AnnotateSnippets ===\n");
55 println!(
56 "{}",
57 diag.to_text_with_renderer(Some(&ctx), &opts, Some(SourceRenderer::AnnotateSnippets),)
58 );
59 }
60}8fn main() {
9 println!("=== Example 1: Simple builder usage ===\n");
10
11 let error1 = DiagnosticMessageBuilder::error("Invalid input")
12 .problem("Value must be numeric")
13 .add_detail("Found text in column 3")
14 .add_hint("Check the input file format")
15 .build();
16
17 println!("{}", error1.to_text(None));
18
19 println!("\n=== Example 2: Tidyverse four-part structure ===\n");
20
21 let error2 = DiagnosticMessageBuilder::error("Incompatible types")
22 .problem("Cannot combine date and datetime types")
23 .add_detail("`x` has type `date`")
24 .add_detail("`y` has type `datetime`")
25 .add_info("Both values come from the same data source")
26 .add_hint("Convert both to the same type first?")
27 .build();
28
29 println!("{}", error2.to_text(None));
30
31 println!("\n=== Example 3: Multiple details and hints ===\n");
32
33 let error3 = DiagnosticMessageBuilder::error("Schema validation failed")
34 .problem("Configuration does not match expected schema")
35 .add_detail("Property `title` has type `number`")
36 .add_detail("Expected type is `string`")
37 .add_detail("Property `author` is missing")
38 .add_info("Schema is defined in `_quarto.yml`")
39 .add_hint("Did you forget quotes around the title?")
40 .add_hint("Add an `author` field to the configuration")
41 .build();
42
43 println!("{}", error3.to_text(None));
44
45 println!("\n=== Example 4: Builder validation ===\n");
46
47 // This will trigger validation warnings
48 let (msg, warnings) = DiagnosticMessageBuilder::error("Validation test")
49 .add_detail("Detail 1")
50 .add_detail("Detail 2")
51 .add_detail("Detail 3")
52 .add_detail("Detail 4")
53 .add_detail("Detail 5")
54 .add_detail("Detail 6") // Too many!
55 .build_with_validation();
56
57 println!("{}", msg.to_text(None));
58
59 if !warnings.is_empty() {
60 println!("\nValidation warnings:");
61 for warning in warnings {
62 println!(" ⚠ {}", warning);
63 }
64 }
65}9fn main() {
10 println!("=== Example 1: Error with source location ===\n");
11
12 // Create a source context
13 let mut ctx = SourceContext::new();
14 let file_id = ctx.add_file(
15 "example.qmd".to_string(),
16 Some("title: My Document\nauthor: John Doe\ndate: 2024-01-01\n".to_string()),
17 );
18
19 // Create a location (let's say there's an error in "My Document" - offsets 7 to 18)
20 let location = SourceInfo::original(file_id, 7, 18);
21
22 let error = DiagnosticMessageBuilder::error("Invalid title format")
23 .with_code("Q-1-10")
24 .with_location(location)
25 .problem("Title must be a string, not a complex object")
26 .add_detail("Title value starts at this location")
27 .add_hint("Ensure the title is a simple quoted string")
28 .build();
29
30 // Render WITHOUT context - shows offset
31 println!("Without context:");
32 println!("{}", error.to_text(None));
33
34 println!("\n---\n");
35
36 // Render WITH context - shows file path and line:column
37 println!("With context:");
38 println!("{}", error.to_text(Some(&ctx)));
39
40 println!("\n=== Example 2: Multiple locations ===\n");
41
42 let another_ctx = SourceContext::new();
43
44 // Note: This example shows the API, but without actual file content,
45 // the rendering will still show offsets. In real usage with proper
46 // SourceContext, this would show rich source snippets via ariadne.
47
48 let location2 = SourceInfo::original(quarto_source_map::FileId(0), 100, 110);
49
50 let error2 = DiagnosticMessageBuilder::error("Unclosed code block")
51 .with_code("Q-2-301")
52 .with_location(location2)
53 .problem("Code block started but never closed")
54 .add_detail("The opening ``` was found but no closing ``` before end of block")
55 .add_hint("Add a closing ``` on a new line")
56 .build();
57
58 println!("{}", error2.to_text(Some(&another_ctx)));
59
60 println!("\n=== Example 3: JSON output with location ===\n");
61
62 let json = error.to_json();
63 println!("{}", serde_json::to_string_pretty(&json).unwrap());
64}9fn main() {
10 println!("=== Example 1: Default rendering (with hyperlinks) ===\n");
11
12 let mut ctx = SourceContext::new();
13 let file_id = ctx.add_file(
14 "document.qmd".to_string(),
15 Some("# My Document\n\nSome content here.\n".to_string()),
16 );
17
18 let location = SourceInfo::original(file_id, 15, 27);
19
20 let error = DiagnosticMessageBuilder::error("Parse error")
21 .with_code("Q-2-100")
22 .with_location(location)
23 .problem("Invalid markdown syntax")
24 .add_hint("Check the markdown formatting")
25 .build();
26
27 // Default rendering includes OSC 8 hyperlinks for file paths
28 let default_text = error.to_text(Some(&ctx));
29 println!("{}", default_text);
30
31 println!("\n=== Example 2: Rendering without hyperlinks (for tests) ===\n");
32
33 // Disable hyperlinks - useful for snapshot testing where absolute paths
34 // would cause differences between machines
35 let options = TextRenderOptions {
36 enable_hyperlinks: false,
37 };
38
39 let no_hyperlink_text = error.to_text_with_options(Some(&ctx), &options);
40 println!("{}", no_hyperlink_text);
41
42 println!("\n=== Example 3: Comparing outputs ===\n");
43
44 // Show the difference in output
45 println!("With hyperlinks enabled:");
46 println!(" Length: {} bytes", default_text.len());
47 println!(
48 " Contains OSC 8 codes: {}",
49 default_text.contains("\x1b]8;")
50 );
51
52 println!("\nWith hyperlinks disabled:");
53 println!(" Length: {} bytes", no_hyperlink_text.len());
54 println!(
55 " Contains OSC 8 codes: {}",
56 no_hyperlink_text.contains("\x1b]8;")
57 );
58
59 println!("\n=== Example 4: JSON output (no hyperlinks) ===\n");
60
61 let json = error.to_json();
62 println!("{}", serde_json::to_string_pretty(&json).unwrap());
63
64 println!("\n=== Example 5: Multiple diagnostics with custom rendering ===\n");
65
66 let error2 = DiagnosticMessageBuilder::error("Type mismatch")
67 .with_code("Q-1-15")
68 .problem("Expected string, found number")
69 .add_detail("Value: 42")
70 .add_detail("Expected type: string")
71 .build();
72
73 let error3 = DiagnosticMessageBuilder::error("Missing field")
74 .with_code("Q-1-20")
75 .problem("Required field 'author' not found")
76 .add_hint("Add an 'author' field to your configuration")
77 .build();
78
79 let errors = [error, error2, error3];
80
81 // Render all with consistent options
82 let no_hyperlinks = TextRenderOptions {
83 enable_hyperlinks: false,
84 };
85
86 for (i, err) in errors.iter().enumerate() {
87 println!("Error {}:", i + 1);
88 println!("{}", err.to_text_with_options(Some(&ctx), &no_hyperlinks));
89 println!();
90 }
91}Sourcepub fn generic_error(
message: impl Into<String>,
file: &str,
line: u32,
) -> DiagnosticMessage
pub fn generic_error( message: impl Into<String>, file: &str, line: u32, ) -> DiagnosticMessage
Create a generic error for migration purposes.
This is a convenience method for the migration from ErrorCollector to DiagnosticMessage. It creates an error with code Q-0-99 (quarto-error-code-audit-ignore) and includes file/line information for tracking where the error originated in the code.
§Example
use quarto_error_reporting::DiagnosticMessageBuilder;
let error = DiagnosticMessageBuilder::generic_error(
"Found unexpected attribute",
file!(),
line!()
);
assert_eq!(error.code, Some("Q-0-99".to_string())); // quarto-error-code-audit-ignore
assert!(error.title.contains("Found unexpected attribute"));Sourcepub fn generic_warning(
message: impl Into<String>,
file: &str,
line: u32,
) -> DiagnosticMessage
pub fn generic_warning( message: impl Into<String>, file: &str, line: u32, ) -> DiagnosticMessage
Create a generic warning for migration purposes.
Similar to generic_error() but for warnings.
§Example
use quarto_error_reporting::DiagnosticMessageBuilder;
let warning = DiagnosticMessageBuilder::generic_warning(
"Caption found without table",
file!(),
line!()
);
assert_eq!(warning.code, Some("Q-0-99".to_string()));Sourcepub fn warning(title: impl Into<String>) -> Self
pub fn warning(title: impl Into<String>) -> Self
Create a warning diagnostic builder.
§Example
use quarto_error_reporting::DiagnosticMessageBuilder;
let warning = DiagnosticMessageBuilder::warning("Deprecated feature")
.build();Examples found in repository?
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}Sourcepub fn info(title: impl Into<String>) -> Self
pub fn info(title: impl Into<String>) -> Self
Create an info diagnostic builder.
§Example
use quarto_error_reporting::DiagnosticMessageBuilder;
let info = DiagnosticMessageBuilder::info("Processing complete")
.build();Sourcepub fn with_code(self, code: impl Into<String>) -> Self
pub fn with_code(self, code: impl Into<String>) -> Self
Set the error code.
Error codes follow the format Q-<subsystem>-<number> (e.g., “Q-1-1”). (quarto-error-code-audit-ignore)
§Example
use quarto_error_reporting::DiagnosticMessageBuilder;
let error = DiagnosticMessageBuilder::error("YAML Syntax Error")
.with_code("Q-1-1") // quarto-error-code-audit-ignore
.build();
assert_eq!(error.code, Some("Q-1-1".to_string())); // quarto-error-code-audit-ignoreExamples found in repository?
18fn main() {
19 let mut ctx = SourceContext::new();
20 let source = "title: My Document\nformat:\n html:\n theme: nosuchtheme\n";
21 let file_id = ctx.add_file("_quarto.yml".to_string(), Some(source.to_string()));
22
23 // Point at `nosuchtheme` on line 4 (byte offsets into `source`).
24 let start = source.find("nosuchtheme").unwrap();
25 let location = SourceInfo::original(file_id, start, start + "nosuchtheme".len());
26
27 let diag = DiagnosticMessageBuilder::error("Unknown theme")
28 .with_code("Q-14-1")
29 .with_location(location)
30 .problem("`nosuchtheme` is not a known Quarto theme")
31 .add_hint("Did you mean `cosmo`, `darkly`, or `flatly`?")
32 .build();
33
34 // Disable hyperlinks so the output is path-stable in a demo.
35 let opts = TextRenderOptions {
36 enable_hyperlinks: false,
37 };
38
39 // `None` uses the default renderer for the enabled features.
40 println!("=== default renderer (None) ===\n");
41 println!("{}", diag.to_text_with_renderer(Some(&ctx), &opts, None));
42
43 #[cfg(feature = "ariadne")]
44 {
45 println!("=== SourceRenderer::Ariadne ===\n");
46 println!(
47 "{}",
48 diag.to_text_with_renderer(Some(&ctx), &opts, Some(SourceRenderer::Ariadne))
49 );
50 }
51
52 #[cfg(feature = "annotate-snippets")]
53 {
54 println!("=== SourceRenderer::AnnotateSnippets ===\n");
55 println!(
56 "{}",
57 diag.to_text_with_renderer(Some(&ctx), &opts, Some(SourceRenderer::AnnotateSnippets),)
58 );
59 }
60}More examples
9fn main() {
10 println!("=== Example 1: Error with source location ===\n");
11
12 // Create a source context
13 let mut ctx = SourceContext::new();
14 let file_id = ctx.add_file(
15 "example.qmd".to_string(),
16 Some("title: My Document\nauthor: John Doe\ndate: 2024-01-01\n".to_string()),
17 );
18
19 // Create a location (let's say there's an error in "My Document" - offsets 7 to 18)
20 let location = SourceInfo::original(file_id, 7, 18);
21
22 let error = DiagnosticMessageBuilder::error("Invalid title format")
23 .with_code("Q-1-10")
24 .with_location(location)
25 .problem("Title must be a string, not a complex object")
26 .add_detail("Title value starts at this location")
27 .add_hint("Ensure the title is a simple quoted string")
28 .build();
29
30 // Render WITHOUT context - shows offset
31 println!("Without context:");
32 println!("{}", error.to_text(None));
33
34 println!("\n---\n");
35
36 // Render WITH context - shows file path and line:column
37 println!("With context:");
38 println!("{}", error.to_text(Some(&ctx)));
39
40 println!("\n=== Example 2: Multiple locations ===\n");
41
42 let another_ctx = SourceContext::new();
43
44 // Note: This example shows the API, but without actual file content,
45 // the rendering will still show offsets. In real usage with proper
46 // SourceContext, this would show rich source snippets via ariadne.
47
48 let location2 = SourceInfo::original(quarto_source_map::FileId(0), 100, 110);
49
50 let error2 = DiagnosticMessageBuilder::error("Unclosed code block")
51 .with_code("Q-2-301")
52 .with_location(location2)
53 .problem("Code block started but never closed")
54 .add_detail("The opening ``` was found but no closing ``` before end of block")
55 .add_hint("Add a closing ``` on a new line")
56 .build();
57
58 println!("{}", error2.to_text(Some(&another_ctx)));
59
60 println!("\n=== Example 3: JSON output with location ===\n");
61
62 let json = error.to_json();
63 println!("{}", serde_json::to_string_pretty(&json).unwrap());
64}9fn main() {
10 println!("=== Example 1: Default rendering (with hyperlinks) ===\n");
11
12 let mut ctx = SourceContext::new();
13 let file_id = ctx.add_file(
14 "document.qmd".to_string(),
15 Some("# My Document\n\nSome content here.\n".to_string()),
16 );
17
18 let location = SourceInfo::original(file_id, 15, 27);
19
20 let error = DiagnosticMessageBuilder::error("Parse error")
21 .with_code("Q-2-100")
22 .with_location(location)
23 .problem("Invalid markdown syntax")
24 .add_hint("Check the markdown formatting")
25 .build();
26
27 // Default rendering includes OSC 8 hyperlinks for file paths
28 let default_text = error.to_text(Some(&ctx));
29 println!("{}", default_text);
30
31 println!("\n=== Example 2: Rendering without hyperlinks (for tests) ===\n");
32
33 // Disable hyperlinks - useful for snapshot testing where absolute paths
34 // would cause differences between machines
35 let options = TextRenderOptions {
36 enable_hyperlinks: false,
37 };
38
39 let no_hyperlink_text = error.to_text_with_options(Some(&ctx), &options);
40 println!("{}", no_hyperlink_text);
41
42 println!("\n=== Example 3: Comparing outputs ===\n");
43
44 // Show the difference in output
45 println!("With hyperlinks enabled:");
46 println!(" Length: {} bytes", default_text.len());
47 println!(
48 " Contains OSC 8 codes: {}",
49 default_text.contains("\x1b]8;")
50 );
51
52 println!("\nWith hyperlinks disabled:");
53 println!(" Length: {} bytes", no_hyperlink_text.len());
54 println!(
55 " Contains OSC 8 codes: {}",
56 no_hyperlink_text.contains("\x1b]8;")
57 );
58
59 println!("\n=== Example 4: JSON output (no hyperlinks) ===\n");
60
61 let json = error.to_json();
62 println!("{}", serde_json::to_string_pretty(&json).unwrap());
63
64 println!("\n=== Example 5: Multiple diagnostics with custom rendering ===\n");
65
66 let error2 = DiagnosticMessageBuilder::error("Type mismatch")
67 .with_code("Q-1-15")
68 .problem("Expected string, found number")
69 .add_detail("Value: 42")
70 .add_detail("Expected type: string")
71 .build();
72
73 let error3 = DiagnosticMessageBuilder::error("Missing field")
74 .with_code("Q-1-20")
75 .problem("Required field 'author' not found")
76 .add_hint("Add an 'author' field to your configuration")
77 .build();
78
79 let errors = [error, error2, error3];
80
81 // Render all with consistent options
82 let no_hyperlinks = TextRenderOptions {
83 enable_hyperlinks: false,
84 };
85
86 for (i, err) in errors.iter().enumerate() {
87 println!("Error {}:", i + 1);
88 println!("{}", err.to_text_with_options(Some(&ctx), &no_hyperlinks));
89 println!();
90 }
91}Sourcepub fn with_location(self, location: SourceInfo) -> Self
pub fn with_location(self, location: SourceInfo) -> Self
Attach a source location to this diagnostic.
The location identifies where in the source code the issue occurred. The location may track transformation history, allowing the error to be mapped back through multiple processing steps to the original source file.
§Example
use quarto_error_reporting::DiagnosticMessageBuilder;
use quarto_source_map::{SourceInfo, SourceContext, FileId, Range, Location};
let mut ctx = SourceContext::new();
let file_id = ctx.add_file("test.qmd".into(), Some("content".into()));
let range = Range {
start: Location { offset: 0, row: 0, column: 0 },
end: Location { offset: 7, row: 0, column: 7 },
};
let source_info = SourceInfo::original(file_id, range);
let error = DiagnosticMessageBuilder::error("Parse error")
.with_location(source_info)
.build();Examples found in repository?
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}More examples
18fn main() {
19 let mut ctx = SourceContext::new();
20 let source = "title: My Document\nformat:\n html:\n theme: nosuchtheme\n";
21 let file_id = ctx.add_file("_quarto.yml".to_string(), Some(source.to_string()));
22
23 // Point at `nosuchtheme` on line 4 (byte offsets into `source`).
24 let start = source.find("nosuchtheme").unwrap();
25 let location = SourceInfo::original(file_id, start, start + "nosuchtheme".len());
26
27 let diag = DiagnosticMessageBuilder::error("Unknown theme")
28 .with_code("Q-14-1")
29 .with_location(location)
30 .problem("`nosuchtheme` is not a known Quarto theme")
31 .add_hint("Did you mean `cosmo`, `darkly`, or `flatly`?")
32 .build();
33
34 // Disable hyperlinks so the output is path-stable in a demo.
35 let opts = TextRenderOptions {
36 enable_hyperlinks: false,
37 };
38
39 // `None` uses the default renderer for the enabled features.
40 println!("=== default renderer (None) ===\n");
41 println!("{}", diag.to_text_with_renderer(Some(&ctx), &opts, None));
42
43 #[cfg(feature = "ariadne")]
44 {
45 println!("=== SourceRenderer::Ariadne ===\n");
46 println!(
47 "{}",
48 diag.to_text_with_renderer(Some(&ctx), &opts, Some(SourceRenderer::Ariadne))
49 );
50 }
51
52 #[cfg(feature = "annotate-snippets")]
53 {
54 println!("=== SourceRenderer::AnnotateSnippets ===\n");
55 println!(
56 "{}",
57 diag.to_text_with_renderer(Some(&ctx), &opts, Some(SourceRenderer::AnnotateSnippets),)
58 );
59 }
60}9fn main() {
10 println!("=== Example 1: Error with source location ===\n");
11
12 // Create a source context
13 let mut ctx = SourceContext::new();
14 let file_id = ctx.add_file(
15 "example.qmd".to_string(),
16 Some("title: My Document\nauthor: John Doe\ndate: 2024-01-01\n".to_string()),
17 );
18
19 // Create a location (let's say there's an error in "My Document" - offsets 7 to 18)
20 let location = SourceInfo::original(file_id, 7, 18);
21
22 let error = DiagnosticMessageBuilder::error("Invalid title format")
23 .with_code("Q-1-10")
24 .with_location(location)
25 .problem("Title must be a string, not a complex object")
26 .add_detail("Title value starts at this location")
27 .add_hint("Ensure the title is a simple quoted string")
28 .build();
29
30 // Render WITHOUT context - shows offset
31 println!("Without context:");
32 println!("{}", error.to_text(None));
33
34 println!("\n---\n");
35
36 // Render WITH context - shows file path and line:column
37 println!("With context:");
38 println!("{}", error.to_text(Some(&ctx)));
39
40 println!("\n=== Example 2: Multiple locations ===\n");
41
42 let another_ctx = SourceContext::new();
43
44 // Note: This example shows the API, but without actual file content,
45 // the rendering will still show offsets. In real usage with proper
46 // SourceContext, this would show rich source snippets via ariadne.
47
48 let location2 = SourceInfo::original(quarto_source_map::FileId(0), 100, 110);
49
50 let error2 = DiagnosticMessageBuilder::error("Unclosed code block")
51 .with_code("Q-2-301")
52 .with_location(location2)
53 .problem("Code block started but never closed")
54 .add_detail("The opening ``` was found but no closing ``` before end of block")
55 .add_hint("Add a closing ``` on a new line")
56 .build();
57
58 println!("{}", error2.to_text(Some(&another_ctx)));
59
60 println!("\n=== Example 3: JSON output with location ===\n");
61
62 let json = error.to_json();
63 println!("{}", serde_json::to_string_pretty(&json).unwrap());
64}9fn main() {
10 println!("=== Example 1: Default rendering (with hyperlinks) ===\n");
11
12 let mut ctx = SourceContext::new();
13 let file_id = ctx.add_file(
14 "document.qmd".to_string(),
15 Some("# My Document\n\nSome content here.\n".to_string()),
16 );
17
18 let location = SourceInfo::original(file_id, 15, 27);
19
20 let error = DiagnosticMessageBuilder::error("Parse error")
21 .with_code("Q-2-100")
22 .with_location(location)
23 .problem("Invalid markdown syntax")
24 .add_hint("Check the markdown formatting")
25 .build();
26
27 // Default rendering includes OSC 8 hyperlinks for file paths
28 let default_text = error.to_text(Some(&ctx));
29 println!("{}", default_text);
30
31 println!("\n=== Example 2: Rendering without hyperlinks (for tests) ===\n");
32
33 // Disable hyperlinks - useful for snapshot testing where absolute paths
34 // would cause differences between machines
35 let options = TextRenderOptions {
36 enable_hyperlinks: false,
37 };
38
39 let no_hyperlink_text = error.to_text_with_options(Some(&ctx), &options);
40 println!("{}", no_hyperlink_text);
41
42 println!("\n=== Example 3: Comparing outputs ===\n");
43
44 // Show the difference in output
45 println!("With hyperlinks enabled:");
46 println!(" Length: {} bytes", default_text.len());
47 println!(
48 " Contains OSC 8 codes: {}",
49 default_text.contains("\x1b]8;")
50 );
51
52 println!("\nWith hyperlinks disabled:");
53 println!(" Length: {} bytes", no_hyperlink_text.len());
54 println!(
55 " Contains OSC 8 codes: {}",
56 no_hyperlink_text.contains("\x1b]8;")
57 );
58
59 println!("\n=== Example 4: JSON output (no hyperlinks) ===\n");
60
61 let json = error.to_json();
62 println!("{}", serde_json::to_string_pretty(&json).unwrap());
63
64 println!("\n=== Example 5: Multiple diagnostics with custom rendering ===\n");
65
66 let error2 = DiagnosticMessageBuilder::error("Type mismatch")
67 .with_code("Q-1-15")
68 .problem("Expected string, found number")
69 .add_detail("Value: 42")
70 .add_detail("Expected type: string")
71 .build();
72
73 let error3 = DiagnosticMessageBuilder::error("Missing field")
74 .with_code("Q-1-20")
75 .problem("Required field 'author' not found")
76 .add_hint("Add an 'author' field to your configuration")
77 .build();
78
79 let errors = [error, error2, error3];
80
81 // Render all with consistent options
82 let no_hyperlinks = TextRenderOptions {
83 enable_hyperlinks: false,
84 };
85
86 for (i, err) in errors.iter().enumerate() {
87 println!("Error {}:", i + 1);
88 println!("{}", err.to_text_with_options(Some(&ctx), &no_hyperlinks));
89 println!();
90 }
91}Sourcepub fn problem(self, stmt: impl Into<MessageContent>) -> Self
pub fn problem(self, stmt: impl Into<MessageContent>) -> Self
Set the problem statement.
Following tidyverse guidelines, the problem statement should:
- Start with a general, concise statement
- Use “must” for requirements or “can’t” for impossibilities
- Be specific about types/expectations
§Example
use quarto_error_reporting::DiagnosticMessageBuilder;
let error = DiagnosticMessageBuilder::error("Invalid input")
.problem("`n` must be a numeric vector, not a character vector")
.build();Examples found in repository?
18fn main() {
19 let mut ctx = SourceContext::new();
20 let source = "title: My Document\nformat:\n html:\n theme: nosuchtheme\n";
21 let file_id = ctx.add_file("_quarto.yml".to_string(), Some(source.to_string()));
22
23 // Point at `nosuchtheme` on line 4 (byte offsets into `source`).
24 let start = source.find("nosuchtheme").unwrap();
25 let location = SourceInfo::original(file_id, start, start + "nosuchtheme".len());
26
27 let diag = DiagnosticMessageBuilder::error("Unknown theme")
28 .with_code("Q-14-1")
29 .with_location(location)
30 .problem("`nosuchtheme` is not a known Quarto theme")
31 .add_hint("Did you mean `cosmo`, `darkly`, or `flatly`?")
32 .build();
33
34 // Disable hyperlinks so the output is path-stable in a demo.
35 let opts = TextRenderOptions {
36 enable_hyperlinks: false,
37 };
38
39 // `None` uses the default renderer for the enabled features.
40 println!("=== default renderer (None) ===\n");
41 println!("{}", diag.to_text_with_renderer(Some(&ctx), &opts, None));
42
43 #[cfg(feature = "ariadne")]
44 {
45 println!("=== SourceRenderer::Ariadne ===\n");
46 println!(
47 "{}",
48 diag.to_text_with_renderer(Some(&ctx), &opts, Some(SourceRenderer::Ariadne))
49 );
50 }
51
52 #[cfg(feature = "annotate-snippets")]
53 {
54 println!("=== SourceRenderer::AnnotateSnippets ===\n");
55 println!(
56 "{}",
57 diag.to_text_with_renderer(Some(&ctx), &opts, Some(SourceRenderer::AnnotateSnippets),)
58 );
59 }
60}More examples
8fn main() {
9 println!("=== Example 1: Simple builder usage ===\n");
10
11 let error1 = DiagnosticMessageBuilder::error("Invalid input")
12 .problem("Value must be numeric")
13 .add_detail("Found text in column 3")
14 .add_hint("Check the input file format")
15 .build();
16
17 println!("{}", error1.to_text(None));
18
19 println!("\n=== Example 2: Tidyverse four-part structure ===\n");
20
21 let error2 = DiagnosticMessageBuilder::error("Incompatible types")
22 .problem("Cannot combine date and datetime types")
23 .add_detail("`x` has type `date`")
24 .add_detail("`y` has type `datetime`")
25 .add_info("Both values come from the same data source")
26 .add_hint("Convert both to the same type first?")
27 .build();
28
29 println!("{}", error2.to_text(None));
30
31 println!("\n=== Example 3: Multiple details and hints ===\n");
32
33 let error3 = DiagnosticMessageBuilder::error("Schema validation failed")
34 .problem("Configuration does not match expected schema")
35 .add_detail("Property `title` has type `number`")
36 .add_detail("Expected type is `string`")
37 .add_detail("Property `author` is missing")
38 .add_info("Schema is defined in `_quarto.yml`")
39 .add_hint("Did you forget quotes around the title?")
40 .add_hint("Add an `author` field to the configuration")
41 .build();
42
43 println!("{}", error3.to_text(None));
44
45 println!("\n=== Example 4: Builder validation ===\n");
46
47 // This will trigger validation warnings
48 let (msg, warnings) = DiagnosticMessageBuilder::error("Validation test")
49 .add_detail("Detail 1")
50 .add_detail("Detail 2")
51 .add_detail("Detail 3")
52 .add_detail("Detail 4")
53 .add_detail("Detail 5")
54 .add_detail("Detail 6") // Too many!
55 .build_with_validation();
56
57 println!("{}", msg.to_text(None));
58
59 if !warnings.is_empty() {
60 println!("\nValidation warnings:");
61 for warning in warnings {
62 println!(" ⚠ {}", warning);
63 }
64 }
65}9fn main() {
10 println!("=== Example 1: Error with source location ===\n");
11
12 // Create a source context
13 let mut ctx = SourceContext::new();
14 let file_id = ctx.add_file(
15 "example.qmd".to_string(),
16 Some("title: My Document\nauthor: John Doe\ndate: 2024-01-01\n".to_string()),
17 );
18
19 // Create a location (let's say there's an error in "My Document" - offsets 7 to 18)
20 let location = SourceInfo::original(file_id, 7, 18);
21
22 let error = DiagnosticMessageBuilder::error("Invalid title format")
23 .with_code("Q-1-10")
24 .with_location(location)
25 .problem("Title must be a string, not a complex object")
26 .add_detail("Title value starts at this location")
27 .add_hint("Ensure the title is a simple quoted string")
28 .build();
29
30 // Render WITHOUT context - shows offset
31 println!("Without context:");
32 println!("{}", error.to_text(None));
33
34 println!("\n---\n");
35
36 // Render WITH context - shows file path and line:column
37 println!("With context:");
38 println!("{}", error.to_text(Some(&ctx)));
39
40 println!("\n=== Example 2: Multiple locations ===\n");
41
42 let another_ctx = SourceContext::new();
43
44 // Note: This example shows the API, but without actual file content,
45 // the rendering will still show offsets. In real usage with proper
46 // SourceContext, this would show rich source snippets via ariadne.
47
48 let location2 = SourceInfo::original(quarto_source_map::FileId(0), 100, 110);
49
50 let error2 = DiagnosticMessageBuilder::error("Unclosed code block")
51 .with_code("Q-2-301")
52 .with_location(location2)
53 .problem("Code block started but never closed")
54 .add_detail("The opening ``` was found but no closing ``` before end of block")
55 .add_hint("Add a closing ``` on a new line")
56 .build();
57
58 println!("{}", error2.to_text(Some(&another_ctx)));
59
60 println!("\n=== Example 3: JSON output with location ===\n");
61
62 let json = error.to_json();
63 println!("{}", serde_json::to_string_pretty(&json).unwrap());
64}9fn main() {
10 println!("=== Example 1: Default rendering (with hyperlinks) ===\n");
11
12 let mut ctx = SourceContext::new();
13 let file_id = ctx.add_file(
14 "document.qmd".to_string(),
15 Some("# My Document\n\nSome content here.\n".to_string()),
16 );
17
18 let location = SourceInfo::original(file_id, 15, 27);
19
20 let error = DiagnosticMessageBuilder::error("Parse error")
21 .with_code("Q-2-100")
22 .with_location(location)
23 .problem("Invalid markdown syntax")
24 .add_hint("Check the markdown formatting")
25 .build();
26
27 // Default rendering includes OSC 8 hyperlinks for file paths
28 let default_text = error.to_text(Some(&ctx));
29 println!("{}", default_text);
30
31 println!("\n=== Example 2: Rendering without hyperlinks (for tests) ===\n");
32
33 // Disable hyperlinks - useful for snapshot testing where absolute paths
34 // would cause differences between machines
35 let options = TextRenderOptions {
36 enable_hyperlinks: false,
37 };
38
39 let no_hyperlink_text = error.to_text_with_options(Some(&ctx), &options);
40 println!("{}", no_hyperlink_text);
41
42 println!("\n=== Example 3: Comparing outputs ===\n");
43
44 // Show the difference in output
45 println!("With hyperlinks enabled:");
46 println!(" Length: {} bytes", default_text.len());
47 println!(
48 " Contains OSC 8 codes: {}",
49 default_text.contains("\x1b]8;")
50 );
51
52 println!("\nWith hyperlinks disabled:");
53 println!(" Length: {} bytes", no_hyperlink_text.len());
54 println!(
55 " Contains OSC 8 codes: {}",
56 no_hyperlink_text.contains("\x1b]8;")
57 );
58
59 println!("\n=== Example 4: JSON output (no hyperlinks) ===\n");
60
61 let json = error.to_json();
62 println!("{}", serde_json::to_string_pretty(&json).unwrap());
63
64 println!("\n=== Example 5: Multiple diagnostics with custom rendering ===\n");
65
66 let error2 = DiagnosticMessageBuilder::error("Type mismatch")
67 .with_code("Q-1-15")
68 .problem("Expected string, found number")
69 .add_detail("Value: 42")
70 .add_detail("Expected type: string")
71 .build();
72
73 let error3 = DiagnosticMessageBuilder::error("Missing field")
74 .with_code("Q-1-20")
75 .problem("Required field 'author' not found")
76 .add_hint("Add an 'author' field to your configuration")
77 .build();
78
79 let errors = [error, error2, error3];
80
81 // Render all with consistent options
82 let no_hyperlinks = TextRenderOptions {
83 enable_hyperlinks: false,
84 };
85
86 for (i, err) in errors.iter().enumerate() {
87 println!("Error {}:", i + 1);
88 println!("{}", err.to_text_with_options(Some(&ctx), &no_hyperlinks));
89 println!();
90 }
91}Sourcepub fn add_detail(self, detail: impl Into<MessageContent>) -> Self
pub fn add_detail(self, detail: impl Into<MessageContent>) -> Self
Add an error detail (displayed with error/cross bullet).
Error details provide specific information about what went wrong. Following tidyverse guidelines:
- Keep sentences short and specific
- Reveal location, name, or content of problematic input
- Limit to 5 total details (error + info) to avoid overwhelming users
§Example
use quarto_error_reporting::DiagnosticMessageBuilder;
let error = DiagnosticMessageBuilder::error("Incompatible lengths")
.add_detail("`x` has length 3")
.add_detail("`y` has length 5")
.build();
assert_eq!(error.details.len(), 2);Examples found in repository?
8fn main() {
9 println!("=== Example 1: Simple builder usage ===\n");
10
11 let error1 = DiagnosticMessageBuilder::error("Invalid input")
12 .problem("Value must be numeric")
13 .add_detail("Found text in column 3")
14 .add_hint("Check the input file format")
15 .build();
16
17 println!("{}", error1.to_text(None));
18
19 println!("\n=== Example 2: Tidyverse four-part structure ===\n");
20
21 let error2 = DiagnosticMessageBuilder::error("Incompatible types")
22 .problem("Cannot combine date and datetime types")
23 .add_detail("`x` has type `date`")
24 .add_detail("`y` has type `datetime`")
25 .add_info("Both values come from the same data source")
26 .add_hint("Convert both to the same type first?")
27 .build();
28
29 println!("{}", error2.to_text(None));
30
31 println!("\n=== Example 3: Multiple details and hints ===\n");
32
33 let error3 = DiagnosticMessageBuilder::error("Schema validation failed")
34 .problem("Configuration does not match expected schema")
35 .add_detail("Property `title` has type `number`")
36 .add_detail("Expected type is `string`")
37 .add_detail("Property `author` is missing")
38 .add_info("Schema is defined in `_quarto.yml`")
39 .add_hint("Did you forget quotes around the title?")
40 .add_hint("Add an `author` field to the configuration")
41 .build();
42
43 println!("{}", error3.to_text(None));
44
45 println!("\n=== Example 4: Builder validation ===\n");
46
47 // This will trigger validation warnings
48 let (msg, warnings) = DiagnosticMessageBuilder::error("Validation test")
49 .add_detail("Detail 1")
50 .add_detail("Detail 2")
51 .add_detail("Detail 3")
52 .add_detail("Detail 4")
53 .add_detail("Detail 5")
54 .add_detail("Detail 6") // Too many!
55 .build_with_validation();
56
57 println!("{}", msg.to_text(None));
58
59 if !warnings.is_empty() {
60 println!("\nValidation warnings:");
61 for warning in warnings {
62 println!(" ⚠ {}", warning);
63 }
64 }
65}More examples
9fn main() {
10 println!("=== Example 1: Error with source location ===\n");
11
12 // Create a source context
13 let mut ctx = SourceContext::new();
14 let file_id = ctx.add_file(
15 "example.qmd".to_string(),
16 Some("title: My Document\nauthor: John Doe\ndate: 2024-01-01\n".to_string()),
17 );
18
19 // Create a location (let's say there's an error in "My Document" - offsets 7 to 18)
20 let location = SourceInfo::original(file_id, 7, 18);
21
22 let error = DiagnosticMessageBuilder::error("Invalid title format")
23 .with_code("Q-1-10")
24 .with_location(location)
25 .problem("Title must be a string, not a complex object")
26 .add_detail("Title value starts at this location")
27 .add_hint("Ensure the title is a simple quoted string")
28 .build();
29
30 // Render WITHOUT context - shows offset
31 println!("Without context:");
32 println!("{}", error.to_text(None));
33
34 println!("\n---\n");
35
36 // Render WITH context - shows file path and line:column
37 println!("With context:");
38 println!("{}", error.to_text(Some(&ctx)));
39
40 println!("\n=== Example 2: Multiple locations ===\n");
41
42 let another_ctx = SourceContext::new();
43
44 // Note: This example shows the API, but without actual file content,
45 // the rendering will still show offsets. In real usage with proper
46 // SourceContext, this would show rich source snippets via ariadne.
47
48 let location2 = SourceInfo::original(quarto_source_map::FileId(0), 100, 110);
49
50 let error2 = DiagnosticMessageBuilder::error("Unclosed code block")
51 .with_code("Q-2-301")
52 .with_location(location2)
53 .problem("Code block started but never closed")
54 .add_detail("The opening ``` was found but no closing ``` before end of block")
55 .add_hint("Add a closing ``` on a new line")
56 .build();
57
58 println!("{}", error2.to_text(Some(&another_ctx)));
59
60 println!("\n=== Example 3: JSON output with location ===\n");
61
62 let json = error.to_json();
63 println!("{}", serde_json::to_string_pretty(&json).unwrap());
64}9fn main() {
10 println!("=== Example 1: Default rendering (with hyperlinks) ===\n");
11
12 let mut ctx = SourceContext::new();
13 let file_id = ctx.add_file(
14 "document.qmd".to_string(),
15 Some("# My Document\n\nSome content here.\n".to_string()),
16 );
17
18 let location = SourceInfo::original(file_id, 15, 27);
19
20 let error = DiagnosticMessageBuilder::error("Parse error")
21 .with_code("Q-2-100")
22 .with_location(location)
23 .problem("Invalid markdown syntax")
24 .add_hint("Check the markdown formatting")
25 .build();
26
27 // Default rendering includes OSC 8 hyperlinks for file paths
28 let default_text = error.to_text(Some(&ctx));
29 println!("{}", default_text);
30
31 println!("\n=== Example 2: Rendering without hyperlinks (for tests) ===\n");
32
33 // Disable hyperlinks - useful for snapshot testing where absolute paths
34 // would cause differences between machines
35 let options = TextRenderOptions {
36 enable_hyperlinks: false,
37 };
38
39 let no_hyperlink_text = error.to_text_with_options(Some(&ctx), &options);
40 println!("{}", no_hyperlink_text);
41
42 println!("\n=== Example 3: Comparing outputs ===\n");
43
44 // Show the difference in output
45 println!("With hyperlinks enabled:");
46 println!(" Length: {} bytes", default_text.len());
47 println!(
48 " Contains OSC 8 codes: {}",
49 default_text.contains("\x1b]8;")
50 );
51
52 println!("\nWith hyperlinks disabled:");
53 println!(" Length: {} bytes", no_hyperlink_text.len());
54 println!(
55 " Contains OSC 8 codes: {}",
56 no_hyperlink_text.contains("\x1b]8;")
57 );
58
59 println!("\n=== Example 4: JSON output (no hyperlinks) ===\n");
60
61 let json = error.to_json();
62 println!("{}", serde_json::to_string_pretty(&json).unwrap());
63
64 println!("\n=== Example 5: Multiple diagnostics with custom rendering ===\n");
65
66 let error2 = DiagnosticMessageBuilder::error("Type mismatch")
67 .with_code("Q-1-15")
68 .problem("Expected string, found number")
69 .add_detail("Value: 42")
70 .add_detail("Expected type: string")
71 .build();
72
73 let error3 = DiagnosticMessageBuilder::error("Missing field")
74 .with_code("Q-1-20")
75 .problem("Required field 'author' not found")
76 .add_hint("Add an 'author' field to your configuration")
77 .build();
78
79 let errors = [error, error2, error3];
80
81 // Render all with consistent options
82 let no_hyperlinks = TextRenderOptions {
83 enable_hyperlinks: false,
84 };
85
86 for (i, err) in errors.iter().enumerate() {
87 println!("Error {}:", i + 1);
88 println!("{}", err.to_text_with_options(Some(&ctx), &no_hyperlinks));
89 println!();
90 }
91}Sourcepub fn add_detail_at(
self,
detail: impl Into<MessageContent>,
location: SourceInfo,
) -> Self
pub fn add_detail_at( self, detail: impl Into<MessageContent>, location: SourceInfo, ) -> Self
Add an error detail with a source location.
This allows adding contextual information that points to specific locations in the source code, creating rich multi-location error messages.
§Example
use quarto_error_reporting::DiagnosticMessageBuilder;
let error = DiagnosticMessageBuilder::error("Mismatched brackets")
.add_detail_at("Opening bracket here", opening_location)
.add_detail_at("But no closing bracket found", end_location)
.build();Sourcepub fn add_info(self, info: impl Into<MessageContent>) -> Self
pub fn add_info(self, info: impl Into<MessageContent>) -> Self
Add an info detail (displayed with info bullet).
Info details provide additional context or explanatory information.
§Example
use quarto_error_reporting::DiagnosticMessageBuilder;
let error = DiagnosticMessageBuilder::error("Missing file")
.add_detail("Could not find `config.yaml`")
.add_info("Default configuration will be used")
.build();Examples found in repository?
8fn main() {
9 println!("=== Example 1: Simple builder usage ===\n");
10
11 let error1 = DiagnosticMessageBuilder::error("Invalid input")
12 .problem("Value must be numeric")
13 .add_detail("Found text in column 3")
14 .add_hint("Check the input file format")
15 .build();
16
17 println!("{}", error1.to_text(None));
18
19 println!("\n=== Example 2: Tidyverse four-part structure ===\n");
20
21 let error2 = DiagnosticMessageBuilder::error("Incompatible types")
22 .problem("Cannot combine date and datetime types")
23 .add_detail("`x` has type `date`")
24 .add_detail("`y` has type `datetime`")
25 .add_info("Both values come from the same data source")
26 .add_hint("Convert both to the same type first?")
27 .build();
28
29 println!("{}", error2.to_text(None));
30
31 println!("\n=== Example 3: Multiple details and hints ===\n");
32
33 let error3 = DiagnosticMessageBuilder::error("Schema validation failed")
34 .problem("Configuration does not match expected schema")
35 .add_detail("Property `title` has type `number`")
36 .add_detail("Expected type is `string`")
37 .add_detail("Property `author` is missing")
38 .add_info("Schema is defined in `_quarto.yml`")
39 .add_hint("Did you forget quotes around the title?")
40 .add_hint("Add an `author` field to the configuration")
41 .build();
42
43 println!("{}", error3.to_text(None));
44
45 println!("\n=== Example 4: Builder validation ===\n");
46
47 // This will trigger validation warnings
48 let (msg, warnings) = DiagnosticMessageBuilder::error("Validation test")
49 .add_detail("Detail 1")
50 .add_detail("Detail 2")
51 .add_detail("Detail 3")
52 .add_detail("Detail 4")
53 .add_detail("Detail 5")
54 .add_detail("Detail 6") // Too many!
55 .build_with_validation();
56
57 println!("{}", msg.to_text(None));
58
59 if !warnings.is_empty() {
60 println!("\nValidation warnings:");
61 for warning in warnings {
62 println!(" ⚠ {}", warning);
63 }
64 }
65}Sourcepub fn add_info_at(
self,
info: impl Into<MessageContent>,
location: SourceInfo,
) -> Self
pub fn add_info_at( self, info: impl Into<MessageContent>, location: SourceInfo, ) -> Self
Add an info detail with a source location.
Sourcepub fn add_note(self, note: impl Into<MessageContent>) -> Self
pub fn add_note(self, note: impl Into<MessageContent>) -> Self
Add a note detail (displayed with plain bullet).
§Example
use quarto_error_reporting::DiagnosticMessageBuilder;
let error = DiagnosticMessageBuilder::error("Parse error")
.add_note("This is an experimental feature")
.build();Sourcepub fn add_note_at(
self,
note: impl Into<MessageContent>,
location: SourceInfo,
) -> Self
pub fn add_note_at( self, note: impl Into<MessageContent>, location: SourceInfo, ) -> Self
Add a note detail with a source location.
Sourcepub fn add_faded_at(
self,
content: impl Into<MessageContent>,
location: SourceInfo,
) -> Self
pub fn add_faded_at( self, content: impl Into<MessageContent>, location: SourceInfo, ) -> Self
Add a faded detail with a source location.
Rendered with the same dim grey colour Ariadne uses for unlabelled source characters, so it visually “punches a hole” in any wider label that also covers the same column range. Useful for excluding block-quote prefixes or other prefix decorations from the highlight of a multi-line span.
Sourcepub fn add_hint(self, hint: impl Into<MessageContent>) -> Self
pub fn add_hint(self, hint: impl Into<MessageContent>) -> Self
Add a hint for fixing the error.
Following tidyverse guidelines, hints should:
- Only be included when the problem source is clear and common
- Provide straightforward fix suggestions
- End with a question mark if suggesting action
§Example
use quarto_error_reporting::DiagnosticMessageBuilder;
let error = DiagnosticMessageBuilder::error("Function not found")
.problem("Could not find function `summarise()`")
.add_hint("Did you mean `summarize()`?")
.build();
assert_eq!(error.hints.len(), 1);Examples found in repository?
18fn main() {
19 let mut ctx = SourceContext::new();
20 let source = "title: My Document\nformat:\n html:\n theme: nosuchtheme\n";
21 let file_id = ctx.add_file("_quarto.yml".to_string(), Some(source.to_string()));
22
23 // Point at `nosuchtheme` on line 4 (byte offsets into `source`).
24 let start = source.find("nosuchtheme").unwrap();
25 let location = SourceInfo::original(file_id, start, start + "nosuchtheme".len());
26
27 let diag = DiagnosticMessageBuilder::error("Unknown theme")
28 .with_code("Q-14-1")
29 .with_location(location)
30 .problem("`nosuchtheme` is not a known Quarto theme")
31 .add_hint("Did you mean `cosmo`, `darkly`, or `flatly`?")
32 .build();
33
34 // Disable hyperlinks so the output is path-stable in a demo.
35 let opts = TextRenderOptions {
36 enable_hyperlinks: false,
37 };
38
39 // `None` uses the default renderer for the enabled features.
40 println!("=== default renderer (None) ===\n");
41 println!("{}", diag.to_text_with_renderer(Some(&ctx), &opts, None));
42
43 #[cfg(feature = "ariadne")]
44 {
45 println!("=== SourceRenderer::Ariadne ===\n");
46 println!(
47 "{}",
48 diag.to_text_with_renderer(Some(&ctx), &opts, Some(SourceRenderer::Ariadne))
49 );
50 }
51
52 #[cfg(feature = "annotate-snippets")]
53 {
54 println!("=== SourceRenderer::AnnotateSnippets ===\n");
55 println!(
56 "{}",
57 diag.to_text_with_renderer(Some(&ctx), &opts, Some(SourceRenderer::AnnotateSnippets),)
58 );
59 }
60}More examples
8fn main() {
9 println!("=== Example 1: Simple builder usage ===\n");
10
11 let error1 = DiagnosticMessageBuilder::error("Invalid input")
12 .problem("Value must be numeric")
13 .add_detail("Found text in column 3")
14 .add_hint("Check the input file format")
15 .build();
16
17 println!("{}", error1.to_text(None));
18
19 println!("\n=== Example 2: Tidyverse four-part structure ===\n");
20
21 let error2 = DiagnosticMessageBuilder::error("Incompatible types")
22 .problem("Cannot combine date and datetime types")
23 .add_detail("`x` has type `date`")
24 .add_detail("`y` has type `datetime`")
25 .add_info("Both values come from the same data source")
26 .add_hint("Convert both to the same type first?")
27 .build();
28
29 println!("{}", error2.to_text(None));
30
31 println!("\n=== Example 3: Multiple details and hints ===\n");
32
33 let error3 = DiagnosticMessageBuilder::error("Schema validation failed")
34 .problem("Configuration does not match expected schema")
35 .add_detail("Property `title` has type `number`")
36 .add_detail("Expected type is `string`")
37 .add_detail("Property `author` is missing")
38 .add_info("Schema is defined in `_quarto.yml`")
39 .add_hint("Did you forget quotes around the title?")
40 .add_hint("Add an `author` field to the configuration")
41 .build();
42
43 println!("{}", error3.to_text(None));
44
45 println!("\n=== Example 4: Builder validation ===\n");
46
47 // This will trigger validation warnings
48 let (msg, warnings) = DiagnosticMessageBuilder::error("Validation test")
49 .add_detail("Detail 1")
50 .add_detail("Detail 2")
51 .add_detail("Detail 3")
52 .add_detail("Detail 4")
53 .add_detail("Detail 5")
54 .add_detail("Detail 6") // Too many!
55 .build_with_validation();
56
57 println!("{}", msg.to_text(None));
58
59 if !warnings.is_empty() {
60 println!("\nValidation warnings:");
61 for warning in warnings {
62 println!(" ⚠ {}", warning);
63 }
64 }
65}9fn main() {
10 println!("=== Example 1: Error with source location ===\n");
11
12 // Create a source context
13 let mut ctx = SourceContext::new();
14 let file_id = ctx.add_file(
15 "example.qmd".to_string(),
16 Some("title: My Document\nauthor: John Doe\ndate: 2024-01-01\n".to_string()),
17 );
18
19 // Create a location (let's say there's an error in "My Document" - offsets 7 to 18)
20 let location = SourceInfo::original(file_id, 7, 18);
21
22 let error = DiagnosticMessageBuilder::error("Invalid title format")
23 .with_code("Q-1-10")
24 .with_location(location)
25 .problem("Title must be a string, not a complex object")
26 .add_detail("Title value starts at this location")
27 .add_hint("Ensure the title is a simple quoted string")
28 .build();
29
30 // Render WITHOUT context - shows offset
31 println!("Without context:");
32 println!("{}", error.to_text(None));
33
34 println!("\n---\n");
35
36 // Render WITH context - shows file path and line:column
37 println!("With context:");
38 println!("{}", error.to_text(Some(&ctx)));
39
40 println!("\n=== Example 2: Multiple locations ===\n");
41
42 let another_ctx = SourceContext::new();
43
44 // Note: This example shows the API, but without actual file content,
45 // the rendering will still show offsets. In real usage with proper
46 // SourceContext, this would show rich source snippets via ariadne.
47
48 let location2 = SourceInfo::original(quarto_source_map::FileId(0), 100, 110);
49
50 let error2 = DiagnosticMessageBuilder::error("Unclosed code block")
51 .with_code("Q-2-301")
52 .with_location(location2)
53 .problem("Code block started but never closed")
54 .add_detail("The opening ``` was found but no closing ``` before end of block")
55 .add_hint("Add a closing ``` on a new line")
56 .build();
57
58 println!("{}", error2.to_text(Some(&another_ctx)));
59
60 println!("\n=== Example 3: JSON output with location ===\n");
61
62 let json = error.to_json();
63 println!("{}", serde_json::to_string_pretty(&json).unwrap());
64}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}9fn main() {
10 println!("=== Example 1: Default rendering (with hyperlinks) ===\n");
11
12 let mut ctx = SourceContext::new();
13 let file_id = ctx.add_file(
14 "document.qmd".to_string(),
15 Some("# My Document\n\nSome content here.\n".to_string()),
16 );
17
18 let location = SourceInfo::original(file_id, 15, 27);
19
20 let error = DiagnosticMessageBuilder::error("Parse error")
21 .with_code("Q-2-100")
22 .with_location(location)
23 .problem("Invalid markdown syntax")
24 .add_hint("Check the markdown formatting")
25 .build();
26
27 // Default rendering includes OSC 8 hyperlinks for file paths
28 let default_text = error.to_text(Some(&ctx));
29 println!("{}", default_text);
30
31 println!("\n=== Example 2: Rendering without hyperlinks (for tests) ===\n");
32
33 // Disable hyperlinks - useful for snapshot testing where absolute paths
34 // would cause differences between machines
35 let options = TextRenderOptions {
36 enable_hyperlinks: false,
37 };
38
39 let no_hyperlink_text = error.to_text_with_options(Some(&ctx), &options);
40 println!("{}", no_hyperlink_text);
41
42 println!("\n=== Example 3: Comparing outputs ===\n");
43
44 // Show the difference in output
45 println!("With hyperlinks enabled:");
46 println!(" Length: {} bytes", default_text.len());
47 println!(
48 " Contains OSC 8 codes: {}",
49 default_text.contains("\x1b]8;")
50 );
51
52 println!("\nWith hyperlinks disabled:");
53 println!(" Length: {} bytes", no_hyperlink_text.len());
54 println!(
55 " Contains OSC 8 codes: {}",
56 no_hyperlink_text.contains("\x1b]8;")
57 );
58
59 println!("\n=== Example 4: JSON output (no hyperlinks) ===\n");
60
61 let json = error.to_json();
62 println!("{}", serde_json::to_string_pretty(&json).unwrap());
63
64 println!("\n=== Example 5: Multiple diagnostics with custom rendering ===\n");
65
66 let error2 = DiagnosticMessageBuilder::error("Type mismatch")
67 .with_code("Q-1-15")
68 .problem("Expected string, found number")
69 .add_detail("Value: 42")
70 .add_detail("Expected type: string")
71 .build();
72
73 let error3 = DiagnosticMessageBuilder::error("Missing field")
74 .with_code("Q-1-20")
75 .problem("Required field 'author' not found")
76 .add_hint("Add an 'author' field to your configuration")
77 .build();
78
79 let errors = [error, error2, error3];
80
81 // Render all with consistent options
82 let no_hyperlinks = TextRenderOptions {
83 enable_hyperlinks: false,
84 };
85
86 for (i, err) in errors.iter().enumerate() {
87 println!("Error {}:", i + 1);
88 println!("{}", err.to_text_with_options(Some(&ctx), &no_hyperlinks));
89 println!();
90 }
91}Sourcepub fn build(self) -> DiagnosticMessage
pub fn build(self) -> DiagnosticMessage
Build the diagnostic message.
This consumes the builder and returns the constructed DiagnosticMessage.
§Example
use quarto_error_reporting::DiagnosticMessageBuilder;
let error = DiagnosticMessageBuilder::error("Parse error")
.problem("Invalid syntax")
.build();
assert_eq!(error.title, "Parse error");Examples found in repository?
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}More examples
18fn main() {
19 let mut ctx = SourceContext::new();
20 let source = "title: My Document\nformat:\n html:\n theme: nosuchtheme\n";
21 let file_id = ctx.add_file("_quarto.yml".to_string(), Some(source.to_string()));
22
23 // Point at `nosuchtheme` on line 4 (byte offsets into `source`).
24 let start = source.find("nosuchtheme").unwrap();
25 let location = SourceInfo::original(file_id, start, start + "nosuchtheme".len());
26
27 let diag = DiagnosticMessageBuilder::error("Unknown theme")
28 .with_code("Q-14-1")
29 .with_location(location)
30 .problem("`nosuchtheme` is not a known Quarto theme")
31 .add_hint("Did you mean `cosmo`, `darkly`, or `flatly`?")
32 .build();
33
34 // Disable hyperlinks so the output is path-stable in a demo.
35 let opts = TextRenderOptions {
36 enable_hyperlinks: false,
37 };
38
39 // `None` uses the default renderer for the enabled features.
40 println!("=== default renderer (None) ===\n");
41 println!("{}", diag.to_text_with_renderer(Some(&ctx), &opts, None));
42
43 #[cfg(feature = "ariadne")]
44 {
45 println!("=== SourceRenderer::Ariadne ===\n");
46 println!(
47 "{}",
48 diag.to_text_with_renderer(Some(&ctx), &opts, Some(SourceRenderer::Ariadne))
49 );
50 }
51
52 #[cfg(feature = "annotate-snippets")]
53 {
54 println!("=== SourceRenderer::AnnotateSnippets ===\n");
55 println!(
56 "{}",
57 diag.to_text_with_renderer(Some(&ctx), &opts, Some(SourceRenderer::AnnotateSnippets),)
58 );
59 }
60}8fn main() {
9 println!("=== Example 1: Simple builder usage ===\n");
10
11 let error1 = DiagnosticMessageBuilder::error("Invalid input")
12 .problem("Value must be numeric")
13 .add_detail("Found text in column 3")
14 .add_hint("Check the input file format")
15 .build();
16
17 println!("{}", error1.to_text(None));
18
19 println!("\n=== Example 2: Tidyverse four-part structure ===\n");
20
21 let error2 = DiagnosticMessageBuilder::error("Incompatible types")
22 .problem("Cannot combine date and datetime types")
23 .add_detail("`x` has type `date`")
24 .add_detail("`y` has type `datetime`")
25 .add_info("Both values come from the same data source")
26 .add_hint("Convert both to the same type first?")
27 .build();
28
29 println!("{}", error2.to_text(None));
30
31 println!("\n=== Example 3: Multiple details and hints ===\n");
32
33 let error3 = DiagnosticMessageBuilder::error("Schema validation failed")
34 .problem("Configuration does not match expected schema")
35 .add_detail("Property `title` has type `number`")
36 .add_detail("Expected type is `string`")
37 .add_detail("Property `author` is missing")
38 .add_info("Schema is defined in `_quarto.yml`")
39 .add_hint("Did you forget quotes around the title?")
40 .add_hint("Add an `author` field to the configuration")
41 .build();
42
43 println!("{}", error3.to_text(None));
44
45 println!("\n=== Example 4: Builder validation ===\n");
46
47 // This will trigger validation warnings
48 let (msg, warnings) = DiagnosticMessageBuilder::error("Validation test")
49 .add_detail("Detail 1")
50 .add_detail("Detail 2")
51 .add_detail("Detail 3")
52 .add_detail("Detail 4")
53 .add_detail("Detail 5")
54 .add_detail("Detail 6") // Too many!
55 .build_with_validation();
56
57 println!("{}", msg.to_text(None));
58
59 if !warnings.is_empty() {
60 println!("\nValidation warnings:");
61 for warning in warnings {
62 println!(" ⚠ {}", warning);
63 }
64 }
65}9fn main() {
10 println!("=== Example 1: Error with source location ===\n");
11
12 // Create a source context
13 let mut ctx = SourceContext::new();
14 let file_id = ctx.add_file(
15 "example.qmd".to_string(),
16 Some("title: My Document\nauthor: John Doe\ndate: 2024-01-01\n".to_string()),
17 );
18
19 // Create a location (let's say there's an error in "My Document" - offsets 7 to 18)
20 let location = SourceInfo::original(file_id, 7, 18);
21
22 let error = DiagnosticMessageBuilder::error("Invalid title format")
23 .with_code("Q-1-10")
24 .with_location(location)
25 .problem("Title must be a string, not a complex object")
26 .add_detail("Title value starts at this location")
27 .add_hint("Ensure the title is a simple quoted string")
28 .build();
29
30 // Render WITHOUT context - shows offset
31 println!("Without context:");
32 println!("{}", error.to_text(None));
33
34 println!("\n---\n");
35
36 // Render WITH context - shows file path and line:column
37 println!("With context:");
38 println!("{}", error.to_text(Some(&ctx)));
39
40 println!("\n=== Example 2: Multiple locations ===\n");
41
42 let another_ctx = SourceContext::new();
43
44 // Note: This example shows the API, but without actual file content,
45 // the rendering will still show offsets. In real usage with proper
46 // SourceContext, this would show rich source snippets via ariadne.
47
48 let location2 = SourceInfo::original(quarto_source_map::FileId(0), 100, 110);
49
50 let error2 = DiagnosticMessageBuilder::error("Unclosed code block")
51 .with_code("Q-2-301")
52 .with_location(location2)
53 .problem("Code block started but never closed")
54 .add_detail("The opening ``` was found but no closing ``` before end of block")
55 .add_hint("Add a closing ``` on a new line")
56 .build();
57
58 println!("{}", error2.to_text(Some(&another_ctx)));
59
60 println!("\n=== Example 3: JSON output with location ===\n");
61
62 let json = error.to_json();
63 println!("{}", serde_json::to_string_pretty(&json).unwrap());
64}9fn main() {
10 println!("=== Example 1: Default rendering (with hyperlinks) ===\n");
11
12 let mut ctx = SourceContext::new();
13 let file_id = ctx.add_file(
14 "document.qmd".to_string(),
15 Some("# My Document\n\nSome content here.\n".to_string()),
16 );
17
18 let location = SourceInfo::original(file_id, 15, 27);
19
20 let error = DiagnosticMessageBuilder::error("Parse error")
21 .with_code("Q-2-100")
22 .with_location(location)
23 .problem("Invalid markdown syntax")
24 .add_hint("Check the markdown formatting")
25 .build();
26
27 // Default rendering includes OSC 8 hyperlinks for file paths
28 let default_text = error.to_text(Some(&ctx));
29 println!("{}", default_text);
30
31 println!("\n=== Example 2: Rendering without hyperlinks (for tests) ===\n");
32
33 // Disable hyperlinks - useful for snapshot testing where absolute paths
34 // would cause differences between machines
35 let options = TextRenderOptions {
36 enable_hyperlinks: false,
37 };
38
39 let no_hyperlink_text = error.to_text_with_options(Some(&ctx), &options);
40 println!("{}", no_hyperlink_text);
41
42 println!("\n=== Example 3: Comparing outputs ===\n");
43
44 // Show the difference in output
45 println!("With hyperlinks enabled:");
46 println!(" Length: {} bytes", default_text.len());
47 println!(
48 " Contains OSC 8 codes: {}",
49 default_text.contains("\x1b]8;")
50 );
51
52 println!("\nWith hyperlinks disabled:");
53 println!(" Length: {} bytes", no_hyperlink_text.len());
54 println!(
55 " Contains OSC 8 codes: {}",
56 no_hyperlink_text.contains("\x1b]8;")
57 );
58
59 println!("\n=== Example 4: JSON output (no hyperlinks) ===\n");
60
61 let json = error.to_json();
62 println!("{}", serde_json::to_string_pretty(&json).unwrap());
63
64 println!("\n=== Example 5: Multiple diagnostics with custom rendering ===\n");
65
66 let error2 = DiagnosticMessageBuilder::error("Type mismatch")
67 .with_code("Q-1-15")
68 .problem("Expected string, found number")
69 .add_detail("Value: 42")
70 .add_detail("Expected type: string")
71 .build();
72
73 let error3 = DiagnosticMessageBuilder::error("Missing field")
74 .with_code("Q-1-20")
75 .problem("Required field 'author' not found")
76 .add_hint("Add an 'author' field to your configuration")
77 .build();
78
79 let errors = [error, error2, error3];
80
81 // Render all with consistent options
82 let no_hyperlinks = TextRenderOptions {
83 enable_hyperlinks: false,
84 };
85
86 for (i, err) in errors.iter().enumerate() {
87 println!("Error {}:", i + 1);
88 println!("{}", err.to_text_with_options(Some(&ctx), &no_hyperlinks));
89 println!();
90 }
91}Sourcepub fn build_with_validation(self) -> (DiagnosticMessage, Vec<String>)
pub fn build_with_validation(self) -> (DiagnosticMessage, Vec<String>)
Build with validation.
This validates the message structure according to tidyverse guidelines:
- Warns if there’s no problem statement (recommended but not required)
- Warns if there are more than 5 details (overwhelming for users)
- Future: Could check that hints end with ‘?’
Returns warnings as a Vec of strings. An empty Vec means validation passed.
§Example
use quarto_error_reporting::DiagnosticMessageBuilder;
let (error, warnings) = DiagnosticMessageBuilder::error("Test error")
.build_with_validation();
// Warns because there's no problem statement
assert!(!warnings.is_empty());Examples found in repository?
8fn main() {
9 println!("=== Example 1: Simple builder usage ===\n");
10
11 let error1 = DiagnosticMessageBuilder::error("Invalid input")
12 .problem("Value must be numeric")
13 .add_detail("Found text in column 3")
14 .add_hint("Check the input file format")
15 .build();
16
17 println!("{}", error1.to_text(None));
18
19 println!("\n=== Example 2: Tidyverse four-part structure ===\n");
20
21 let error2 = DiagnosticMessageBuilder::error("Incompatible types")
22 .problem("Cannot combine date and datetime types")
23 .add_detail("`x` has type `date`")
24 .add_detail("`y` has type `datetime`")
25 .add_info("Both values come from the same data source")
26 .add_hint("Convert both to the same type first?")
27 .build();
28
29 println!("{}", error2.to_text(None));
30
31 println!("\n=== Example 3: Multiple details and hints ===\n");
32
33 let error3 = DiagnosticMessageBuilder::error("Schema validation failed")
34 .problem("Configuration does not match expected schema")
35 .add_detail("Property `title` has type `number`")
36 .add_detail("Expected type is `string`")
37 .add_detail("Property `author` is missing")
38 .add_info("Schema is defined in `_quarto.yml`")
39 .add_hint("Did you forget quotes around the title?")
40 .add_hint("Add an `author` field to the configuration")
41 .build();
42
43 println!("{}", error3.to_text(None));
44
45 println!("\n=== Example 4: Builder validation ===\n");
46
47 // This will trigger validation warnings
48 let (msg, warnings) = DiagnosticMessageBuilder::error("Validation test")
49 .add_detail("Detail 1")
50 .add_detail("Detail 2")
51 .add_detail("Detail 3")
52 .add_detail("Detail 4")
53 .add_detail("Detail 5")
54 .add_detail("Detail 6") // Too many!
55 .build_with_validation();
56
57 println!("{}", msg.to_text(None));
58
59 if !warnings.is_empty() {
60 println!("\nValidation warnings:");
61 for warning in warnings {
62 println!(" ⚠ {}", warning);
63 }
64 }
65}Trait Implementations§
Source§impl Clone for DiagnosticMessageBuilder
impl Clone for DiagnosticMessageBuilder
Source§fn clone(&self) -> DiagnosticMessageBuilder
fn clone(&self) -> DiagnosticMessageBuilder
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreAuto Trait Implementations§
impl Freeze for DiagnosticMessageBuilder
impl RefUnwindSafe for DiagnosticMessageBuilder
impl Send for DiagnosticMessageBuilder
impl Sync for DiagnosticMessageBuilder
impl Unpin for DiagnosticMessageBuilder
impl UnsafeUnpin for DiagnosticMessageBuilder
impl UnwindSafe for DiagnosticMessageBuilder
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Paint for Twhere
T: ?Sized,
impl<T> Paint for Twhere
T: ?Sized,
Source§fn fg(&self, value: Color) -> Painted<&T>
fn fg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self with the foreground set to
value.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like red() and
green(), which have the same functionality but are
pithier.
§Example
Set foreground color to white using fg():
use yansi::{Paint, Color};
painted.fg(Color::White);Set foreground color to white using white().
use yansi::Paint;
painted.white();Source§fn bright_black(&self) -> Painted<&T>
fn bright_black(&self) -> Painted<&T>
Source§fn bright_red(&self) -> Painted<&T>
fn bright_red(&self) -> Painted<&T>
Source§fn bright_green(&self) -> Painted<&T>
fn bright_green(&self) -> Painted<&T>
Source§fn bright_yellow(&self) -> Painted<&T>
fn bright_yellow(&self) -> Painted<&T>
Source§fn bright_blue(&self) -> Painted<&T>
fn bright_blue(&self) -> Painted<&T>
Source§fn bright_magenta(&self) -> Painted<&T>
fn bright_magenta(&self) -> Painted<&T>
Source§fn bright_cyan(&self) -> Painted<&T>
fn bright_cyan(&self) -> Painted<&T>
Source§fn bright_white(&self) -> Painted<&T>
fn bright_white(&self) -> Painted<&T>
Source§fn bg(&self, value: Color) -> Painted<&T>
fn bg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self with the background set to
value.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like on_red() and
on_green(), which have the same functionality but
are pithier.
§Example
Set background color to red using fg():
use yansi::{Paint, Color};
painted.bg(Color::Red);Set background color to red using on_red().
use yansi::Paint;
painted.on_red();Source§fn on_primary(&self) -> Painted<&T>
fn on_primary(&self) -> Painted<&T>
Source§fn on_magenta(&self) -> Painted<&T>
fn on_magenta(&self) -> Painted<&T>
Source§fn on_bright_black(&self) -> Painted<&T>
fn on_bright_black(&self) -> Painted<&T>
Source§fn on_bright_red(&self) -> Painted<&T>
fn on_bright_red(&self) -> Painted<&T>
Source§fn on_bright_green(&self) -> Painted<&T>
fn on_bright_green(&self) -> Painted<&T>
Source§fn on_bright_yellow(&self) -> Painted<&T>
fn on_bright_yellow(&self) -> Painted<&T>
Source§fn on_bright_blue(&self) -> Painted<&T>
fn on_bright_blue(&self) -> Painted<&T>
Source§fn on_bright_magenta(&self) -> Painted<&T>
fn on_bright_magenta(&self) -> Painted<&T>
Source§fn on_bright_cyan(&self) -> Painted<&T>
fn on_bright_cyan(&self) -> Painted<&T>
Source§fn on_bright_white(&self) -> Painted<&T>
fn on_bright_white(&self) -> Painted<&T>
Source§fn attr(&self, value: Attribute) -> Painted<&T>
fn attr(&self, value: Attribute) -> Painted<&T>
Enables the styling Attribute value.
This method should be used rarely. Instead, prefer to use
attribute-specific builder methods like bold() and
underline(), which have the same functionality
but are pithier.
§Example
Make text bold using attr():
use yansi::{Paint, Attribute};
painted.attr(Attribute::Bold);Make text bold using using bold().
use yansi::Paint;
painted.bold();Source§fn rapid_blink(&self) -> Painted<&T>
fn rapid_blink(&self) -> Painted<&T>
Source§fn quirk(&self, value: Quirk) -> Painted<&T>
fn quirk(&self, value: Quirk) -> Painted<&T>
Enables the yansi Quirk value.
This method should be used rarely. Instead, prefer to use quirk-specific
builder methods like mask() and
wrap(), which have the same functionality but are
pithier.
§Example
Enable wrapping using .quirk():
use yansi::{Paint, Quirk};
painted.quirk(Quirk::Wrap);Enable wrapping using wrap().
use yansi::Paint;
painted.wrap();Source§fn clear(&self) -> Painted<&T>
👎Deprecated since 1.0.1: renamed to resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.
fn clear(&self) -> Painted<&T>
renamed to resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.
Source§fn whenever(&self, value: Condition) -> Painted<&T>
fn whenever(&self, value: Condition) -> Painted<&T>
Conditionally enable styling based on whether the Condition value
applies. Replaces any previous condition.
See the crate level docs for more details.
§Example
Enable styling painted only when both stdout and stderr are TTYs:
use yansi::{Paint, Condition};
painted.red().on_yellow().whenever(Condition::STDOUTERR_ARE_TTY);