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}