#![warn(clippy::all)]
pub use lazydns_macros::{RegisterExecPlugin, RegisterPlugin, ShutdownPlugin};
pub mod dns;
pub mod server;
pub mod plugin;
pub mod plugins;
pub mod utils;
pub mod config;
#[cfg(feature = "log")]
pub use lazylog::{FileLogConfig, LogConfig, RotationPeriod, RotationTrigger, init_logging};
#[cfg(feature = "metrics")]
pub mod metrics;
#[cfg(feature = "web")]
pub mod web;
pub mod error {
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("DNS protocol error: {0}")]
DnsProtocol(String),
#[error("Upstream timeout: {upstream} ({timeout_ms}ms)")]
UpstreamTimeout {
upstream: String,
timeout_ms: u64,
},
#[error("Configuration error: {0}")]
Config(String),
#[error("Missing required config field: {field} in {context}")]
MissingConfigField {
field: String,
context: String,
},
#[error("Invalid config value for {field}: {value} - {reason}")]
InvalidConfigValue {
field: String,
value: String,
reason: String,
},
#[error("Plugin error: {0}")]
Plugin(String),
#[error("Connection error to {address}: {reason}")]
Connection {
address: String,
reason: String,
},
#[error("Invalid address: {input}")]
InvalidAddress {
input: String,
},
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Failed to parse file '{path}': {reason}")]
FileParse {
path: String,
reason: String,
},
#[error("Error: {0}")]
Other(String),
}
impl Error {
pub fn upstream_timeout(upstream: impl Into<String>, timeout_ms: u64) -> Self {
Self::UpstreamTimeout {
upstream: upstream.into(),
timeout_ms,
}
}
pub fn connection(address: impl Into<String>, reason: impl Into<String>) -> Self {
Self::Connection {
address: address.into(),
reason: reason.into(),
}
}
pub fn invalid_address(input: impl Into<String>) -> Self {
Self::InvalidAddress {
input: input.into(),
}
}
pub fn missing_config_field(field: impl Into<String>, context: impl Into<String>) -> Self {
Self::MissingConfigField {
field: field.into(),
context: context.into(),
}
}
pub fn invalid_config_value(
field: impl Into<String>,
value: impl Into<String>,
reason: impl Into<String>,
) -> Self {
Self::InvalidConfigValue {
field: field.into(),
value: value.into(),
reason: reason.into(),
}
}
pub fn file_parse(path: impl Into<String>, reason: impl Into<String>) -> Self {
Self::FileParse {
path: path.into(),
reason: reason.into(),
}
}
}
pub type Result<T> = std::result::Result<T, Error>;
}
pub use error::{Error, Result};
#[cfg(feature = "web")]
pub use web::WebServer;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_types() {
let _dns_err = Error::DnsProtocol("test error".to_string());
let _config_err = Error::Config("test error".to_string());
let _plugin_err = Error::Plugin("test error".to_string());
}
#[test]
fn test_structured_error_types() {
let err = Error::upstream_timeout("8.8.8.8:53", 5000);
assert!(matches!(err, Error::UpstreamTimeout { .. }));
assert!(err.to_string().contains("8.8.8.8:53"));
assert!(err.to_string().contains("5000ms"));
let err = Error::connection("1.1.1.1:53", "connection refused");
assert!(matches!(err, Error::Connection { .. }));
assert!(err.to_string().contains("1.1.1.1:53"));
let err = Error::invalid_address("not-an-ip");
assert!(matches!(err, Error::InvalidAddress { .. }));
assert!(err.to_string().contains("not-an-ip"));
let err = Error::invalid_config_value("port", "abc", "must be a number");
assert!(matches!(err, Error::InvalidConfigValue { .. }));
assert!(err.to_string().contains("port"));
assert!(err.to_string().contains("abc"));
let err = Error::missing_config_field("upstream", "forward");
assert!(matches!(err, Error::MissingConfigField { .. }));
assert!(err.to_string().contains("upstream"));
let err = Error::file_parse("/etc/hosts", "invalid line");
assert!(matches!(err, Error::FileParse { .. }));
assert!(err.to_string().contains("/etc/hosts"));
}
#[test]
fn test_error_from_io() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let err: Error = io_err.into();
assert!(matches!(err, Error::Io(_)));
}
}