Skip to main content

ip_discovery/
config.rs

1//! Configuration for IP detection.
2//!
3//! Use [`Config::builder()`] to create a customized configuration,
4//! or [`Config::default()`] for sensible defaults (all protocols, first-success strategy).
5
6use crate::provider::BoxedProvider;
7use crate::types::{BuiltinProvider, IpVersion, Protocol};
8use std::time::Duration;
9
10/// Strategy for resolving the public IP across multiple providers.
11#[derive(Debug, Clone, Copy, Default)]
12#[non_exhaustive]
13pub enum Strategy {
14    /// Try providers sequentially, return the first success.
15    #[default]
16    First,
17    /// Race all providers concurrently, return the fastest success.
18    Race,
19    /// Query all providers, require multiple to agree on the same IP.
20    ///
21    /// Values of `min_agree` below 2 are clamped to 2 at build time,
22    /// since consensus with fewer than 2 providers is meaningless.
23    Consensus {
24        /// Minimum number of providers that must return the same IP (≥ 2).
25        min_agree: usize,
26    },
27}
28
29/// Configuration for IP detection
30pub struct Config {
31    /// List of providers to use
32    pub(crate) providers: Vec<BoxedProvider>,
33    /// Timeout for each provider
34    pub(crate) timeout: Duration,
35    /// IP version preference
36    pub(crate) version: IpVersion,
37    /// Resolution strategy
38    pub(crate) strategy: Strategy,
39}
40
41impl Default for Config {
42    fn default() -> Self {
43        Self::builder().build()
44    }
45}
46
47impl Config {
48    /// Create a new configuration builder
49    pub fn builder() -> ConfigBuilder {
50        ConfigBuilder::new()
51    }
52}
53
54/// Builder for [`Config`].
55///
56/// Created via [`Config::builder()`]. Call methods to customize, then
57/// [`.build()`](ConfigBuilder::build) to produce the final [`Config`].
58pub struct ConfigBuilder {
59    custom_providers: Vec<BoxedProvider>,
60    timeout: Duration,
61    version: IpVersion,
62    strategy: Strategy,
63    provider_filter: Option<ProviderFilter>,
64}
65
66/// Filter to select which providers to include
67enum ProviderFilter {
68    /// Only providers of specified protocols
69    Protocols(Vec<Protocol>),
70    /// Specific built-in providers
71    Select(Vec<BuiltinProvider>),
72}
73
74impl ConfigBuilder {
75    /// Create a new builder with default settings
76    pub fn new() -> Self {
77        Self {
78            custom_providers: Vec::new(),
79            timeout: Duration::from_secs(10),
80            version: IpVersion::Any,
81            strategy: Strategy::First,
82            provider_filter: None,
83        }
84    }
85
86    /// Filter providers by protocol (e.g., DNS, HTTP, STUN)
87    ///
88    /// # Example
89    /// ```rust,no_run
90    /// use ip_discovery::{Config, Protocol};
91    ///
92    /// let config = Config::builder()
93    ///     .protocols(&[Protocol::Dns, Protocol::Stun])
94    ///     .build();
95    /// ```
96    pub fn protocols(mut self, protocols: &[Protocol]) -> Self {
97        self.provider_filter = Some(ProviderFilter::Protocols(protocols.to_vec()));
98        self
99    }
100
101    /// Select specific built-in providers
102    ///
103    /// # Example
104    /// ```rust,no_run
105    /// use ip_discovery::{Config, BuiltinProvider};
106    ///
107    /// let config = Config::builder()
108    ///     .providers(&[
109    ///         BuiltinProvider::CloudflareDns,
110    ///         BuiltinProvider::GoogleStun,
111    ///     ])
112    ///     .build();
113    /// ```
114    pub fn providers(mut self, providers: &[BuiltinProvider]) -> Self {
115        self.provider_filter = Some(ProviderFilter::Select(providers.to_vec()));
116        self
117    }
118
119    /// Add a custom provider (advanced usage)
120    ///
121    /// Custom providers are added alongside any filter-selected providers.
122    pub fn add_provider(mut self, provider: BoxedProvider) -> Self {
123        self.custom_providers.push(provider);
124        self
125    }
126
127    /// Set timeout for each provider
128    pub fn timeout(mut self, timeout: Duration) -> Self {
129        self.timeout = timeout;
130        self
131    }
132
133    /// Set IP version preference
134    pub fn version(mut self, version: IpVersion) -> Self {
135        self.version = version;
136        self
137    }
138
139    /// Set resolution strategy.
140    ///
141    /// For [`Strategy::Consensus`], `min_agree` is clamped to at least 2.
142    pub fn strategy(mut self, strategy: Strategy) -> Self {
143        self.strategy = match strategy {
144            Strategy::Consensus { min_agree } => Strategy::Consensus {
145                min_agree: min_agree.max(2),
146            },
147            other => other,
148        };
149        self
150    }
151
152    /// Build the configuration
153    pub fn build(mut self) -> Config {
154        let mut providers: Vec<BoxedProvider> = match self.provider_filter.take() {
155            Some(ProviderFilter::Protocols(protocols)) => BuiltinProvider::ALL
156                .iter()
157                .filter(|p| protocols.contains(&p.protocol()))
158                .map(|p| p.to_boxed())
159                .collect(),
160            Some(ProviderFilter::Select(selected)) => {
161                selected.into_iter().map(|p| p.to_boxed()).collect()
162            }
163            None if self.custom_providers.is_empty() => {
164                BuiltinProvider::ALL.iter().map(|p| p.to_boxed()).collect()
165            }
166            None => Vec::new(),
167        };
168
169        // Append any custom providers added via add_provider()
170        providers.append(&mut self.custom_providers);
171
172        Config {
173            providers,
174            timeout: self.timeout,
175            version: self.version,
176            strategy: self.strategy,
177        }
178    }
179}
180
181impl Default for ConfigBuilder {
182    fn default() -> Self {
183        Self::new()
184    }
185}