Skip to main content

hornet_bind9/
lib.rs

1//! # hornet-bind9
2//!
3//! Parse, write, and validate BIND9 `named.conf` configuration files and DNS
4//! zone files.
5//!
6//! ## Quick start
7//!
8//! ```rust
9//! use hornet_bind9::parse_named_conf;
10//!
11//! let input = r#"
12//! options {
13//!     directory "/var/cache/bind";
14//!     recursion yes;
15//!     allow-query { any; };
16//! };
17//!
18//! zone "example.com" {
19//!     type primary;
20//!     file "/etc/bind/zones/example.com.db";
21//! };
22//! "#;
23//!
24//! let conf = parse_named_conf(input).expect("parse failed");
25//! assert_eq!(conf.statements.len(), 2);
26//! ```
27//!
28//! ## Feature flags
29//!
30//! | Flag    | Default | Description |
31//! |---------|---------|-------------|
32//! | `serde` | off     | Derive `serde::Serialize`/`Deserialize` on all AST types |
33
34pub mod ast;
35pub mod error;
36pub mod parser;
37pub mod validator;
38pub mod writer;
39
40// ── Re-exports for ergonomic use ──────────────────────────────────────────────
41
42pub use ast::{named_conf, zone_file};
43pub use error::{Error, Result, Severity, ValidationError};
44
45/// Parse a `named.conf` string into an AST.
46///
47/// # Errors
48/// Returns [`Error::Parse`] if the input is not valid BIND9 configuration.
49#[allow(clippy::result_large_err)]
50pub fn parse_named_conf(input: &str) -> Result<ast::named_conf::NamedConf> {
51    parser::parse_named_conf(input).map_err(|msg| Error::Parse {
52        file: "<input>".into(),
53        message: msg.clone(),
54        src: miette::NamedSource::new("<input>", input.to_owned()),
55        span: (0, 0).into(),
56    })
57}
58
59/// Parse a `named.conf` file from disk.
60///
61/// # Errors
62/// Returns [`Error::Io`] on read failure or [`Error::Parse`] on bad syntax.
63#[allow(clippy::result_large_err)]
64pub fn parse_named_conf_file(path: &std::path::Path) -> Result<ast::named_conf::NamedConf> {
65    let input = std::fs::read_to_string(path)?;
66    parser::parse_named_conf(&input).map_err(|msg| Error::Parse {
67        file: path.display().to_string(),
68        message: msg.clone(),
69        src: miette::NamedSource::new(path.display().to_string(), input),
70        span: (0, 0).into(),
71    })
72}
73
74/// Parse a DNS zone file string into an AST.
75///
76/// # Errors
77/// Returns [`Error::Parse`] if the input is not a valid zone file.
78#[allow(clippy::result_large_err)]
79pub fn parse_zone_file(input: &str) -> Result<ast::zone_file::ZoneFile> {
80    parser::parse_zone_file(input).map_err(|msg| Error::Parse {
81        file: "<input>".into(),
82        message: msg.clone(),
83        src: miette::NamedSource::new("<input>", input.to_owned()),
84        span: (0, 0).into(),
85    })
86}
87
88/// Parse a zone file from disk.
89///
90/// # Errors
91/// Returns [`Error::Io`] on read failure or [`Error::Parse`] on bad syntax.
92#[allow(clippy::result_large_err)]
93pub fn parse_zone_file_from_path(path: &std::path::Path) -> Result<ast::zone_file::ZoneFile> {
94    let input = std::fs::read_to_string(path)?;
95    parser::parse_zone_file(&input).map_err(|msg| Error::Parse {
96        file: path.display().to_string(),
97        message: msg.clone(),
98        src: miette::NamedSource::new(path.display().to_string(), input),
99        span: (0, 0).into(),
100    })
101}
102
103/// Serialise a [`NamedConf`] AST back to a `String`.
104#[must_use]
105pub fn write_named_conf(conf: &ast::named_conf::NamedConf, opts: &writer::WriteOptions) -> String {
106    writer::write_named_conf(conf, opts)
107}
108
109/// Serialise a [`ZoneFile`] AST back to a `String`.
110#[must_use]
111pub fn write_zone_file(zone: &ast::zone_file::ZoneFile, opts: &writer::WriteOptions) -> String {
112    writer::write_zone_file(zone, opts)
113}
114
115/// Validate a parsed `named.conf` AST and return any diagnostics.
116#[must_use]
117pub fn validate_named_conf(conf: &ast::named_conf::NamedConf) -> Vec<ValidationError> {
118    validator::validate_named_conf(conf)
119}
120
121/// Validate a parsed zone file AST and return any diagnostics.
122#[must_use]
123pub fn validate_zone_file(zone: &ast::zone_file::ZoneFile) -> Vec<ValidationError> {
124    validator::validate_zone_file(zone)
125}