hornet-bind9 0.1.0

Parse, write, and validate BIND9 named.conf configuration files and DNS zone files
Documentation
# Architecture

## Module layout

Hornet is a single crate that provides both the library and the optional CLI binary.
The CLI is gated behind the `cli` feature flag (enabled by default).

```
src/
├── ast/                # Typed AST definitions
│   ├── named_conf.rs
│   └── zone_file.rs
├── parser/             # Winnow parser combinators
│   ├── common.rs
│   ├── named_conf.rs
│   └── zone_file.rs
├── writer/             # AST → text serialisers
│   ├── named_conf.rs
│   └── zone_file.rs
├── validator/          # Semantic validation
│   └── mod.rs
├── error.rs            # Error and diagnostic types
├── lib.rs              # Public API surface
└── main.rs             # CLI binary (requires `cli` feature)
```

---

## The `ast` module

The AST types are the shared language between the parser, validator, and writer.
They are pure data — no methods that perform IO or side effects.

### `named_conf` AST

The top-level type is `NamedConf`, which contains a `Vec<Statement>`.
`Statement` is an enum with one variant per top-level BIND9 statement:

```
NamedConf
└── Vec<Statement>
    ├── Statement::Options(OptionsBlock)
    ├── Statement::Zone(ZoneStmt)
    ├── Statement::View(ViewStmt)
    ├── Statement::Acl(AclStmt)
    ├── Statement::Logging(LoggingBlock)
    ├── Statement::Controls(ControlsBlock)
    ├── Statement::Key(KeyStmt)
    ├── Statement::Primaries(PrimariesStmt)
    ├── Statement::Server(ServerStmt)
    ├── Statement::Include(String)
    └── Statement::Unknown { keyword, body }
```

### `zone_file` AST

The top-level type is `ZoneFile`, which contains a `Vec<Entry>`.
`Entry` is an enum covering directives and resource records:

```
ZoneFile
└── Vec<Entry>
    ├── Entry::Origin(String)       — $ORIGIN directive
    ├── Entry::Ttl(u32)             — $TTL directive
    ├── Entry::Include(String)      — $INCLUDE directive
    ├── Entry::Generate { ... }     — $GENERATE directive
    └── Entry::Record(ResourceRecord)
        └── rdata: RData
            ├── RData::A(Ipv4Addr)
            ├── RData::Aaaa(Ipv6Addr)
            ├── RData::Ns(DomainName)
            ├── RData::Mx { priority, exchange }
            ├── RData::Soa { ... }
            ├── RData::Cname(DomainName)
            └── ... (24+ variants)
```

---

## The `parser` module

Parsers are built with [winnow](https://docs.rs/winnow), a fast, zero-copy parser combinator
library. The `common.rs` module provides shared primitives (whitespace, comments, quoted strings,
domain names, IP addresses) reused by both the `named_conf` and `zone_file` parsers.

Parsers are internal (`pub(crate)`) and exposed only through the top-level convenience functions
in `lib.rs`.

---

## The `writer` module

Writers traverse the AST and produce a `String` of valid BIND9 text.
All formatting decisions (indent size, keyword style, blank lines between statements)
are controlled by [`WriteOptions`](../reference/write-options.md).

Writers are deterministic: the same AST with the same `WriteOptions` always produces
identical output.

---

## The `validator` module

Validation is a two-pass process:

1. **Collection pass** — walk all statements and collect declared ACL names, key names,
   and zone names.
2. **Semantic pass** — walk all statements again, cross-referencing declarations against usages.

Validators return `Vec<ValidationError>` — they never panic or mutate the AST.
Diagnostics carry a `Severity` (`Info`, `Warning`, `Error`) to allow callers to
decide their own tolerance threshold.

---

## Error handling

Hornet uses [`thiserror`](https://crates.io/crates/thiserror) for its `Error` enum
and [`miette`](https://crates.io/crates/miette) for rich diagnostic rendering.

Parse errors include the source text and a byte-range span, enabling pretty-printed
output with the offending line highlighted — identical to what `rustc` produces.

See [Error Types](../reference/error-types.md) for the full type inventory.

---

## Design principles

- **No IO in the AST or parser**`parse_named_conf_file()` reads the file and delegates
  to `parse_named_conf()`. The parser itself never touches the filesystem.
- **No mutation of the AST** — validation and writing both take `&AST` (shared reference).
- **Zero unsafe code** — the entire codebase is `#![forbid(unsafe_code)]`.
- **Feature-gated serde** — AST types are lean by default; serialisation is opt-in.