Dependency-graph reports

Graph reports show the architecture rather than declaring it valid. Every renderer consumes the same immutable GraphReportSnapshot, so filtering, collapsing, counts, and Rust import evidence do not drift between formats.

Shape the query

Start with project_graph() or project_graph_in(path). dependency_graph() and dependency_graph_in(path) are aliases.

use archunit::{ArchUnitError, CheckOptions, project_graph};

fn snapshot() -> Result<(), ArchUnitError> {
    let graph = project_graph()
        .include_external_dependencies()
        .focus_on("src/api/**", 1)
        .reachable_from("src/application/**")
        .collapse_to_folder_depth(2)
        .titled("Application dependencies")
        .with_check_options(CheckOptions::new().with_clear_cache(true))
        .snapshot()?;

    println!("{} nodes", graph.summary.node_count);
    Ok(())
}

External dependencies and marker/collapsed self-edges are excluded by default. Enable them with include_external_dependencies and include_self_dependencies.

Select nodes

Three graph queries can be combined:

When several are present, their selected nodes form a union and the snapshot contains the induced subgraph. A query selector that matches no node is a typed GraphQueryError, not an empty report.

Collapse nodes

collapse_to_folder_depth(depth) groups file paths by a positive leading folder depth. collapse_by_pattern(expression) uses the first regex capture as the label. The explicit collapse_by_pattern_with_replacement(expression, replacement) form accepts Rust regex replacement syntax such as $1 and ${component}.

Collapsed edges aggregate their raw edge count, external flag, and union of ImportKind evidence. Self-edges created by collapse remain omitted unless explicitly enabled.

Read the snapshot

snapshot() returns:

summary() is the shorter terminal when only those counts are needed. raw_edge_count counts selected merged file-to-file edges before collapse; edge_count counts final aggregated edges.

Render or export

There are six formats and two ergonomic terminal styles for each:

Format In-memory UTF-8 file
Graphviz DOT to_dot() export_as_dot(path)
Mermaid to_mermaid() export_as_mermaid(path)
D2 to_d2() export_as_d2(path)
CSV to_csv() export_as_csv(path)
JSON to_json() export_as_json(path)
HTML to_html() export_as_html(path)
use archunit::{ArchUnitError, GraphRenderer, project_graph};

fn render_reports() -> Result<(), ArchUnitError> {
    let report = project_graph()
        .collapse_to_folder_depth(2)
        .titled("Application dependencies");

    let mermaid = report.to_mermaid()?;
    report.export_as_html("target/architecture/dependencies.html")?;

    let snapshot = report.snapshot()?;
    let dot = GraphRenderer::to_dot(&snapshot);
    let json = GraphRenderer::to_json(&snapshot);
    assert!(!mermaid.is_empty() && !dot.is_empty() && !json.is_empty());
    Ok(())
}

render(GraphReportFormat) and export(GraphReportFormat, path) provide typed dispatch. DOT, Mermaid, and D2 preserve edge counts and distinguish external nodes. CSV contains one row per aggregated edge. JSON contains the full snapshot. HTML is self-contained, offline-safe, and embeds portable source views without scripts or remote assets.

Continue with running a rule for extraction options, caching, logging, and structured failure handling.