use std::time::Duration;
use crate::nameserver::predefined::PredefinedProvider;
use crate::nameserver::{NameServerConfig, NameServerConfigGroup};
use crate::resolver::{Mode, ResolverConfigGroup, ResolverGroup, ResolverGroupOpts, ResolverOpts};
use crate::Result;
#[derive(Debug)]
enum NameServerSource {
System,
Predefined(PredefinedProvider),
Custom(NameServerConfig),
}
#[derive(Debug)]
pub struct ResolverGroupBuilder {
sources: Vec<NameServerSource>,
resolver_opts: Option<ResolverOpts>,
group_opts: Option<ResolverGroupOpts>,
timeout: Option<Duration>,
retries: Option<usize>,
max_concurrent_requests: Option<usize>,
max_concurrent_servers: Option<usize>,
limit: Option<usize>,
mode: Option<Mode>,
}
impl ResolverGroupBuilder {
pub fn new() -> Self {
ResolverGroupBuilder {
sources: Vec::new(),
resolver_opts: None,
group_opts: None,
timeout: None,
retries: None,
max_concurrent_requests: None,
max_concurrent_servers: None,
limit: None,
mode: None,
}
}
pub fn system(mut self) -> Self {
if !self.sources.iter().any(|s| matches!(s, NameServerSource::System)) {
self.sources.push(NameServerSource::System);
}
self
}
pub fn predefined(mut self, provider: PredefinedProvider) -> Self {
self.sources.push(NameServerSource::Predefined(provider));
self
}
pub fn all_predefined(mut self) -> Self {
for provider in PredefinedProvider::all() {
self.sources.push(NameServerSource::Predefined(*provider));
}
self
}
pub fn nameserver(mut self, config: NameServerConfig) -> Self {
self.sources.push(NameServerSource::Custom(config));
self
}
pub fn nameservers(mut self, configs: impl IntoIterator<Item = NameServerConfig>) -> Self {
for config in configs {
self.sources.push(NameServerSource::Custom(config));
}
self
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
pub fn retries(mut self, retries: usize) -> Self {
self.retries = Some(retries);
self
}
pub fn max_concurrent_requests(mut self, max: usize) -> Self {
self.max_concurrent_requests = Some(max);
self
}
pub fn max_concurrent_servers(mut self, max: usize) -> Self {
self.max_concurrent_servers = Some(max);
self
}
pub fn limit(mut self, limit: usize) -> Self {
self.limit = Some(limit);
self
}
pub fn mode(mut self, mode: Mode) -> Self {
self.mode = Some(mode);
self
}
pub fn resolver_opts(mut self, opts: ResolverOpts) -> Self {
self.resolver_opts = Some(opts);
self
}
pub fn group_opts(mut self, opts: ResolverGroupOpts) -> Self {
self.group_opts = Some(opts);
self
}
pub async fn build(self) -> Result<ResolverGroup> {
let mut configs = Vec::new();
for source in self.sources {
match source {
NameServerSource::System => {
let system_configs = NameServerConfigGroup::from_system_config()?;
configs.extend(system_configs);
}
NameServerSource::Predefined(provider) => {
configs.extend(provider.configs());
}
NameServerSource::Custom(config) => {
configs.push(config);
}
}
}
let mut resolver_opts = self.resolver_opts.unwrap_or_default();
if let Some(timeout) = self.timeout {
resolver_opts.timeout = timeout;
}
if let Some(retries) = self.retries {
resolver_opts.retries = retries;
}
if let Some(max) = self.max_concurrent_requests {
resolver_opts.max_concurrent_requests = max;
}
let mut group_opts = self.group_opts.unwrap_or_default();
if let Some(max) = self.max_concurrent_servers {
group_opts.max_concurrent = max;
}
if let Some(limit) = self.limit {
group_opts.limit = Some(limit);
}
if let Some(mode) = self.mode {
group_opts.mode = mode;
}
let resolver_configs: ResolverConfigGroup = crate::nameserver::NameServerConfigGroup::new(configs).into();
ResolverGroup::from_configs(resolver_configs, resolver_opts, group_opts).await
}
}
impl Default for ResolverGroupBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::Ipv4Addr;
#[test]
fn builder_default_is_empty() {
let builder = ResolverGroupBuilder::new();
assert!(builder.sources.is_empty());
assert!(builder.resolver_opts.is_none());
assert!(builder.group_opts.is_none());
assert!(builder.timeout.is_none());
}
#[test]
fn builder_accumulates_sources() {
let builder = ResolverGroupBuilder::new()
.predefined(PredefinedProvider::Google)
.nameserver(NameServerConfig::udp((Ipv4Addr::new(1, 1, 1, 1), 53)));
assert_eq!(builder.sources.len(), 2);
}
#[test]
fn builder_system_deduplicates() {
let builder = ResolverGroupBuilder::new().system().system().system();
let system_count = builder
.sources
.iter()
.filter(|s| matches!(s, NameServerSource::System))
.count();
assert_eq!(system_count, 1);
}
#[test]
fn builder_all_predefined_adds_six() {
let builder = ResolverGroupBuilder::new().all_predefined();
let predefined_count = builder
.sources
.iter()
.filter(|s| matches!(s, NameServerSource::Predefined(_)))
.count();
assert_eq!(predefined_count, 6);
}
#[test]
fn builder_option_overrides() {
let builder = ResolverGroupBuilder::new()
.timeout(Duration::from_secs(10))
.retries(3)
.max_concurrent_servers(20)
.limit(5)
.mode(Mode::Uni);
assert_eq!(builder.timeout, Some(Duration::from_secs(10)));
assert_eq!(builder.retries, Some(3));
assert_eq!(builder.max_concurrent_servers, Some(20));
assert_eq!(builder.limit, Some(5));
assert_eq!(builder.mode, Some(Mode::Uni));
}
#[tokio::test]
async fn build_empty_produces_empty_group() {
let group = ResolverGroupBuilder::new().build().await.unwrap();
assert!(group.is_empty());
}
#[tokio::test]
async fn build_with_custom_nameserver() {
let group = ResolverGroupBuilder::new()
.nameserver(NameServerConfig::udp((Ipv4Addr::new(8, 8, 8, 8), 53)))
.build()
.await
.unwrap();
assert_eq!(group.len(), 1);
}
#[tokio::test]
async fn build_with_predefined_provider() {
let expected_count = PredefinedProvider::Google.configs().len();
let group = ResolverGroupBuilder::new()
.predefined(PredefinedProvider::Google)
.build()
.await
.unwrap();
assert_eq!(group.len(), expected_count);
}
#[tokio::test]
async fn build_applies_option_overrides() {
let group = ResolverGroupBuilder::new()
.nameserver(NameServerConfig::udp((Ipv4Addr::new(8, 8, 8, 8), 53)))
.max_concurrent_servers(42)
.limit(7)
.mode(Mode::Uni)
.build()
.await
.unwrap();
assert_eq!(group.opts().max_concurrent, 42);
assert_eq!(group.opts().limit, Some(7));
assert_eq!(group.opts().mode, Mode::Uni);
}
#[tokio::test]
async fn build_option_overrides_on_base_opts() {
let base = ResolverOpts {
timeout: Duration::from_secs(1),
retries: 1,
..Default::default()
};
let group = ResolverGroupBuilder::new()
.nameserver(NameServerConfig::udp((Ipv4Addr::new(8, 8, 8, 8), 53)))
.resolver_opts(base)
.timeout(Duration::from_secs(99))
.build()
.await
.unwrap();
let resolver = &group.resolvers()[0];
assert_eq!(resolver.opts.timeout, Duration::from_secs(99));
assert_eq!(resolver.opts.retries, 1);
}
}