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