diagprint 0.7.0

A Rust diagnostics lifecycle framework for structured diagnostics, rendering, remediation, CI, editors, and telemetry.
Documentation

diagprint

CI Crates.io Docs.rs License

diagprint is a Rust diagnostics lifecycle framework.

Define diagnostics once, then carry them safely through terminal output, compiler tooling, editors, CI, code scanning, guarded remediation, verification, telemetry, and privacy-aware external reporting.

diagprint turns failures into actionable, structured diagnostics with:

  • rich terminal rendering;
  • primary and secondary source labels;
  • virtual and in-memory sources;
  • immutable source snapshots;
  • per-source revision tracking;
  • stale-source detection;
  • captured revision-aware diagnostics;
  • JSON, Markdown, plain-text, GitHub Actions, and SARIF output;
  • ecosystem interoperability;
  • rustc and Cargo diagnostic ingestion;
  • guarded remediation;
  • transactional multi-file fixes;
  • post-fix verification;
  • version-aware documentation.

The core rule is:

Diagnostics may explain and propose. Mutation must be explicit, structured, validated, and reject uncertainty.

Installation

[dependencies]
diagprint = "0.6"

diagprint v0.6.0 uses Rust 2024 and supports Rust 1.85 and newer.

Optional Features

All optional features are disabled by default.

[dependencies]
diagprint = {
    version = "0.6",
    features = [
        "compression",
        "cybercore",
        "terminal-docs",
        "anyhow",
        "tracing",
        "miette",
        "codespan-reporting",
        "ariadne",
        "annotate-snippets",
    ]
}
Feature Purpose
compression gzip and Zstandard report compression
cybercore Cybercore theme integration
terminal-docs terminal documentation retrieval and highlighting
anyhow anyhow diagnostic integration
tracing tracing subscriber integration
miette miette interoperability
codespan-reporting codespan-reporting interoperability
ariadne Ariadne structured bridge
annotate-snippets annotate-snippets structured bridge

Quick Start

use diagprint::{Reporter, Severity};

fn main() -> std::io::Result<()> {
    let reporter = Reporter::builder()
        .application("myapp")
        .min_severity(Severity::Info)
        .build()?;

    let diagnostic = reporter
        .error("Network initialization failed")
        .code("NET-001")
        .cause("failed to open network interface")
        .note("fallback networking is unavailable")
        .help("check interface permissions and driver state");

    reporter.emit(&diagnostic)?;

    Ok(())
}

Source Diagnostics

Diagnostics can point directly at source locations.

let diagnostic = reporter
    .error("Invalid configuration value")
    .code("CFG-001")
    .label(
        "config.toml",
        12,
        Some(9),
        Some(5),
        Some("unsupported value"),
    )
    .secondary_label(
        "defaults.toml",
        4,
        Some(1),
        Some(7),
        Some("default declared here"),
    );

Primary and secondary labels remain structurally distinct through rendering and interop.

The terminal renderer displays them differently so related context does not look like an additional primary failure.

Virtual and In-Memory Sources

Source text does not need to exist on disk.

let reporter = Reporter::builder()
    .application("editor")
    .source(
        "memory://editor/main.rs",
        "let answer = old_value();\n",
    )
    .build()?;

let diagnostic = reporter
    .error("Invalid value")
    .label(
        "memory://editor/main.rs",
        1,
        Some(14),
        Some(9),
        Some("value"),
    );

reporter.emit(&diagnostic)?;

SourceCache stores virtual or generated source text:

use diagprint::SourceCache;

let cache = SourceCache::new();

cache.insert(
    "memory://generated.rs",
    "fn generated() {}\n",
);

Source names are matched exactly.

When source-aware terminal rendering is used, cached source takes precedence over filesystem fallback.

Cloned SourceCache handles share the same underlying source state.

Immutable Source Snapshots

Mutable editor buffers can change after a diagnostic is created.

SourceSnapshot freezes the source view at a point in time while source text itself remains shared through Arc.

let cache = reporter.source_cache();

let snapshot = cache.snapshot();

let diagnostic = reporter
    .error("old diagnostic")
    .label(
        "memory://editor/main.rs",
        1,
        Some(14),
        Some(9),
        Some("original value"),
    )
    .bind_source_revisions(&snapshot);

Later mutations to the live cache do not change the snapshot.

Source Revisions

Every cached source tracks a SourceRevision.

let revision = cache.insert_revisioned(
    "memory://editor/main.rs",
    "let answer = old_value();\n",
);

println!("revision: {revision}");

Revisions are tracked independently per source.

Every insertion advances the revision, including replacement with identical text.

Revision history survives removal and clearing so an old revision cannot be silently reused after a source is recreated.

Revision-Bound Diagnostics

A diagnostic can bind its source locations to the source revision it was created from.

let snapshot = reporter.source_snapshot();

let diagnostic = reporter
    .error("Invalid editor value")
    .label(
        "memory://editor/main.rs",
        1,
        Some(14),
        Some(9),
        Some("old value"),
    )
    .bind_source_revisions(&snapshot);

If the live source later changes, a revision-aware terminal render fails closed.

Instead of underlining unrelated newer text, it reports the mismatch:

! stale source: r1 != r2

No misleading newer source excerpt is shown.

Revision-unbound diagnostics retain the existing source behavior.

Captured Diagnostics

CapturedDiagnostic pairs a diagnostic with the immutable source snapshot that belongs to it.

let captured = reporter.capture(
    reporter
        .error("editor diagnostic")
        .label(
            "memory://editor/main.rs",
            1,
            Some(14),
            Some(9),
            Some("source at diagnostic time"),
        ),
);

The live buffer may then continue changing:

reporter.register_source(
    "memory://editor/main.rs",
    "let answer = new_value();\n",
);

assert!(
    captured.is_stale(
        &reporter.source_cache()
    )
);

The captured diagnostic still renders against its original immutable source:

reporter.emit_captured(&captured)?;

Source text stays outside Diagnostic serialization.

Source Providers

Integrations which own in-memory source text can expose it through SourceProvider.

A reporter can register those sources:

reporter.register_sources(&provider);

Or a builder can import them:

let reporter = Reporter::builder()
    .sources_from(&provider)
    .build()?;

The Ariadne and annotate-snippets bridges implement this handoff.

GitHub Actions Annotations

diagprint can emit native GitHub Actions workflow-command annotations.

let diagnostic = reporter
    .error("Cannot combine incompatible values")
    .code("E-TYPE")
    .label(
        "src/main.rs",
        12,
        Some(9),
        Some(5),
        Some("numeric value"),
    )
    .secondary_label(
        "src/lib.rs",
        4,
        Some(5),
        Some(8),
        Some("string declaration"),
    );

reporter.emit_github_actions(&diagnostic)?;

Severity mapping:

diagprint GitHub Actions
Trace notice
Debug notice
Info notice
Warning warning
Error error
Fatal error

Primary labels retain the diagnostic severity.

Secondary labels are emitted as notices so related source locations remain visible without appearing as additional failures.

Workflow command data and properties are escaped before output.

SARIF 2.1.0

SarifRenderer produces SARIF for GitHub Code Scanning and other SARIF 2.1.0 consumers.

use diagprint::render::SarifRenderer;

let first = reporter
    .error("Type mismatch")
    .code("E-TYPE")
    .label(
        "src/main.rs",
        12,
        Some(9),
        Some(5),
        Some("numeric value"),
    )
    .secondary_label(
        "src/lib.rs",
        4,
        Some(5),
        Some(8),
        Some("declared here"),
    );

let second = reporter
    .warning("Deprecated configuration")
    .code("W-CONFIG")
    .label(
        "src/config.rs",
        7,
        Some(1),
        Some(12),
        Some("deprecated setting"),
    );

SarifRenderer.write_many(
    "target/diagprint.sarif",
    [&first, &second],
)?;

SARIF output includes:

  • SARIF 2.1.0 metadata;
  • deterministic rule IDs;
  • deterministic rule indices;
  • severity mapping;
  • primary source locations;
  • related source locations;
  • exclusive SARIF end-column ranges;
  • notes;
  • help;
  • cause information;
  • diagprint source revision metadata.

No synthetic fingerprints are invented.

write_many() writes one complete SARIF document rather than appending independent JSON documents.

Built-In Output Formats

The same diagnostic data can be rendered as:

  • terminal output;
  • plain text;
  • JSON;
  • Markdown;
  • GitHub Actions annotations;
  • SARIF 2.1.0.
use diagprint::render::{
    GithubActionsRenderer,
    JsonRenderer,
    MarkdownRenderer,
    PlainRenderer,
    Renderer,
    SarifRenderer,
    TerminalRenderer,
};

Diagnostic construction stays independent from presentation.

Generic Diagnostic Interop

diagprint provides a dependency-free interoperability protocol for structured diagnostics.

The generic protocol can preserve:

  • severity;
  • diagnostic codes;
  • messages;
  • help;
  • notes;
  • source labels;
  • primary and secondary label roles;
  • causes;
  • documentation links;
  • related diagnostics.

Generic interoperability deliberately does not grant remediation trust.

An integration that wants automatic edits must establish remediation trust separately.

anyhow

Enable:

features = ["anyhow"]

The anyhow adapter can convert error context into structured diagprint diagnostics while preserving the error chain.

tracing

Enable:

features = ["tracing"]

The tracing integration connects structured tracing events with diagprint reporting.

miette

Enable:

features = ["miette"]

The miette integration maps compatible diagnostic metadata into diagprint's structured interoperability model.

codespan-reporting

Enable:

features = ["codespan-reporting"]

Codespan diagnostics are routed through the generic interoperability layer.

Ariadne

Enable:

features = ["ariadne"]

AriadneBridge captures structured source and label metadata once and can produce both:

  • a real Ariadne report;
  • a diagprint interoperability diagnostic.

The bridge does not parse rendered Ariadne terminal text and does not rely on private Ariadne internals.

Ariadne source spans are resolved into one-based source locations for diagprint.

annotate-snippets

Enable:

features = ["annotate-snippets"]

The annotate-snippets bridge:

  • preserves primary and context labels;
  • validates byte ranges;
  • rejects invalid UTF-8 boundaries;
  • converts byte spans into one-based line and column locations;
  • exposes its in-memory sources through SourceProvider.

Rustc Diagnostics

diagprint can ingest structured rustc JSON diagnostics.

The compiler integration can preserve information including:

  • severity;
  • error codes;
  • source spans;
  • child diagnostics;
  • structured suggestions;
  • applicability;
  • compiler source edits;
  • rendered compiler context when available.

Compiler edits require explicit trusted source-root hydration before they can participate in remediation.

Cargo Intelligence

Cargo ingestion can track information including:

  • workspace packages;
  • exact package versions;
  • workspace membership;
  • targets;
  • resolved dependencies;
  • renamed dependencies;
  • compiler artifacts;
  • build-script results;
  • build completion;
  • build summaries.

Unknown Cargo messages are preserved for forward compatibility.

Diagnostic Suggestions

Diagnostics can carry structured suggestions.

use diagprint::{
    Applicability,
    Edit,
    Suggestion,
    TextRange,
};

let suggestion =
    Suggestion::new(
        "Replace deprecated value",
    )
    .applicability(
        Applicability::MachineApplicable,
    )
    .edit(
        Edit::replace(
            "config.toml",
            TextRange::new(10, 13),
            "old",
            "new",
        ),
    );

Applicability levels are:

pub enum Applicability {
    MachineApplicable,
    MaybeIncorrect,
    HasPlaceholders,
    Manual,
}

Only guarded, machine-applicable structured edits are eligible for automatic application.

Fixer

Validate without writing:

use diagprint::Fixer;

let check =
    Fixer::new()
        .check(&diagnostic)?;

Apply validated fixes:

let report =
    Fixer::new()
        .backups(true)
        .apply(&diagnostic)?;

Interactive mode:

Fixer::new()
    .backups(true)
    .apply_interactive(
        &diagnostic,
    )?;

Before mutation, diagprint validates:

  • applicability;
  • expected source contents;
  • edit ranges;
  • UTF-8 boundaries;
  • overlapping edits;
  • duplicate insertion positions;
  • filesystem state.

Stale edits are rejected instead of guessed.

FixPlan

FixPlan supports transaction-wide multi-file remediation.

The remediation flow:

  1. validates all affected files;
  2. prepares all resulting contents;
  3. creates recovery state;
  4. performs transaction writes;
  5. rolls back observed failures;
  6. optionally performs post-fix verification;
  7. rolls back when verification fails.

Portable crash-atomic multi-file writes are not claimed.

Post-Fix Verification

Fix plans can declare structured verification requirements after application.

Verification remains declarative.

diagprint does not automatically execute arbitrary shell commands as part of fix application or verification.

Suggested Commands

Suggestions may contain advisory commands:

use diagprint::SuggestedCommand;

let command =
    SuggestedCommand::new(
        "cargo check",
    )
    .explanation(
        "Verify the project after applying the edit",
    );

Suggested commands are never executed automatically.

Documentation Intelligence

DocumentationResolver supports version-aware documentation resolution using Cargo metadata and lockfiles.

Supported documentation targets include:

  • Rust error documentation;
  • Cargo Book pages;
  • docs.rs package documentation;
  • custom documentation links.

Ambiguous package versions fail closed instead of guessing a docs.rs version.

Terminal Documentation

Enable:

features = ["terminal-docs"]

Then:

use diagprint::{
    DocumentationLink,
    TerminalDocViewer,
};

let link =
    DocumentationLink::rust_error(
        "E0277",
    );

TerminalDocViewer::new()
    .width(96)
    .open_and_print(&link)?;

The terminal documentation viewer:

  • accepts HTTP and HTTPS documentation URLs;
  • limits remote document size;
  • sanitizes terminal control characters;
  • converts HTML to terminal-readable text;
  • extracts code examples;
  • syntax-highlights code.

The current documentation viewer uses blocking I/O.

Themes

Terminal presentation is customizable.

use diagprint::{
    Style,
    Theme,
};

let theme = Theme {
    border: Style::rgb(
        20,
        185,
        181,
    ),
    patch_add: Style::rgb(
        100,
        255,
        100,
    ),
    patch_remove: Style::rgb(
        255,
        80,
        100,
    ),
    ..Theme::default()
};

Styles support:

  • standard ANSI colors;
  • ANSI-256 colors;
  • RGB foregrounds and backgrounds;
  • hex colors;
  • bold;
  • dim;
  • italic;
  • underline.

color(false) remains authoritative and disables ANSI styling regardless of theme configuration.

Cybercore Integration

Enable:

features = ["cybercore"]

Use the active Cybercore theme:

use diagprint::Theme;

let theme =
    Theme::cybercore();

Named Cybercore themes are also supported.

The integration consumes Cybercore's semantic palette rather than duplicating theme values inside diagprint.

Rotation

File reports support:

  • size-based rotation;
  • hourly rotation;
  • daily rotation;
  • retention cleanup.
use diagprint::{
    RotationCadence,
    RotationPolicy,
};

let policy = RotationPolicy {
    cadence:
        RotationCadence::Daily,
    ..Default::default()
};

Compression

Enable:

features = ["compression"]

Supported compression formats:

use diagprint::Compression;

// Compression::Gzip
// Compression::Zstd

Safety Model

diagprint deliberately separates diagnostic presentation from mutation.

Rendering never modifies source files.

Automatic remediation is restricted to structured edits that:

  1. are marked MachineApplicable;
  2. still match expected source contents;
  3. use valid UTF-8 boundaries;
  4. do not overlap;
  5. satisfy declared preconditions.

Additional safeguards include:

  • stale-edit rejection;
  • guarded insertion requirements;
  • transaction-wide validation;
  • recovery state before writes;
  • rollback on observed write failures;
  • optional post-fix verification;
  • verification rollback;
  • trusted-root requirements for hydrated compiler edits;
  • fail-closed documentation version resolution.

Suggested shell commands are never automatically executed.

Revision-aware source rendering also fails closed when source identity no longer matches the diagnostic.

Minimum Supported Rust Version

The minimum supported Rust version is:

Rust 1.85

CI performs an MSRV-aware fresh dependency resolution and checks all targets and all features on Rust 1.85.

Development

Default gate:

cargo fmt --all -- --check

cargo check \
    --all-targets

cargo clippy \
    --all-targets \
    -- -D warnings

cargo test \
    --all-targets

cargo test \
    --doc

Full feature gate:

cargo check \
    --all-targets \
    --all-features

cargo clippy \
    --all-targets \
    --all-features \
    -- -D warnings

cargo test \
    --all-targets \
    --all-features

cargo test \
    --doc \
    --all-features

cargo doc \
    --no-deps \
    --all-features

MSRV gate:

CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS=fallback \
cargo +1.85.0 check \
    --all-targets \
    --all-features

Examples

Core examples:

cargo run --example basic
cargo run --example error_chain
cargo run --example intelligence

Revision-aware source examples:

cargo run --example virtual_source
cargo run --example source_snapshot
cargo run --example source_revision
cargo run --example revision_bound_diagnostic
cargo run --example captured_diagnostic

CI output examples:

cargo run --example github_actions
cargo run --example sarif

Interop examples:

cargo run \
    --features ariadne \
    --example ariadne_integration

cargo run \
    --features annotate-snippets \
    --example annotate_snippets_integration

Other optional examples:

cargo run \
    --features cybercore \
    --example cybercore

cargo run \
    --features terminal-docs \
    --example terminal_docs

v0.6

Revision-Aware Diagnostics and CI Output

v0.6 adds:

  • distinct primary and secondary label rendering;
  • virtual source caching;
  • generic source-provider handoff;
  • immutable source snapshots;
  • per-source revision tracking;
  • revision-bound source locations;
  • stale-source detection;
  • fail-closed stale-source terminal rendering;
  • captured diagnostics;
  • Ariadne structured interoperability;
  • annotate-snippets structured interoperability;
  • GitHub Actions annotations;
  • SARIF 2.1.0 rendering;
  • hardened Rust 1.85 validation across all targets and features.

v0.5

Interoperability and Transactional Remediation

v0.5 added:

  • Rust 2024;
  • Rust 1.85 MSRV;
  • anyhow integration;
  • typed-error metadata;
  • tracing integration;
  • rustc/Cargo structured ingestion;
  • version-aware documentation;
  • FixPlan;
  • transaction-wide multi-file remediation;
  • post-fix verification;
  • Cargo intelligence;
  • miette interoperability;
  • codespan-reporting interoperability;
  • generic dependency-free diagnostic interoperability.

Roadmap

Potential future work includes:

  • asynchronous and nonblocking report output;
  • asynchronous terminal documentation retrieval;
  • richer structured diff presentation;
  • additional structured fix sources.

Repository

https://github.com/darkstardevx/diagprint

License

Licensed under either:

  • Apache License, Version 2.0
  • MIT License

at your option.