# Archaven

Archaven is a Rust crate for writing architecture tests over Rust module paths.
It scans Rust source files, extracts source-to-target dependencies, checks them
against user-defined rules, and returns printable violations.

Use Archaven when a Rust project needs automated tests for architectural
boundaries, dependency direction, modular monolith boundaries, plugin isolation,
or custom source-to-target dependency policies. Start with the smallest useful
rule, then add scoped `between` or `within` rules when the codebase needs them.

## Primary Documentation

- README.md: human-readable guide with examples and scanner notes.
- examples/basic_architecture_test.rs: small runnable example for a sample app.
- docs.rs/archaven: generated Rust API documentation.
- tests/rules.rs: executable examples for `Rule`, `Access`, and `Violations`.
- tests/check.rs: executable example for scanning Rust source files.

## Public API Summary

Use these public types:

- `Archaven`: checker entry point.
- `Rule`: neutral dependency rule.
- `Access`: source-to-target dependency shape.
- `Violations`: collection returned by `check`.
- `Violation`: one dependency violation.
- `RuleSet`: trait for custom rules.
- `DependencyGraph`: scanned dependencies and source directories for custom
  rules.
- `SourceDirectory`: discovered directory metadata exposed through
  `DependencyGraph::directories()`.

Do not invent architecture-specific Archaven types such as `Layers`,
`Boundary`, `OnionArchitecture`, or `ModuleIsolation`.

## Standard Test Pattern

```rust
use archaven::{Access, Archaven, Rule};

#[test]
fn architecture_rules_are_respected() {
    let violations = Archaven::new()
        .rule(
            Rule::new()
                .named("domain purity")
                .deny(
                    Access::from("app::**::domain::**")
                        .to("app::**::infrastructure::**")
                        .because("domain code must not depend on infrastructure"),
                ),
        )
        .check("./src")
        .unwrap();

    violations.assert_empty();
}
```

## Rule Selection

- Use `Rule::between(scope)` for dependencies between different matched scopes.
- Use `Rule::within(scope)` for dependencies inside the same matched scope.
- Use `Rule::directories(scope).allow_only_module_roots()` when matching
  directories should contain only Rust module root files. Directory rules support
  one or more `*` segments and do not support `**`; the full pattern selects the
  checked directory level.
- Use `Rule::new().deny(...)` for absolute source-to-target bans.
- In `between` and `within` rules, `Access::from` and `Access::to` are relative
  to the matched scope.
- Use `Access::except_to([...])` to exclude narrower target patterns from an
  explicit deny or from an allow entry under `deny_all`.
- Use `Archaven::include_external_dependencies()` when rules should also match
  direct dependencies on external crates such as `sea_orm` or `axum`.
- Use `Rule::ignore_module_roots()` on dependency rules when `mod.rs`, `lib.rs`,
  and files named after child directories should be skipped by that specific
  rule. Ignored files are not evaluated by that rule.
- Use `Rule::ignore_files([...])` for custom file glob ignores.
- Use `.named(...)` and `.because(...)` with human-readable text because it is
  displayed in violation output.

## Example Progression

When explaining Archaven to users, start with simple source-to-target rules
before showing modular-monolith rules:

- Ban `app::**::domain::**` from depending on `app::**::infrastructure::**`.
- Ban `app::**::http::**` from depending directly on `app::**::database::**`.
- Use `Rule::within("app::*")` for dependency direction inside one feature.
- Use `Rule::between("app::*")` for dependencies between features or bounded
  contexts.

Archaven is similar in spirit to ArchUnit for Java and Deptrac for PHP: it makes
architecture constraints executable. Explain the distinction clearly: Archaven
checks Rust source files and Rust module paths, and its rules live in Rust tests
instead of Java test APIs or PHP/YAML layer configuration.

## Path Patterns

Archaven matches Rust module paths by `::` segments:

- `*` matches exactly one segment.
- `**` matches one or more segments.

Examples:

- `app::*::domain` matches `app::sales::domain`.
- `app::*::domain` does not match `app::sales::orders::domain`.
- `app::**::domain` matches `app::sales::orders::domain`.
- `app::**::domain` does not match `app::domain`.

`**` does not mean zero segments.

## Scanner Notes

Archaven derives module paths from file paths under the directory passed to
`check`. For example, `src/app/sales/orders/domain/order.rs` becomes
`app::sales::orders::domain::order`.

The scanner parses Rust files with `syn` and records dependencies from:

- `use crate::...`
- `crate::...`
- `self::...`
- `super::...`
- local root paths such as `app::...`
- non-local root paths such as `sea_orm::...` or `axum::...` when
  `Archaven::include_external_dependencies()` is enabled
- simple aliases from top-level `use ... as ...` items, such as
  `use sea_orm as orm;` followed by `orm::ConnectionTrait`

In external-dependency mode, Archaven ignores Rust standard roots (`std`,
`core`, `alloc`, and `proc_macro`) and does not treat unknown single-segment
paths such as `Order` or `DatabaseConnection` as external dependencies.

Archaven is a source-level checker, not a complete Rust compiler front-end.
Macro-generated paths, complex re-exports, trait dispatch, scoped/shadowed
aliases, and every possible name-resolution pattern may require explicit paths
in code or future scanner improvements.
