Skip to main content

DiagnosticMessage

Struct DiagnosticMessage 

Source
pub struct DiagnosticMessage {
    pub code: Option<String>,
    pub title: String,
    pub kind: DiagnosticKind,
    pub problem: Option<MessageContent>,
    pub details: Vec<DetailItem>,
    pub hints: Vec<MessageContent>,
    pub location: Option<SourceInfo>,
}
Expand description

A diagnostic message following tidyverse-style structure.

Structure:

  1. Code: Optional error code (e.g., “Q-1-1”) for searchability
  2. Title: Brief error message
  3. Kind: Error, Warning, Info
  4. Problem: What went wrong (the “must” or “can’t” statement)
  5. Details: Specific information (bulleted, max 5 per tidyverse)
  6. Hints: Optional guidance for fixing (ends with ?)

§Example

let msg = DiagnosticMessage {
    code: Some("Q-1-2".to_string()), // quarto-error-code-audit-ignore
    title: "Incompatible types".to_string(),
    kind: DiagnosticKind::Error,
    problem: Some("Cannot combine date and datetime types".into()),
    details: vec![
        DetailItem {
            kind: DetailKind::Error,
            content: "`x`{.arg} has type `date`{.type}".into(),
        },
        DetailItem {
            kind: DetailKind::Error,
            content: "`y`{.arg} has type `datetime`{.type}".into(),
        },
    ],
    hints: vec!["Convert both to the same type?".into()],
    source_spans: vec![],
};

Fields§

§code: Option<String>

Optional error code (e.g., “Q-1-1”)

Error codes are optional but encouraged. They provide:

  • Searchability (users can Google “Q-1-1”)
  • Stability (codes don’t change even if message wording improves)
  • Documentation (each code maps to a detailed explanation)
§title: String

Brief title for the error

§kind: DiagnosticKind

The kind of diagnostic (Error, Warning, Info)

§problem: Option<MessageContent>

The problem statement (the “what” - using “must” or “can’t”)

§details: Vec<DetailItem>

Specific error details (the “where/why” - max 5 per tidyverse)

§hints: Vec<MessageContent>

Optional hints for fixing (ends with ?)

§location: Option<SourceInfo>

Source location for this diagnostic

When present, this 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.

Implementations§

Source§

impl DiagnosticMessage

Source

pub fn builder() -> DiagnosticMessageBuilder

Access the diagnostic message builder API.

This is the recommended way to create diagnostic messages, as the builder API encodes tidyverse-style guidelines and makes it easy to construct well-structured error messages.

§Example
use quarto_error_reporting::{DiagnosticMessage, 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` has type `date`")
    .add_detail("`y` has type `datetime`")
    .add_hint("Convert both to the same type?")
    .build();
Source

pub fn new(kind: DiagnosticKind, title: impl Into<String>) -> Self

Create a new diagnostic message with just a title and kind.

Note: Consider using DiagnosticMessage::builder() instead for better structure.

Source

pub fn error(title: impl Into<String>) -> Self

Create an error diagnostic.

Note: Consider using DiagnosticMessage::builder().error() instead for better structure.

Examples found in repository?
examples/diagnostic_collector.rs (line 30)
29    fn error(&mut self, message: impl Into<String>) {
30        self.add(DiagnosticMessage::error(message.into()));
31    }
More examples
Hide additional examples
examples/basic_error.rs (line 9)
7fn main() {
8    // Create a simple error message
9    let error = DiagnosticMessage::error("File not found");
10
11    // Render to text
12    println!("{}", error.to_text(None));
13    println!();
14
15    // Create a warning
16    let warning = DiagnosticMessage::warning("Deprecated feature used");
17    println!("{}", warning.to_text(None));
18    println!();
19
20    // Create an info message
21    let info = DiagnosticMessage::info("Processing 42 files");
22    println!("{}", info.to_text(None));
23}
Source

pub fn warning(title: impl Into<String>) -> Self

Create a warning diagnostic.

Note: Consider using DiagnosticMessage::builder().warning() instead for better structure.

Examples found in repository?
examples/diagnostic_collector.rs (line 34)
33    fn warn(&mut self, message: impl Into<String>) {
34        self.add(DiagnosticMessage::warning(message.into()));
35    }
More examples
Hide additional examples
examples/basic_error.rs (line 16)
7fn main() {
8    // Create a simple error message
9    let error = DiagnosticMessage::error("File not found");
10
11    // Render to text
12    println!("{}", error.to_text(None));
13    println!();
14
15    // Create a warning
16    let warning = DiagnosticMessage::warning("Deprecated feature used");
17    println!("{}", warning.to_text(None));
18    println!();
19
20    // Create an info message
21    let info = DiagnosticMessage::info("Processing 42 files");
22    println!("{}", info.to_text(None));
23}
Source

pub fn info(title: impl Into<String>) -> Self

Create an info diagnostic.

Note: Consider using DiagnosticMessage::builder().info() instead for better structure.

Examples found in repository?
examples/basic_error.rs (line 21)
7fn main() {
8    // Create a simple error message
9    let error = DiagnosticMessage::error("File not found");
10
11    // Render to text
12    println!("{}", error.to_text(None));
13    println!();
14
15    // Create a warning
16    let warning = DiagnosticMessage::warning("Deprecated feature used");
17    println!("{}", warning.to_text(None));
18    println!();
19
20    // Create an info message
21    let info = DiagnosticMessage::info("Processing 42 files");
22    println!("{}", info.to_text(None));
23}
Source

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”).

§Example
use quarto_error_reporting::DiagnosticMessage;

let msg = DiagnosticMessage::error("YAML Syntax Error")
    .with_code("Q-1-1");
Source

pub fn docs_url(&self) -> Option<&str>

Get the documentation URL for this error, if it has an error code.

§Example

Resolves the code against the installed [CatalogProvider] (crate::catalog); returns None when no catalog is installed, the code is unknown, or the entry has no docs URL.

use quarto_error_reporting::DiagnosticMessage;

let msg = DiagnosticMessage::error("Internal Error")
    .with_code("Q-0-1");

// `Some(url)` iff a catalog mapping "Q-0-1" (with a docs URL) is installed.
let _ = msg.docs_url();
Source

pub fn to_text(&self, ctx: Option<&SourceContext>) -> String

Render this diagnostic message as text following tidyverse style.

This is a convenience method that uses default rendering options. For more control over rendering, use Self::to_text_with_options.

§Example
use quarto_error_reporting::DiagnosticMessageBuilder;

let msg = DiagnosticMessageBuilder::error("Invalid input")
    .problem("Values must be numeric")
    .add_detail("Found text in column 3")
    .add_hint("Convert to numbers first?")
    .build();
let text = msg.to_text(None);
assert!(text.contains("Error: Invalid input"));
assert!(text.contains("Values must be numeric"));
Examples found in repository?
examples/diagnostic_collector.rs (line 56)
55    fn to_text(&self, ctx: Option<&SourceContext>) -> Vec<String> {
56        self.diagnostics.iter().map(|d| d.to_text(ctx)).collect()
57    }
More examples
Hide additional examples
examples/basic_error.rs (line 12)
7fn main() {
8    // Create a simple error message
9    let error = DiagnosticMessage::error("File not found");
10
11    // Render to text
12    println!("{}", error.to_text(None));
13    println!();
14
15    // Create a warning
16    let warning = DiagnosticMessage::warning("Deprecated feature used");
17    println!("{}", warning.to_text(None));
18    println!();
19
20    // Create an info message
21    let info = DiagnosticMessage::info("Processing 42 files");
22    println!("{}", info.to_text(None));
23}
examples/builder_api.rs (line 17)
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}
examples/with_location.rs (line 32)
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}
examples/migration_helpers.rs (line 18)
9fn main() {
10    println!("=== Example 1: Using generic_error! macro ===\n");
11
12    // The generic_error! macro creates a DiagnosticMessage with:
13    // - Code: Q-0-99 (generic migration error)
14    // - File and line number where the macro was invoked
15    // - The provided message
16    let error = generic_error!("Something went wrong during migration");
17
18    println!("{}", error.to_text(None));
19    println!();
20
21    // Check the error code
22    println!("Error code: {:?}", error.code);
23    println!();
24
25    println!("=== Example 2: Using generic_warning! macro ===\n");
26
27    let warning = generic_warning!("This feature is not yet fully migrated");
28
29    println!("{}", warning.to_text(None));
30    println!();
31
32    println!("=== Example 3: Migration pattern in practice ===\n");
33
34    // During migration, you might replace old error handling like this:
35    //
36    // OLD CODE:
37    //   eprintln!("Error: File not found: {}", path);
38    //   return Err(...);
39    //
40    // NEW CODE (migration phase):
41    //   let error = generic_error!(format!("File not found: {}", path));
42    //   eprintln!("{}", error.to_text(None));
43    //   return Err(...);
44    //
45    // FINAL CODE:
46    //   let error = DiagnosticMessageBuilder::error("File not found")
47    //       .with_code("Q-X-Y")  // Proper error code
48    //       .problem(format!("Could not open file: {}", path))
49    //       .add_hint("Check that the file exists and you have permission")
50    //       .build();
51
52    let path = "/nonexistent/file.qmd";
53    let migration_error = generic_error!(format!("File not found: {}", path));
54
55    println!("Migration-style error:");
56    println!("{}", migration_error.to_text(None));
57    println!();
58
59    println!("=== Example 4: JSON output shows file/line info ===\n");
60
61    let error_with_location = generic_error!("Error with source tracking");
62    let json = error_with_location.to_json();
63
64    println!("{}", serde_json::to_string_pretty(&json).unwrap());
65    println!();
66
67    println!("Note: The generic_error! and generic_warning! macros are intended");
68    println!("for migration purposes only. New code should use DiagnosticMessageBuilder");
69    println!("with proper error codes (Q-X-Y) instead of Q-0-99.");
70}
examples/custom_rendering.rs (line 28)
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}
Source

pub fn to_text_with_options( &self, ctx: Option<&SourceContext>, options: &TextRenderOptions, ) -> String

Render this diagnostic message as text following tidyverse style with custom options.

Format:

Error: title
Problem statement here
✖ Error detail 1
✖ Error detail 2
ℹ Info detail
• Note detail
? Hint 1
? Hint 2
§Example
use quarto_error_reporting::{DiagnosticMessageBuilder, TextRenderOptions};

let msg = DiagnosticMessageBuilder::error("Invalid input")
    .problem("Values must be numeric")
    .add_detail("Found text in column 3")
    .add_hint("Convert to numbers first?")
    .build();

// Disable hyperlinks for snapshot testing
let options = TextRenderOptions { enable_hyperlinks: false };
let text = msg.to_text_with_options(None, &options);
assert!(text.contains("Error: Invalid input"));
Examples found in repository?
examples/custom_rendering.rs (line 39)
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}
Source

pub fn to_text_with_renderer( &self, ctx: Option<&SourceContext>, options: &TextRenderOptions, renderer: Option<SourceRenderer>, ) -> String

Like Self::to_text_with_options, but explicitly selects which source-context snippet renderer draws the visual code excerpt.

Pass Some(SourceRenderer::Ariadne) or Some(SourceRenderer::AnnotateSnippets) to force a specific renderer (the corresponding feature must be enabled), or None to use SourceRenderer::default_for_features. This is the seam for experimenting with diagnostic rendering styles without changing the rest of the API: only the source-excerpt block differs between renderers; the surrounding structured text (unlocated details, hints) is identical.

When no renderer feature is enabled — or the diagnostic has no location / source context — this falls back to the structured tidyverse-style text block, exactly as Self::to_text_with_options.

§Example
use quarto_error_reporting::{DiagnosticMessageBuilder, TextRenderOptions};

let msg = DiagnosticMessageBuilder::error("Invalid input")
    .problem("Values must be numeric")
    .build();

// `None` picks the default renderer for the enabled features.
let text = msg.to_text_with_renderer(None, &TextRenderOptions::default(), None);
assert!(text.contains("Invalid input"));
Examples found in repository?
examples/renderer_selection.rs (line 41)
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}
Source

pub fn to_json(&self) -> Value

Render this diagnostic message as a JSON value.

Returns a structured JSON object with all fields:

{
  "kind": "error",
  "title": "Invalid input",
  "code": "Q-1-2", // quarto-error-code-audit-ignore
  "problem": "Values must be numeric",
  "details": [{"kind": "error", "content": "Found text in column 3"}],
  "hints": ["Convert to numbers first?"]
}
§Example
use quarto_error_reporting::DiagnosticMessage;

let msg = DiagnosticMessage::error("Something went wrong");
let json = msg.to_json();
assert_eq!(json["kind"], "error");
assert_eq!(json["title"], "Something went wrong");
Examples found in repository?
examples/with_location.rs (line 62)
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}
More examples
Hide additional examples
examples/migration_helpers.rs (line 62)
9fn main() {
10    println!("=== Example 1: Using generic_error! macro ===\n");
11
12    // The generic_error! macro creates a DiagnosticMessage with:
13    // - Code: Q-0-99 (generic migration error)
14    // - File and line number where the macro was invoked
15    // - The provided message
16    let error = generic_error!("Something went wrong during migration");
17
18    println!("{}", error.to_text(None));
19    println!();
20
21    // Check the error code
22    println!("Error code: {:?}", error.code);
23    println!();
24
25    println!("=== Example 2: Using generic_warning! macro ===\n");
26
27    let warning = generic_warning!("This feature is not yet fully migrated");
28
29    println!("{}", warning.to_text(None));
30    println!();
31
32    println!("=== Example 3: Migration pattern in practice ===\n");
33
34    // During migration, you might replace old error handling like this:
35    //
36    // OLD CODE:
37    //   eprintln!("Error: File not found: {}", path);
38    //   return Err(...);
39    //
40    // NEW CODE (migration phase):
41    //   let error = generic_error!(format!("File not found: {}", path));
42    //   eprintln!("{}", error.to_text(None));
43    //   return Err(...);
44    //
45    // FINAL CODE:
46    //   let error = DiagnosticMessageBuilder::error("File not found")
47    //       .with_code("Q-X-Y")  // Proper error code
48    //       .problem(format!("Could not open file: {}", path))
49    //       .add_hint("Check that the file exists and you have permission")
50    //       .build();
51
52    let path = "/nonexistent/file.qmd";
53    let migration_error = generic_error!(format!("File not found: {}", path));
54
55    println!("Migration-style error:");
56    println!("{}", migration_error.to_text(None));
57    println!();
58
59    println!("=== Example 4: JSON output shows file/line info ===\n");
60
61    let error_with_location = generic_error!("Error with source tracking");
62    let json = error_with_location.to_json();
63
64    println!("{}", serde_json::to_string_pretty(&json).unwrap());
65    println!();
66
67    println!("Note: The generic_error! and generic_warning! macros are intended");
68    println!("for migration purposes only. New code should use DiagnosticMessageBuilder");
69    println!("with proper error codes (Q-X-Y) instead of Q-0-99.");
70}
examples/diagnostic_collector.rs (line 113)
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}
examples/custom_rendering.rs (line 61)
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}

Trait Implementations§

Source§

impl Clone for DiagnosticMessage

Source§

fn clone(&self) -> DiagnosticMessage

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for DiagnosticMessage

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for DiagnosticMessage

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl PartialEq for DiagnosticMessage

Source§

fn eq(&self, other: &DiagnosticMessage) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Serialize for DiagnosticMessage

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for DiagnosticMessage

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Paint for T
where T: ?Sized,

Source§

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 primary(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: Primary].

§Example
println!("{}", value.primary());
Source§

fn fixed(&self, color: u8) -> Painted<&T>

Returns self with the fg() set to [Color :: Fixed].

§Example
println!("{}", value.fixed(color));
Source§

fn rgb(&self, r: u8, g: u8, b: u8) -> Painted<&T>

Returns self with the fg() set to [Color :: Rgb].

§Example
println!("{}", value.rgb(r, g, b));
Source§

fn black(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: Black].

§Example
println!("{}", value.black());
Source§

fn red(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: Red].

§Example
println!("{}", value.red());
Source§

fn green(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: Green].

§Example
println!("{}", value.green());
Source§

fn yellow(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: Yellow].

§Example
println!("{}", value.yellow());
Source§

fn blue(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: Blue].

§Example
println!("{}", value.blue());
Source§

fn magenta(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: Magenta].

§Example
println!("{}", value.magenta());
Source§

fn cyan(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: Cyan].

§Example
println!("{}", value.cyan());
Source§

fn white(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: White].

§Example
println!("{}", value.white());
Source§

fn bright_black(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: BrightBlack].

§Example
println!("{}", value.bright_black());
Source§

fn bright_red(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: BrightRed].

§Example
println!("{}", value.bright_red());
Source§

fn bright_green(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: BrightGreen].

§Example
println!("{}", value.bright_green());
Source§

fn bright_yellow(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: BrightYellow].

§Example
println!("{}", value.bright_yellow());
Source§

fn bright_blue(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: BrightBlue].

§Example
println!("{}", value.bright_blue());
Source§

fn bright_magenta(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: BrightMagenta].

§Example
println!("{}", value.bright_magenta());
Source§

fn bright_cyan(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: BrightCyan].

§Example
println!("{}", value.bright_cyan());
Source§

fn bright_white(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: BrightWhite].

§Example
println!("{}", value.bright_white());
Source§

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>

Returns self with the bg() set to [Color :: Primary].

§Example
println!("{}", value.on_primary());
Source§

fn on_fixed(&self, color: u8) -> Painted<&T>

Returns self with the bg() set to [Color :: Fixed].

§Example
println!("{}", value.on_fixed(color));
Source§

fn on_rgb(&self, r: u8, g: u8, b: u8) -> Painted<&T>

Returns self with the bg() set to [Color :: Rgb].

§Example
println!("{}", value.on_rgb(r, g, b));
Source§

fn on_black(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: Black].

§Example
println!("{}", value.on_black());
Source§

fn on_red(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: Red].

§Example
println!("{}", value.on_red());
Source§

fn on_green(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: Green].

§Example
println!("{}", value.on_green());
Source§

fn on_yellow(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: Yellow].

§Example
println!("{}", value.on_yellow());
Source§

fn on_blue(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: Blue].

§Example
println!("{}", value.on_blue());
Source§

fn on_magenta(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: Magenta].

§Example
println!("{}", value.on_magenta());
Source§

fn on_cyan(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: Cyan].

§Example
println!("{}", value.on_cyan());
Source§

fn on_white(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: White].

§Example
println!("{}", value.on_white());
Source§

fn on_bright_black(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: BrightBlack].

§Example
println!("{}", value.on_bright_black());
Source§

fn on_bright_red(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: BrightRed].

§Example
println!("{}", value.on_bright_red());
Source§

fn on_bright_green(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: BrightGreen].

§Example
println!("{}", value.on_bright_green());
Source§

fn on_bright_yellow(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: BrightYellow].

§Example
println!("{}", value.on_bright_yellow());
Source§

fn on_bright_blue(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: BrightBlue].

§Example
println!("{}", value.on_bright_blue());
Source§

fn on_bright_magenta(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: BrightMagenta].

§Example
println!("{}", value.on_bright_magenta());
Source§

fn on_bright_cyan(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: BrightCyan].

§Example
println!("{}", value.on_bright_cyan());
Source§

fn on_bright_white(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: BrightWhite].

§Example
println!("{}", value.on_bright_white());
Source§

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 bold(&self) -> Painted<&T>

Returns self with the attr() set to [Attribute :: Bold].

§Example
println!("{}", value.bold());
Source§

fn dim(&self) -> Painted<&T>

Returns self with the attr() set to [Attribute :: Dim].

§Example
println!("{}", value.dim());
Source§

fn italic(&self) -> Painted<&T>

Returns self with the attr() set to [Attribute :: Italic].

§Example
println!("{}", value.italic());
Source§

fn underline(&self) -> Painted<&T>

Returns self with the attr() set to [Attribute :: Underline].

§Example
println!("{}", value.underline());

Returns self with the attr() set to [Attribute :: Blink].

§Example
println!("{}", value.blink());

Returns self with the attr() set to [Attribute :: RapidBlink].

§Example
println!("{}", value.rapid_blink());
Source§

fn invert(&self) -> Painted<&T>

Returns self with the attr() set to [Attribute :: Invert].

§Example
println!("{}", value.invert());
Source§

fn conceal(&self) -> Painted<&T>

Returns self with the attr() set to [Attribute :: Conceal].

§Example
println!("{}", value.conceal());
Source§

fn strike(&self) -> Painted<&T>

Returns self with the attr() set to [Attribute :: Strike].

§Example
println!("{}", value.strike());
Source§

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 mask(&self) -> Painted<&T>

Returns self with the quirk() set to [Quirk :: Mask].

§Example
println!("{}", value.mask());
Source§

fn wrap(&self) -> Painted<&T>

Returns self with the quirk() set to [Quirk :: Wrap].

§Example
println!("{}", value.wrap());
Source§

fn linger(&self) -> Painted<&T>

Returns self with the quirk() set to [Quirk :: Linger].

§Example
println!("{}", value.linger());
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.

Returns self with the quirk() set to [Quirk :: Clear].

§Example
println!("{}", value.clear());
Source§

fn resetting(&self) -> Painted<&T>

Returns self with the quirk() set to [Quirk :: Resetting].

§Example
println!("{}", value.resetting());
Source§

fn bright(&self) -> Painted<&T>

Returns self with the quirk() set to [Quirk :: Bright].

§Example
println!("{}", value.bright());
Source§

fn on_bright(&self) -> Painted<&T>

Returns self with the quirk() set to [Quirk :: OnBright].

§Example
println!("{}", value.on_bright());
Source§

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);
Source§

fn new(self) -> Painted<Self>
where Self: Sized,

Create a new Painted with a default Style. Read more
Source§

fn paint<S>(&self, style: S) -> Painted<&Self>
where S: Into<Style>,

Apply a style wholesale to self. Any previous style is replaced. Read more
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.