Skip to main content

pkarr_client/client/
builder.rs

1use std::sync::Arc;
2
3#[cfg(relays)]
4use std::time::Duration;
5
6#[cfg(feature = "relays")]
7use url::Url;
8
9#[cfg(dht)]
10use crate::mainline;
11use crate::{errors::BuildError, Client};
12use crate::{Cache, DEFAULT_CACHE_SIZE, DEFAULT_MAXIMUM_TTL, DEFAULT_MINIMUM_TTL};
13
14#[cfg(feature = "endpoints")]
15pub const DEFAULT_MAX_RECURSION_DEPTH: u8 = 7;
16
17/// Default request timeout for relays requests.
18#[cfg(relays)]
19pub const DEFAULT_REQUEST_TIMEOUT: Duration = Duration::from_secs(2);
20
21/// [Client]'s Config
22#[derive(Clone)]
23pub(crate) struct Config {
24    /// Configures the [crate::InMemoryCache] size, if no [Self::cache] is set.
25    ///
26    /// Defaults to [DEFAULT_CACHE_SIZE]
27    pub cache_size: usize,
28    /// Used in the `min` parameter in [crate::SignedPacket::expires_in].
29    ///
30    /// Defaults to [DEFAULT_MINIMUM_TTL]
31    pub minimum_ttl: u32,
32    /// Used in the `max` parameter in [crate::SignedPacket::expires_in].
33    ///
34    /// Defaults to [DEFAULT_MAXIMUM_TTL]
35    pub maximum_ttl: u32,
36    /// Custom [Cache] implementation, defaults to [crate::InMemoryCache]
37    pub cache: Option<Arc<dyn Cache>>,
38
39    #[cfg(dht)]
40    pub dht: Option<mainline::DhtBuilder>,
41
42    /// Pkarr [Relays](https://pkarr.org/relays) Urls
43    #[cfg(feature = "relays")]
44    pub relays: Option<Vec<Url>>,
45
46    /// Timeout for Relays requests (The Dht uses an adaptive timeout based on observed RTTs).
47    ///
48    /// The longer this timeout the longer resolve queries will take before consider failed.
49    ///
50    /// Defaults to [DEFAULT_REQUEST_TIMEOUT]
51    #[cfg(feature = "relays")]
52    pub request_timeout: Duration,
53
54    #[cfg(feature = "endpoints")]
55    pub max_recursion_depth: u8,
56}
57
58impl Default for Config {
59    fn default() -> Self {
60        Self {
61            cache_size: DEFAULT_CACHE_SIZE,
62            minimum_ttl: DEFAULT_MINIMUM_TTL,
63            maximum_ttl: DEFAULT_MAXIMUM_TTL,
64            cache: None,
65
66            #[cfg(dht)]
67            dht: Some(mainline::Dht::builder()),
68
69            #[cfg(feature = "relays")]
70            relays: Some(
71                crate::DEFAULT_RELAYS
72                    .iter()
73                    .map(|s| {
74                        Url::parse(s).expect("DEFAULT_RELAYS should be parsed to Url successfully.")
75                    })
76                    .collect(),
77            ),
78
79            #[cfg(relays)]
80            request_timeout: DEFAULT_REQUEST_TIMEOUT,
81
82            #[cfg(feature = "endpoints")]
83            max_recursion_depth: DEFAULT_MAX_RECURSION_DEPTH,
84        }
85    }
86}
87
88impl std::fmt::Debug for Config {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        let mut debug_struct = f.debug_struct("Config");
91
92        debug_struct.field("cache_size", &self.cache_size);
93        debug_struct.field("minimum_ttl", &self.minimum_ttl);
94        debug_struct.field("maximum_ttl", &self.maximum_ttl);
95        debug_struct.field("cache", &self.cache);
96
97        #[cfg(dht)]
98        debug_struct.field("dht", &self.dht);
99        #[cfg(dht)]
100        #[cfg(feature = "relays")]
101        debug_struct.field(
102            "relays",
103            &self
104                .relays
105                .as_ref()
106                .map(|urls| urls.iter().map(|url| url.as_str()).collect::<Vec<_>>()),
107        );
108
109        #[cfg(feature = "relays")]
110        debug_struct.field("request_timeout", &self.request_timeout);
111
112        debug_struct.finish()
113    }
114}
115
116/// A builder for constructing a [`Client`] with custom configuration.
117#[derive(Debug, Default, Clone)]
118pub struct ClientBuilder(Config);
119
120impl ClientBuilder {
121    /// Similar to crates `no-default-features`, this method will remove the default [Self::bootstrap], and [Self::relays]
122    /// effectively disabling the use of both [mainline] and [Relays](https://pkarr.org/relays).
123    ///
124    /// Or you can use [Self::relays] to use custom [Relays](https://pkarr.org/relays).
125    ///
126    /// Similarly you can use [Self::bootstrap] or [Self::dht] to use [mainline] with custom configurations.
127    pub fn no_default_network(&mut self) -> &mut Self {
128        self.no_dht();
129        self.no_relays();
130
131        self
132    }
133
134    /// Disable relays, and use the Dht only.
135    pub fn no_dht(&mut self) -> &mut Self {
136        #[cfg(dht)]
137        {
138            self.0.dht = None;
139        }
140
141        self
142    }
143
144    #[cfg(dht)]
145    /// Create a [mainline::DhtBuilder] if `None`, and allows mutating it with a callback function.
146    pub fn dht<F>(&mut self, f: F) -> &mut Self
147    where
148        F: FnOnce(&mut mainline::DhtBuilder) -> &mut mainline::DhtBuilder,
149    {
150        if self.0.dht.is_none() {
151            self.0.dht = Some(Default::default());
152        }
153
154        if let Some(ref mut builder) = self.0.dht {
155            f(builder);
156        };
157
158        self
159    }
160
161    /// Convenient method to set the `bootstrap` nodes in [Self::dht].
162    ///
163    /// You can start a separate Dht network by setting this to an empty array.
164    ///
165    /// If you want to extend [bootstrap][mainline::DhtBuilder::bootstrap] nodes with more nodes, you can
166    /// use [Self::extra_bootstrap].
167    #[cfg(dht)]
168    pub fn bootstrap<T: ToString>(&mut self, bootstrap: &[T]) -> &mut Self {
169        self.dht(|b| b.bootstrap(bootstrap));
170
171        self
172    }
173
174    #[cfg(dht)]
175    /// Extend the DHT bootstrapping nodes.
176    ///
177    /// If you want to set (override) the DHT bootstrapping nodes,
178    /// use [Self::bootstrap] directly.
179    pub fn extra_bootstrap<T: ToString>(&mut self, bootstrap: &[T]) -> &mut Self {
180        self.dht(|b| b.extra_bootstrap(bootstrap));
181
182        self
183    }
184
185    /// Disable relays, and use the Dht only.
186    pub fn no_relays(&mut self) -> &mut Self {
187        #[cfg(feature = "relays")]
188        {
189            self.0.relays = None;
190        }
191
192        self
193    }
194
195    /// Set custom set of [Relays](https://pkarr.org/relays).
196    ///
197    /// If you want to disable relays use [Self::no_relays] instead.
198    #[cfg(feature = "relays")]
199    pub fn relays<T: reqwest::IntoUrl + Clone>(
200        &mut self,
201        relays: &[T],
202    ) -> Result<&mut Self, InvalidRelayUrl> {
203        self.0.relays = Some(into_urls(relays)?);
204
205        Ok(self)
206    }
207
208    #[cfg(feature = "relays")]
209    /// Extend the current [Self::relays] with extra relays.
210    ///
211    /// If you want to set (override) relays instead, use [Self::relays]
212    pub fn extra_relays<T: reqwest::IntoUrl + Clone>(
213        &mut self,
214        relays: &[T],
215    ) -> Result<&mut Self, InvalidRelayUrl> {
216        if let Some(ref mut existing) = self.0.relays {
217            for relay in into_urls(relays)? {
218                if !existing.contains(&relay) {
219                    existing.push(relay)
220                }
221            }
222        }
223
224        Ok(self)
225    }
226
227    /// Set the size of the capacity of the [Self::cache] implementation.
228    ///
229    /// If set to `0` cache will be disabled.
230    pub fn cache_size(&mut self, cache_size: usize) -> &mut Self {
231        self.0.cache_size = cache_size;
232
233        self
234    }
235
236    /// Set the minimum TTL value.
237    ///
238    /// Limits how soon a [crate::SignedPacket] is considered expired.
239    pub fn minimum_ttl(&mut self, ttl: u32) -> &mut Self {
240        self.0.minimum_ttl = ttl;
241        self.0.maximum_ttl = self.0.maximum_ttl.max(ttl);
242
243        self
244    }
245
246    /// Set the maximum TTL value.
247    ///
248    /// Limits how long it takes before a [crate::SignedPacket] is considered expired.
249    pub fn maximum_ttl(&mut self, ttl: u32) -> &mut Self {
250        self.0.maximum_ttl = ttl;
251        self.0.minimum_ttl = self.0.minimum_ttl.min(ttl);
252
253        self
254    }
255
256    /// Set a custom implementation of [Cache].
257    pub fn cache(&mut self, cache: Arc<dyn Cache>) -> &mut Self {
258        self.0.cache = Some(cache);
259
260        self
261    }
262
263    /// Set the maximum request timeout for relays client.
264    ///
265    /// Useful for testing NOT FOUND responses, where you want to reach the timeout
266    /// sooner than the default of [DEFAULT_REQUEST_TIMEOUT].
267    #[cfg(feature = "relays")]
268    pub fn request_timeout(&mut self, timeout: Duration) -> &mut Self {
269        self.0.request_timeout = timeout;
270
271        self
272    }
273
274    #[cfg(feature = "endpoints")]
275    /// Sets the maximum depth of recursion in [Endpoints](https://pkarr.org/endpoints) resolution.
276    ///
277    /// Similar to `bind9`'s [option](https://bind9.readthedocs.io/en/latest/reference.html#namedconf-statement-max-recursion-depth)
278    ///
279    /// Defaults to `7`
280    pub fn max_recursion_depth(&mut self, max_recursion_depth: u8) -> &mut Self {
281        self.0.max_recursion_depth = max_recursion_depth;
282
283        self
284    }
285
286    /// Try building a [Client] with the configuration in this builder.
287    pub fn build(&self) -> Result<Client, BuildError> {
288        Client::new(self.0.clone())
289    }
290
291    /// Try building a [Client] with the configuration in this builder.
292    ///
293    /// Unlike [Self::build], this doesn NOT block on dns resolution when using mainline Dht.
294    #[cfg(dht)]
295    pub async fn build_async(&self) -> Result<Client, BuildError> {
296        Client::new_async(self.0.clone()).await
297    }
298}
299
300#[cfg(relays)]
301fn into_urls<T: reqwest::IntoUrl + Clone>(relays: &[T]) -> Result<Vec<Url>, InvalidRelayUrl> {
302    relays
303        .iter()
304        .map(|url| match url.clone().into_url() {
305            Err(e) => Err(InvalidRelayUrl::Parse(e)),
306            Ok(url) => {
307                if url.scheme() != "http" && url.scheme() != "https" {
308                    Err(InvalidRelayUrl::NotHttp(url.to_string()))
309                } else {
310                    Ok(url)
311                }
312            }
313        })
314        .collect::<Result<Vec<Url>, InvalidRelayUrl>>()
315}
316
317#[cfg(relays)]
318#[derive(thiserror::Error, Debug)]
319/// Errors occurring during building a [Client]
320pub enum InvalidRelayUrl {
321    #[error("Failed to parse into a Url: {0}")]
322    /// Failed to parse into a Url.
323    Parse(reqwest::Error),
324
325    #[error("Relays Urls should have `http` or `https`: {0}")]
326    /// Relays Urls should have `http` or `https`.
327    NotHttp(String),
328}