diagprint 0.8.1

A Rust diagnostics lifecycle framework for structured diagnostics, rendering, remediation, CI, editors, and telemetry.
Documentation
#![cfg(feature = "derive")]

use diagprint::{Applicability, DiagnosticErrorExt, LabelKind, Reporter, Severity, SourceLocation};
use thiserror::Error;

#[derive(Debug, Error, diagprint::Diagnostic)]
#[diag(
    severity = error,
    note = "configuration diagnostics are validated before startup",
    suggestion(
        title = "Inspect the configuration",
        explanation = "Review the configuration source before retrying",
        applicability = manual
    )
)]
enum ConfigError {
    #[error("missing configuration key")]
    #[diag(
        code = "CFG-101",
        severity = warning,
        help = "add the missing key",
        note = "this key is required",
        suggestion(
            title = "Add the missing key",
            explanation = "Insert the required key with an appropriate value",
            applicability = has_placeholders
        )
    )]
    MissingKey {
        #[diag(primary, message = "key is required here")]
        location: SourceLocation,
    },

    #[error("invalid configuration syntax")]
    #[diag(code = "CFG-102", help = "correct the invalid syntax")]
    InvalidSyntax {
        #[diag(
            primary,
            message = "invalid syntax",
            length = length
        )]
        location: SourceLocation,

        length: usize,

        #[diag(secondary, message = "related declaration")]
        related: SourceLocation,
    },

    #[error("configuration unavailable")]
    #[diag(code = "CFG-103")]
    Unavailable,
}

fn location(file: &str, line: u32, column: u32) -> SourceLocation {
    SourceLocation {
        file: file.into(),
        line,
        column: Some(column),
        revision: None,
    }
}

#[test]
fn enum_variant_overrides_and_inherits_metadata() {
    let reporter = Reporter::builder()
        .application("derive-enum-test")
        .build()
        .unwrap();

    let error = ConfigError::MissingKey {
        location: location("config.toml", 7, 1),
    };

    let diagnostic = error.to_diagprint(&reporter);

    assert_eq!(diagnostic.code.as_deref(), Some("CFG-101"));

    assert_eq!(diagnostic.severity, Severity::Warning);

    assert_eq!(diagnostic.help.as_deref(), Some("add the missing key"));

    assert_eq!(diagnostic.notes.len(), 2);

    assert_eq!(diagnostic.labels.len(), 1);

    assert_eq!(diagnostic.labels[0].kind, LabelKind::Primary);

    assert_eq!(diagnostic.suggestions.len(), 2);

    assert_eq!(
        diagnostic.suggestions[0].applicability,
        Applicability::Manual
    );

    assert_eq!(
        diagnostic.suggestions[1].applicability,
        Applicability::HasPlaceholders
    );

    assert!(!diagnostic.suggestions[1].is_machine_applicable());
}

#[test]
fn enum_named_variant_preserves_multiple_labels() {
    let reporter = Reporter::builder()
        .application("derive-enum-test")
        .build()
        .unwrap();

    let error = ConfigError::InvalidSyntax {
        location: location("config.toml", 10, 5),
        length: 4,
        related: location("defaults.toml", 2, 1),
    };

    let diagnostic = error.to_diagprint(&reporter);

    assert_eq!(diagnostic.code.as_deref(), Some("CFG-102"));

    assert_eq!(diagnostic.severity, Severity::Error);

    assert_eq!(diagnostic.labels.len(), 2);

    assert_eq!(diagnostic.labels[0].length, Some(4));

    assert_eq!(diagnostic.labels[1].kind, LabelKind::Secondary);
}

#[test]
fn unit_variant_is_supported() {
    let reporter = Reporter::builder()
        .application("derive-enum-test")
        .build()
        .unwrap();

    let diagnostic = ConfigError::Unavailable.to_diagprint(&reporter);

    assert_eq!(diagnostic.code.as_deref(), Some("CFG-103"));

    assert_eq!(diagnostic.severity, Severity::Error);

    assert!(diagnostic.labels.is_empty());

    assert_eq!(diagnostic.suggestions.len(), 1);
}