briteverify_rs/
client.rs

1#![allow(unused_qualifications)]
2//! ## BriteVerify API Client
3//
4// Standard Library Imports
5#[allow(unused_imports)]
6use std::{fmt::Debug, net::SocketAddr, ops::Deref, time::Duration};
7
8// Third-Party Imports
9use anyhow::{Context, Result};
10use futures_timer::Delay;
11use reqwest::{
12    header::{HeaderMap, HeaderValue, AUTHORIZATION},
13    StatusCode,
14};
15
16#[cfg(feature = "tracing")]
17use instrumentation as tracing;
18
19// Crate-Level Imports
20use crate::errors::BriteVerifyClientError;
21use crate::{errors, types, utils::ExtensibleUrl};
22
23// <editor-fold desc="// Constants ...">
24
25type Nullable = Option<String>;
26static V1_API_BASE_URL: &str = "https://bpi.briteverify.com/api/v1";
27static V3_API_BASE_URL: &str = "https://bulk-api.briteverify.com/api/v3";
28
29// </editor-fold desc="// Constants ...">
30
31// <editor-fold desc="// ClientBuilder ...">
32
33/// Helper for incrementally building a [`BriteVerifyClient`](BriteVerifyClient)
34/// instance with a custom configuration.
35///
36/// ## Basic Usage
37/// ```no_run
38/// # use std::time::Duration;
39/// # use briteverify_rs::{BriteVerifyClient, BriteVerifyClientBuilder};
40/// #
41/// # fn doc() -> anyhow::Result<()> {
42/// let builder: BriteVerifyClientBuilder = BriteVerifyClient::builder();
43///
44/// let client: BriteVerifyClient = builder
45///     .api_key("YOUR API KEY")
46///     .timeout(Duration::from_secs(360))
47///     .connect_timeout(Duration::from_secs(360))
48///     .build()?;
49/// # Ok(())
50/// # }
51/// ```
52#[derive(Debug)]
53#[cfg_attr(test, visible::StructFields(pub))]
54pub struct BriteVerifyClientBuilder {
55    error: Option<errors::BriteVerifyClientError>,
56    api_key: Option<HeaderValue>,
57    v1_base_url: url::Url,
58    v3_base_url: url::Url,
59    retry_enabled: bool,
60    builder: reqwest::ClientBuilder,
61}
62
63impl From<reqwest::ClientBuilder> for BriteVerifyClientBuilder {
64    fn from(builder: reqwest::ClientBuilder) -> Self {
65        Self {
66            api_key: if !crate::utils::has_auth_header(&builder) {
67                None
68            } else {
69                Some(HeaderValue::from_static("IGNORE ME"))
70            },
71            builder,
72            ..Self::default()
73        }
74    }
75}
76
77impl Default for BriteVerifyClientBuilder {
78    fn default() -> Self {
79        Self {
80            error: None,
81            api_key: None,
82            v1_base_url: url::Url::parse(V1_API_BASE_URL)
83                .expect("Couldn't parse default v1 base url"),
84            v3_base_url: url::Url::parse(V3_API_BASE_URL)
85                .expect("Couldn't parse default v1 base url"),
86            retry_enabled: false,
87            builder: reqwest::Client::builder(),
88        }
89    }
90}
91
92impl BriteVerifyClientBuilder {
93    /// Create a new [`BriteVerifyClientBuilder`][BriteVerifyClientBuilder] instance
94    ///
95    /// #### Example
96    /// ```no_run
97    /// # use briteverify_rs::BriteVerifyClientBuilder;
98    /// #
99    /// # fn doc() -> anyhow::Result<()> {
100    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new();
101    /// # Ok(())
102    /// # }
103    /// ```
104    pub fn new() -> Self {
105        Self::default()
106    }
107
108    /// Build a custom configured [`BriteVerifyClient`](BriteVerifyClient) instance.
109    ///
110    /// #### Example
111    /// ```no_run
112    /// # use briteverify_rs::BriteVerifyClient;
113    /// #
114    /// # fn doc() -> anyhow::Result<()> {
115    /// let client: BriteVerifyClient = BriteVerifyClient::builder()
116    ///     .api_key("YOUR API KEY")
117    ///     .build()?;
118    /// # Ok(())
119    /// # }
120    /// ```
121    #[cfg_attr(feature = "tracing", tracing::instrument)]
122    pub fn build(mut self) -> Result<BriteVerifyClient, errors::BriteVerifyClientError> {
123        if let Some(error) = self.error {
124            return Err(error);
125        }
126
127        match self.api_key {
128            None => Err(errors::BriteVerifyClientError::MissingApiKey),
129            Some(key) => {
130                if key.is_sensitive() {
131                    let headers = HeaderMap::from_iter([(AUTHORIZATION, key)]);
132                    self.builder = self.builder.default_headers(headers);
133                }
134
135                Ok(BriteVerifyClient {
136                    client: self
137                        .builder
138                        .build()
139                        .context("Could not create a usable `reqwest` client")?,
140                    v1_base_url: self.v1_base_url,
141                    v3_base_url: self.v3_base_url,
142                    retry_enabled: self.retry_enabled,
143                })
144            }
145        }
146    }
147
148    /// Set the API key to use for requests to the BriteVerify API
149    /// [[ref](https://docs.briteverify.com/#intro:~:text=API%20Suite%20Documentation-,Authorization,-To%20get%20started)]
150    ///
151    /// #### Example
152    /// ```no_run
153    /// # use briteverify_rs::BriteVerifyClientBuilder;
154    /// #
155    /// # fn doc() -> anyhow::Result<()> {
156    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
157    ///     .api_key("YOUR API KEY");
158    /// # Ok(())
159    /// # }
160    /// ```
161    pub fn api_key<ApiKey: ToString>(mut self, api_key: ApiKey) -> Self {
162        let api_key: String = format!(
163            "ApiKey: {}",
164            api_key.to_string().replace("ApiKey: ", "").trim()
165        );
166
167        match HeaderValue::from_str(&api_key) {
168            Ok(mut header) => {
169                header.set_sensitive(true);
170                self.api_key = Some(header);
171
172                if self.error.as_ref().is_some_and(|err| {
173                    matches!(err, &errors::BriteVerifyClientError::InvalidHeaderValue(_))
174                }) {
175                    self.error = None;
176                }
177            }
178            Err(error) => {
179                self.api_key = None;
180                self.error = Some(error.into());
181            }
182        }
183
184        self
185    }
186
187    /// Enabled or disable automatic rate limit handling via retry.
188    ///
189    /// ___
190    /// **NOTE:** Automatic retry is `disabled` by default. It must be
191    /// explicitly enabled by calling `.retry_enabled(true)` on a
192    /// [`BriteVerifyClientBuilder`](BriteVerifyClientBuilder) instance.
193    /// ___
194    ///
195    /// #### Example
196    /// ```no_run
197    /// # use briteverify_rs::BriteVerifyClientBuilder;
198    /// #
199    /// # fn doc() -> anyhow::Result<()> {
200    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
201    ///     .retry_enabled(true);
202    /// # Ok(())
203    /// # }
204    /// ```
205    pub fn retry_enabled(mut self, value: bool) -> Self {
206        self.retry_enabled = value;
207        self
208    }
209
210    /// Override the base URL for requests to the BriteVerify v1 API
211    /// [[ref](https://docs.briteverify.com/#79e00732-b734-4308-ac7f-820d62dde01f)]
212    ///
213    /// ___
214    /// **NOTE:** Unless overridden (specifically by calling [`v1_base_url`]
215    /// on a builder instance), the default value of `https://bpi.briteverify.com/api/v1`
216    /// will be used as the base url for single-transaction requests.
217    ///
218    /// If you set a custom url, be aware that no additional logic, formatting,
219    /// or validity checks will be applied to whatever value you specify.
220    /// ___
221    ///
222    /// #### Example
223    /// ```no_run
224    /// # use briteverify_rs::BriteVerifyClientBuilder;
225    /// #
226    /// # fn doc() -> anyhow::Result<()> {
227    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
228    ///     .v1_base_url("https://my-custom-domain.net/briteverify/v1");
229    /// # Ok(())
230    /// # }
231    /// ```
232    pub fn v1_base_url<URL>(mut self, url: URL) -> Self
233    where
234        URL: TryInto<url::Url>,
235        URL::Error: Into<BriteVerifyClientError>,
236    {
237        let url = url.try_into();
238
239        match url {
240            Ok(value) => {
241                self.v1_base_url = value;
242            }
243            Err(error) => {
244                self.error = Some(error.into());
245            }
246        }
247
248        self
249    }
250
251    /// Override the base URL for requests to the BriteVerify v3 API
252    /// [[ref](https://docs.briteverify.com/#382f454d-dad2-49c3-b320-c7d117fcc20a)]
253    ///
254    /// ___
255    /// **NOTE:** Unless overridden (specifically by calling [`v3_base_url`]
256    /// on a builder instance), the default value of `https://bulk-api.briteverify.com/api/v3`
257    /// will be used as the base url for bulk transaction requests.
258    ///
259    /// If you set a custom url, be aware that no additional logic, formatting,
260    /// or validity checks will be applied to whatever value you specify.
261    /// ___
262    ///
263    /// #### Example
264    /// ```no_run
265    /// # use briteverify_rs::BriteVerifyClientBuilder;
266    /// #
267    /// # fn doc() -> anyhow::Result<()> {
268    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
269    ///     .v3_base_url("https://my-custom-domain.net/briteverify/v3");
270    /// # Ok(())
271    /// # }
272    /// ```
273    pub fn v3_base_url<URL>(mut self, url: URL) -> Self
274    where
275        URL: TryInto<url::Url>,
276        URL::Error: Into<BriteVerifyClientError>,
277    {
278        let url = url.try_into();
279
280        match url {
281            Ok(value) => {
282                self.v3_base_url = value;
283            }
284            Err(error) => {
285                self.error = Some(error.into());
286            }
287        }
288
289        self
290    }
291
292    // Timeout options
293
294    /// Enables a request timeout.
295    ///
296    /// The timeout is applied from when the request starts connecting until the
297    /// response body has finished.
298    ///
299    /// Default is no timeout.
300    ///
301    /// #### Example
302    /// ```no_run
303    /// # use std::time::Duration;
304    /// # use briteverify_rs::BriteVerifyClientBuilder;
305    /// #
306    /// # fn doc() -> anyhow::Result<()> {
307    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
308    ///     .timeout(Duration::from_secs(5));
309    /// # Ok(())
310    /// # }
311    /// ```
312    #[cfg_attr(tarpaulin, coverage(off))]
313    pub fn timeout(mut self, timeout: Duration) -> Self {
314        self.builder = self.builder.timeout(timeout);
315        self
316    }
317
318    /// Set a timeout for only the connect phase of a `Client`.
319    ///
320    /// Default is `None`.
321    ///
322    /// #### Example
323    /// ```no_run
324    /// # use std::time::Duration;
325    /// # use briteverify_rs::BriteVerifyClientBuilder;
326    /// #
327    /// # fn doc() -> anyhow::Result<()> {
328    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
329    ///     .connect_timeout(Duration::from_secs(5));
330    /// # Ok(())
331    /// # }
332    /// ```
333    #[cfg_attr(tarpaulin, coverage(off))]
334    pub fn connect_timeout(mut self, timeout: Duration) -> Self {
335        self.builder = self.builder.connect_timeout(timeout);
336        self
337    }
338
339    /// Sets the `User-Agent` header to be used by the constructed client.
340    ///
341    /// Unless explicitly set, the `User-Agent` header will be omitted entirely
342    /// from all requests.
343    ///
344    /// #### Example
345    /// ```no_run
346    /// # use briteverify_rs::BriteVerifyClientBuilder;
347    /// #
348    /// # fn doc() -> anyhow::Result<()> {
349    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
350    ///     .user_agent("briteverify-rs");
351    /// # Ok(())
352    /// # }
353    /// ```
354    #[cfg_attr(tarpaulin, coverage(off))]
355    pub fn user_agent<V>(mut self, value: V) -> BriteVerifyClientBuilder
356    where
357        V: TryInto<HeaderValue>,
358        V::Error: Into<http::Error>,
359    {
360        self.builder = self.builder.user_agent(value);
361        self
362    }
363
364    /// Sets the default headers for every request.
365    ///
366    /// **NOTE:** [`HeaderMap`](HeaderMap)s do not enforce
367    /// uniqueness of contained key-value pairs. It is *absolutely*
368    /// possible to insert the same key more than once, either
369    /// with the same value or wildly different values. Proceed
370    /// accordingly.
371    ///
372    /// #### Example
373    /// ```no_run
374    /// # use briteverify_rs::BriteVerifyClientBuilder;
375    /// # fn doc() -> anyhow::Result<()> {
376    /// use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
377    ///
378    /// let mut headers = HeaderMap::new();
379    /// let content_type = HeaderValue::from_static("application/json");
380    ///
381    /// headers.insert(CONTENT_TYPE, content_type);
382    ///
383    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
384    ///     .default_headers(headers);
385    /// # Ok(())
386    /// # }
387    /// ```
388    #[cfg_attr(tarpaulin, coverage(off))]
389    pub fn default_headers(mut self, headers: HeaderMap) -> BriteVerifyClientBuilder {
390        self.builder = self.builder.default_headers(headers);
391        self
392    }
393
394    /// Enable auto gzip decompression by checking the `Content-Encoding` response header.
395    ///
396    /// If auto gzip decompression is turned on:
397    ///
398    /// - When sending a request and if the request's headers do not already contain
399    ///   an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `gzip`.
400    ///   The request body is **not** automatically compressed.
401    /// - When receiving a response, if its headers contain a `Content-Encoding` value of
402    ///   `gzip`, both `Content-Encoding` and `Content-Length` are removed from the
403    ///   headers' set. The response body is automatically decompressed.
404    ///
405    /// Because `briteverify-rs` explicitly enables `reqwest`'s *gzip* feature, this option is
406    /// enabled by default.
407    ///
408    /// #### Example
409    /// ```no_run
410    /// # use briteverify_rs::BriteVerifyClientBuilder;
411    /// #
412    /// # fn doc() -> anyhow::Result<()> {
413    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
414    ///     .gzip(true);
415    /// # Ok(())
416    /// # }
417    /// ```
418    #[cfg_attr(tarpaulin, coverage(off))]
419    pub fn gzip(mut self, enable: bool) -> BriteVerifyClientBuilder {
420        self.builder = self.builder.gzip(enable);
421        self
422    }
423
424    /// Enable auto brotli decompression by checking the `Content-Encoding` response header.
425    ///
426    /// If auto brotli decompression is turned on:
427    ///
428    /// - When sending a request and if the request's headers do not already contain
429    ///   an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `br`.
430    ///   The request body is **not** automatically compressed.
431    /// - When receiving a response, if its headers contain a `Content-Encoding` value of
432    ///   `br`, both `Content-Encoding` and `Content-Length` are removed from the
433    ///   headers' set. The response body is automatically decompressed.
434    ///
435    /// Because `briteverify-rs` explicitly enables `reqwest`'s *brotli* feature, this option is
436    /// enabled by default.
437    ///
438    /// #### Example
439    /// ```no_run
440    /// # use briteverify_rs::BriteVerifyClientBuilder;
441    /// #
442    /// # fn doc() -> anyhow::Result<()> {
443    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
444    ///     .brotli(true);
445    /// # Ok(())
446    /// # }
447    /// ```
448    #[cfg_attr(tarpaulin, coverage(off))]
449    pub fn brotli(mut self, enable: bool) -> BriteVerifyClientBuilder {
450        self.builder = self.builder.brotli(enable);
451        self
452    }
453
454    /// Disable auto response body gzip decompression.
455    ///
456    /// This method exists even if the optional `gzip` feature is not enabled.
457    /// This can be used to ensure a `Client` doesn't use gzip decompression
458    /// even if another dependency were to enable the optional `gzip` feature.
459    ///
460    /// #### Example
461    /// ```no_run
462    /// # use briteverify_rs::BriteVerifyClientBuilder;
463    /// #
464    /// # fn doc() -> anyhow::Result<()> {
465    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
466    ///     .no_gzip();
467    /// # Ok(())
468    /// # }
469    /// ```
470    #[cfg_attr(tarpaulin, coverage(off))]
471    pub fn no_gzip(mut self) -> BriteVerifyClientBuilder {
472        self.builder = self.builder.no_gzip();
473        self
474    }
475
476    /// Disable auto response body brotli decompression.
477    ///
478    /// This method exists even if the optional `brotli` feature is not enabled.
479    /// This can be used to ensure a `Client` doesn't use brotli decompression
480    /// even if another dependency were to enable the optional `brotli` feature.
481    ///
482    /// #### Example
483    /// ```no_run
484    /// # use briteverify_rs::BriteVerifyClientBuilder;
485    /// #
486    /// # fn doc() -> anyhow::Result<()> {
487    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
488    ///     .no_brotli();
489    /// # Ok(())
490    /// # }
491    /// ```
492    #[cfg_attr(tarpaulin, coverage(off))]
493    pub fn no_brotli(mut self) -> BriteVerifyClientBuilder {
494        self.builder = self.builder.no_brotli();
495        self
496    }
497
498    /// Disable auto response body deflate decompression.
499    ///
500    /// This method exists even if the optional `deflate` feature is not enabled.
501    /// This can be used to ensure a `Client` doesn't use deflate decompression
502    /// even if another dependency were to enable the optional `deflate` feature.
503    ///
504    /// #### Example
505    /// ```no_run
506    /// # use briteverify_rs::BriteVerifyClientBuilder;
507    /// #
508    /// # fn doc() -> anyhow::Result<()> {
509    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
510    ///     .no_deflate();
511    /// # Ok(())
512    /// # }
513    /// ```
514    #[cfg_attr(tarpaulin, coverage(off))]
515    pub fn no_deflate(mut self) -> BriteVerifyClientBuilder {
516        self.builder = self.builder.no_deflate();
517        self
518    }
519
520    // Redirect options
521
522    /// Set a [`RedirectPolicy`](reqwest::redirect::Policy) for this client.
523    ///
524    /// Default will follow redirects up to a maximum of 10.
525    ///
526    /// #### Example
527    /// ```no_run
528    /// # use briteverify_rs::BriteVerifyClientBuilder;
529    /// #
530    /// # fn doc() -> anyhow::Result<()> {
531    /// use reqwest::redirect::Policy as RedirectPolicy;
532    ///
533    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
534    ///     .redirect(RedirectPolicy::none());
535    /// # Ok(())
536    /// # }
537    /// ```
538    #[cfg_attr(tarpaulin, coverage(off))]
539    pub fn redirect(mut self, policy: reqwest::redirect::Policy) -> BriteVerifyClientBuilder {
540        self.builder = self.builder.redirect(policy);
541        self
542    }
543
544    /// Enable or disable automatic setting of the `Referer` header.
545    ///
546    /// Default is `true`.
547    ///
548    /// #### Example
549    /// ```no_run
550    /// # use briteverify_rs::BriteVerifyClientBuilder;
551    /// #
552    /// # fn doc() -> anyhow::Result<()> {
553    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
554    ///     .referer(true);
555    /// # Ok(())
556    /// # }
557    /// ```
558    #[cfg_attr(tarpaulin, coverage(off))]
559    pub fn referer(mut self, enable: bool) -> BriteVerifyClientBuilder {
560        self.builder = self.builder.referer(enable);
561        self
562    }
563
564    // Proxy options
565
566    /// Add a [`Proxy`](reqwest::Proxy) to the list of proxies the
567    /// constructed [`BriteVerifyClient`](BriteVerifyClient) will use.
568    ///
569    /// # Note
570    ///
571    /// Adding a proxy will disable the automatic usage of the "system" proxy.
572    ///
573    /// #### Example
574    /// ```no_run
575    /// # use briteverify_rs::BriteVerifyClientBuilder;
576    /// #
577    /// # fn doc() -> anyhow::Result<()> {
578    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
579    ///     .proxy(reqwest::Proxy::http("https://my.prox")?);
580    /// # Ok(())
581    /// # }
582    /// ```
583    #[cfg_attr(tarpaulin, coverage(off))]
584    pub fn proxy(mut self, proxy: reqwest::Proxy) -> BriteVerifyClientBuilder {
585        self.builder = self.builder.proxy(proxy);
586        self
587    }
588
589    /// Clear all [`Proxies`](reqwest::Proxy), so the constructed
590    /// [`BriteVerifyClient`](BriteVerifyClient) will not use any proxies.
591    ///
592    /// # Note
593    /// To add a proxy exclusion list, use [`reqwest::Proxy::no_proxy()`](reqwest::Proxy::no_proxy)
594    /// on all desired proxies instead.
595    ///
596    /// This also disables the automatic usage of the "system" proxy.
597    ///
598    /// #### Example
599    /// ```no_run
600    /// # use briteverify_rs::BriteVerifyClientBuilder;
601    /// #
602    /// # fn doc() -> anyhow::Result<()> {
603    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
604    ///     .no_proxy();
605    /// # Ok(())
606    /// # }
607    /// ```
608    #[cfg_attr(tarpaulin, coverage(off))]
609    pub fn no_proxy(mut self) -> BriteVerifyClientBuilder {
610        self.builder = self.builder.no_proxy();
611        self
612    }
613
614    /// Set whether connections should emit verbose logs.
615    ///
616    /// Enabling this option will emit [`log`](https://crates.io/crates/log)
617    /// messages at the `TRACE` level for read and write operations on connections.
618    ///
619    /// #### Example
620    /// ```no_run
621    /// # use briteverify_rs::BriteVerifyClientBuilder;
622    /// #
623    /// # fn doc() -> anyhow::Result<()> {
624    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
625    ///     .connection_verbose(true);
626    /// # Ok(())
627    /// # }
628    /// ```
629    #[cfg_attr(tarpaulin, coverage(off))]
630    pub fn connection_verbose(mut self, verbose: bool) -> BriteVerifyClientBuilder {
631        self.builder = self.builder.connection_verbose(verbose);
632        self
633    }
634
635    // HTTP options
636
637    /// Set an optional timeout for idle sockets being kept-alive.
638    ///
639    /// Pass `None` to disable timeout.
640    ///
641    /// Unless otherwise set, the default is 90 seconds.
642    ///
643    /// #### Example
644    /// ```no_run
645    /// # use briteverify_rs::BriteVerifyClientBuilder;
646    /// #
647    /// # fn doc() -> anyhow::Result<()> {
648    /// use std::time::Duration;
649    ///
650    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
651    ///     .pool_idle_timeout(Some(Duration::from_secs(10)));
652    /// # Ok(())
653    /// # }
654    /// ```
655    #[cfg_attr(tarpaulin, coverage(off))]
656    pub fn pool_idle_timeout<D: Into<Option<Duration>>>(
657        mut self,
658        value: D,
659    ) -> BriteVerifyClientBuilder {
660        self.builder = self.builder.pool_idle_timeout(value);
661        self
662    }
663
664    /// Sets the maximum idle connection per host allowed in the pool.
665    ///
666    /// #### Example
667    /// ```no_run
668    /// # use briteverify_rs::BriteVerifyClientBuilder;
669    /// #
670    /// # fn doc() -> anyhow::Result<()> {
671    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
672    ///     .pool_max_idle_per_host(10);
673    /// # Ok(())
674    /// # }
675    /// ```
676    #[cfg_attr(tarpaulin, coverage(off))]
677    pub fn pool_max_idle_per_host(mut self, value: usize) -> BriteVerifyClientBuilder {
678        self.builder = self.builder.pool_max_idle_per_host(value);
679        self
680    }
681
682    /// Send headers as title case instead of lowercase.
683    ///
684    /// Enabling this means that header key-value pairs
685    /// that would normally be sent as:
686    ///
687    /// ```yaml
688    /// {
689    ///   # ...
690    ///   "some-header-key": "The Best Header Value Ever Conceived By Gods Or Men",
691    ///   "anotherheaderkey": "A Header Value So Terrible It Must Never Be Spoken Of",
692    ///   # ...
693    /// }
694    /// ```
695    ///
696    /// will instead be sent as:
697    ///
698    /// ```yaml
699    /// {
700    ///   # ...
701    ///   "Some-Header-Key": "The Headerless Horseman, Terror Of Sleepy Hollow",
702    ///   "AnotherHeaderKey": "The Multi-Headed Centaur, Joy Of Wakeful Solidity",
703    ///   # ...
704    /// }
705    /// ```
706    ///
707    /// #### Example
708    /// ```no_run
709    /// # use briteverify_rs::BriteVerifyClientBuilder;
710    /// #
711    /// # fn doc() -> anyhow::Result<()> {
712    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
713    ///     .http1_title_case_headers();
714    /// # Ok(())
715    /// # }
716    /// ```
717    #[cfg_attr(tarpaulin, coverage(off))]
718    pub fn http1_title_case_headers(mut self) -> BriteVerifyClientBuilder {
719        self.builder = self.builder.http1_title_case_headers();
720        self
721    }
722
723    /// Set whether *HTTP/1* connections will accept obsolete line folding for
724    /// header values.
725    ///
726    /// When enabled, newline codepoints (`\r` and `\n`) will be transformed to
727    /// spaces when parsing.
728    ///
729    /// #### Example
730    /// ```no_run
731    /// # use briteverify_rs::BriteVerifyClientBuilder;
732    /// #
733    /// # fn doc() -> anyhow::Result<()> {
734    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
735    ///     .http1_allow_obsolete_multiline_headers_in_responses(true);
736    /// # Ok(())
737    /// # }
738    /// ```
739    #[cfg_attr(tarpaulin, coverage(off))]
740    pub fn http1_allow_obsolete_multiline_headers_in_responses(
741        mut self,
742        value: bool,
743    ) -> BriteVerifyClientBuilder {
744        self.builder = self
745            .builder
746            .http1_allow_obsolete_multiline_headers_in_responses(value);
747        self
748    }
749
750    /// Only use *HTTP/1*.
751    ///
752    /// Calling this method implicitly disables the use of
753    /// *HTTP/2* and/or *HTTP/3*.
754    ///
755    /// #### Example
756    /// ```no_run
757    /// # use briteverify_rs::BriteVerifyClientBuilder;
758    /// #
759    /// # fn doc() -> anyhow::Result<()> {
760    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
761    ///     .http1_only();
762    /// # Ok(())
763    /// # }
764    /// ```
765    #[cfg_attr(tarpaulin, coverage(off))]
766    pub fn http1_only(mut self) -> BriteVerifyClientBuilder {
767        self.builder = self.builder.http1_only();
768        self
769    }
770
771    /// Allow *HTTP/0.9* responses
772    ///
773    /// #### Example
774    /// ```no_run
775    /// # use briteverify_rs::BriteVerifyClientBuilder;
776    /// #
777    /// # fn doc() -> anyhow::Result<()> {
778    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
779    ///     .http09_responses();
780    /// # Ok(())
781    /// # }
782    /// ```
783    #[cfg_attr(tarpaulin, coverage(off))]
784    pub fn http09_responses(mut self) -> BriteVerifyClientBuilder {
785        self.builder = self.builder.http09_responses();
786        self
787    }
788
789    /// Only use *HTTP/2*.
790    ///
791    /// Calling this method implicitly disables the use of
792    /// *HTTP/1* and/or *HTTP/3*.
793    ///
794    /// #### Example
795    /// ```no_run
796    /// # use briteverify_rs::BriteVerifyClientBuilder;
797    /// #
798    /// # fn doc() -> anyhow::Result<()> {
799    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
800    ///     .http2_prior_knowledge();
801    /// # Ok(())
802    /// # }
803    /// ```
804    #[cfg_attr(tarpaulin, coverage(off))]
805    pub fn http2_prior_knowledge(mut self) -> BriteVerifyClientBuilder {
806        self.builder = self.builder.http2_prior_knowledge();
807        self
808    }
809
810    /// Sets the `SETTINGS_INITIAL_WINDOW_SIZE` option for *HTTP/2*
811    /// stream-level flow control.
812    ///
813    /// Default is currently 65,535 but may change internally to
814    /// optimize for common uses.
815    ///
816    /// #### Example
817    /// ```no_run
818    /// # use briteverify_rs::BriteVerifyClientBuilder;
819    /// #
820    /// # fn doc() -> anyhow::Result<()> {
821    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
822    ///     .http2_initial_stream_window_size(32_767u32);
823    /// # Ok(())
824    /// # }
825    /// ```
826    #[cfg_attr(tarpaulin, coverage(off))]
827    pub fn http2_initial_stream_window_size<WindowSize: Into<Option<u32>>>(
828        mut self,
829        value: WindowSize,
830    ) -> BriteVerifyClientBuilder {
831        self.builder = self.builder.http2_initial_stream_window_size(value);
832        self
833    }
834
835    /// Sets the max connection-level flow control for *HTTP/2*
836    ///
837    /// Default is currently 65,535 but may change internally to
838    /// optimize for common uses.
839    ///
840    /// #### Example
841    /// ```no_run
842    /// # use briteverify_rs::BriteVerifyClientBuilder;
843    /// #
844    /// # fn doc() -> anyhow::Result<()> {
845    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
846    ///     .http2_initial_connection_window_size(16_383u32);
847    /// # Ok(())
848    /// # }
849    /// ```
850    #[cfg_attr(tarpaulin, coverage(off))]
851    pub fn http2_initial_connection_window_size<WindowSize: Into<Option<u32>>>(
852        mut self,
853        value: WindowSize,
854    ) -> BriteVerifyClientBuilder {
855        self.builder = self.builder.http2_initial_connection_window_size(value);
856        self
857    }
858
859    /// Sets whether to use an adaptive flow control.
860    ///
861    /// Enabling this will override the limits set in
862    /// [`http2_initial_stream_window_size`] and
863    /// [`http2_initial_connection_window_size`].
864    ///
865    /// [`http2_initial_stream_window_size`]: #method.http2_initial_stream_window_size
866    /// [`http2_initial_connection_window_size`]: #method.http2_initial_connection_window_size
867    ///
868    /// #### Example
869    /// ```no_run
870    /// # use briteverify_rs::BriteVerifyClientBuilder;
871    /// #
872    /// # fn doc() -> anyhow::Result<()> {
873    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
874    ///     .http2_adaptive_window(true);
875    /// # Ok(())
876    /// # }
877    /// ```
878    #[cfg_attr(tarpaulin, coverage(off))]
879    pub fn http2_adaptive_window(mut self, enabled: bool) -> BriteVerifyClientBuilder {
880        self.builder = self.builder.http2_adaptive_window(enabled);
881        self
882    }
883
884    /// Sets the maximum frame size to use for HTTP2.
885    ///
886    /// Default is currently 16,384 but may change internally
887    /// to optimize for common uses.
888    ///
889    /// #### Example
890    /// ```no_run
891    /// # use briteverify_rs::BriteVerifyClientBuilder;
892    /// #
893    /// # fn doc() -> anyhow::Result<()> {
894    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
895    ///     .http2_max_frame_size(8_192u32);
896    /// # Ok(())
897    /// # }
898    /// ```
899    #[cfg_attr(tarpaulin, coverage(off))]
900    pub fn http2_max_frame_size<FrameSize: Into<Option<u32>>>(
901        mut self,
902        value: FrameSize,
903    ) -> BriteVerifyClientBuilder {
904        self.builder = self.builder.http2_max_frame_size(value);
905        self
906    }
907
908    /// Sets the interval for sending *HTTP/2* ping frames to
909    /// keep a connection alive.
910    ///
911    /// Pass `None` to disable *HTTP/2* keep-alive.
912    /// Default is currently disabled.
913    ///
914    /// #### Example
915    /// ```no_run
916    /// # use briteverify_rs::BriteVerifyClientBuilder;
917    /// #
918    /// # fn doc() -> anyhow::Result<()> {
919    /// use std::time::Duration;
920    ///
921    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
922    ///     .http2_keep_alive_interval(Some(Duration::from_secs(10)));
923    /// # Ok(())
924    /// # }
925    /// ```
926    #[cfg_attr(tarpaulin, coverage(off))]
927    pub fn http2_keep_alive_interval<Interval: Into<Option<Duration>>>(
928        mut self,
929        interval: Interval,
930    ) -> BriteVerifyClientBuilder {
931        self.builder = self.builder.http2_keep_alive_interval(interval);
932        self
933    }
934
935    /// Set the timeout for receiving an acknowledgement of
936    /// *HTTP/2* keep-alive ping frames.
937    ///
938    /// If a ping is not acknowledged within the timeout,
939    /// the connection will be closed. Does nothing if `http2_keep_alive_interval`
940    /// is disabled. Default is currently disabled.
941    ///
942    /// [`http2_keep_alive_interval`]: #method.http2_keep_alive_interval
943    ///
944    /// #### Example
945    /// ```no_run
946    /// # use briteverify_rs::BriteVerifyClientBuilder;
947    /// #
948    /// # fn doc() -> anyhow::Result<()> {
949    /// use std::time::Duration;
950    ///
951    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
952    ///     .http2_keep_alive_timeout(Duration::from_secs(2));
953    /// # Ok(())
954    /// # }
955    /// ```
956    #[cfg_attr(tarpaulin, coverage(off))]
957    pub fn http2_keep_alive_timeout(mut self, timeout: Duration) -> BriteVerifyClientBuilder {
958        self.builder = self.builder.http2_keep_alive_timeout(timeout);
959        self
960    }
961
962    /// Sets whether *HTTP/2* keep-alive should apply while the connection is idle.
963    ///
964    /// If disabled, keep-alive pings are only sent while there are open
965    /// request/responses streams. If enabled, pings are also sent when no
966    /// streams are active. Does nothing if `http2_keep_alive_interval` is disabled.
967    /// Default is `false`.
968    ///
969    ///[`http2_keep_alive_interval`]: #method.http2_keep_alive_interval
970    ///
971    /// #### Example
972    /// ```no_run
973    /// # use briteverify_rs::BriteVerifyClientBuilder;
974    /// #
975    /// # fn doc() -> anyhow::Result<()> {
976    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
977    ///     .http2_keep_alive_while_idle(true);
978    /// # Ok(())
979    /// # }
980    /// ```
981    #[cfg_attr(tarpaulin, coverage(off))]
982    pub fn http2_keep_alive_while_idle(mut self, enabled: bool) -> BriteVerifyClientBuilder {
983        self.builder = self.builder.http2_keep_alive_while_idle(enabled);
984        self
985    }
986
987    // TCP options
988
989    /// Set whether sockets have `TCP_NODELAY` enabled.
990    ///
991    /// Default is `true`.
992    ///
993    /// #### Example
994    /// ```no_run
995    /// # use briteverify_rs::BriteVerifyClientBuilder;
996    /// #
997    /// # fn doc() -> anyhow::Result<()> {
998    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
999    ///     .tcp_nodelay(false);
1000    /// # Ok(())
1001    /// # }
1002    /// ```
1003    #[cfg_attr(tarpaulin, coverage(off))]
1004    pub fn tcp_nodelay(mut self, enabled: bool) -> BriteVerifyClientBuilder {
1005        self.builder = self.builder.tcp_nodelay(enabled);
1006        self
1007    }
1008
1009    /// Bind to a local IP Address.
1010    ///
1011    /// #### Example
1012    ///
1013    /// ```no_run
1014    /// use std::net::IpAddr;
1015    ///
1016    /// # fn doc() -> anyhow::Result<()> {
1017    /// let local_addr = IpAddr::from([12, 4, 1, 8]);
1018    ///
1019    /// let client = briteverify_rs::BriteVerifyClient::builder()
1020    ///     .api_key("YOUR API KEY")
1021    ///     .local_address(local_addr)
1022    ///     .build()?;
1023    /// # Ok(())
1024    /// # }
1025    /// ```
1026    #[cfg_attr(tarpaulin, coverage(off))]
1027    pub fn local_address<T: Into<Option<std::net::IpAddr>>>(
1028        mut self,
1029        address: T,
1030    ) -> BriteVerifyClientBuilder {
1031        self.builder = self.builder.local_address(address);
1032        self
1033    }
1034
1035    /// Set that all sockets have `SO_KEEPALIVE` set with the supplied duration.
1036    ///
1037    /// If `None`, the option will not be set.
1038    ///
1039    /// #### Example
1040    /// ```no_run
1041    /// # use briteverify_rs::BriteVerifyClientBuilder;
1042    /// #
1043    /// # fn doc() -> anyhow::Result<()> {
1044    /// use std::time::Duration;
1045    ///
1046    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
1047    ///     .tcp_keepalive(Some(Duration::from_secs(2)));
1048    /// # Ok(())
1049    /// # }
1050    /// ```
1051    #[cfg_attr(tarpaulin, coverage(off))]
1052    pub fn tcp_keepalive<D: Into<Option<Duration>>>(
1053        mut self,
1054        value: D,
1055    ) -> BriteVerifyClientBuilder {
1056        self.builder = self.builder.tcp_keepalive(value);
1057        self
1058    }
1059
1060    // TLS options
1061
1062    /// Add a custom root certificate.
1063    ///
1064    /// This can be used to connect to a server that has a self-signed
1065    /// certificate for example.
1066    ///
1067    /// #### Example
1068    /// ```no_run
1069    /// # use std::io::Read;
1070    /// # use briteverify_rs::BriteVerifyClientBuilder;
1071    /// #
1072    /// # fn doc() -> anyhow::Result<()> {
1073    /// let mut buf = Vec::new();
1074    ///
1075    /// std::fs::File::open("my_cert.pem")?.read_to_end(&mut buf)?;
1076    ///
1077    /// let cert = reqwest::Certificate::from_pem(&buf)?;
1078    ///
1079    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
1080    ///     .add_root_certificate(cert);
1081    /// # Ok(())
1082    /// # }
1083    /// ```
1084    #[cfg_attr(tarpaulin, coverage(off))]
1085    pub fn add_root_certificate(mut self, cert: reqwest::Certificate) -> BriteVerifyClientBuilder {
1086        self.builder = self.builder.add_root_certificate(cert);
1087        self
1088    }
1089
1090    /// Controls the use of built-in/preloaded certificates during certificate validation.
1091    ///
1092    /// Defaults to `true`, meaning built-in system certs will be used.
1093    ///
1094    /// #### Example
1095    /// ```no_run
1096    /// # use briteverify_rs::BriteVerifyClientBuilder;
1097    /// #
1098    /// # fn doc() -> anyhow::Result<()> {
1099    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
1100    ///     .tls_built_in_root_certs(false);
1101    /// # Ok(())
1102    /// # }
1103    /// ```
1104    #[cfg_attr(tarpaulin, coverage(off))]
1105    pub fn tls_built_in_root_certs(mut self, enabled: bool) -> BriteVerifyClientBuilder {
1106        self.builder = self.builder.tls_built_in_root_certs(enabled);
1107        self
1108    }
1109
1110    /// Sets the identity to be used for client certificate authentication.
1111    ///
1112    /// #### Example
1113    /// ```no_run
1114    /// # use std::io::Read;
1115    /// # use briteverify_rs::BriteVerifyClientBuilder;
1116    /// #
1117    /// # fn doc() -> anyhow::Result<()> {
1118    /// let mut buf = Vec::new();
1119    ///
1120    /// std::fs::File::open("my_cert.pem")?.read_to_end(&mut buf)?;
1121    ///
1122    /// let identity = reqwest::Identity::from_pem(&buf)?;
1123    ///
1124    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
1125    ///     .identity(identity);
1126    /// # Ok(())
1127    /// # }
1128    /// ```
1129    #[cfg_attr(tarpaulin, coverage(off))]
1130    pub fn identity(mut self, value: reqwest::Identity) -> BriteVerifyClientBuilder {
1131        self.builder = self.builder.identity(value);
1132        self
1133    }
1134
1135    /// Controls the use of certificate validation.
1136    ///
1137    /// Defaults to `false`.
1138    ///
1139    /// ## **Warning**
1140    ///
1141    /// You should think very carefully before using this method. If
1142    /// invalid certificates are trusted, *any* certificate for *any* site
1143    /// will be trusted for use. This includes expired certificates. This
1144    /// introduces significant vulnerabilities, and should only be used
1145    /// as a last resort.
1146    ///
1147    /// #### Example
1148    /// ```no_run
1149    /// # use briteverify_rs::BriteVerifyClientBuilder;
1150    /// #
1151    /// # fn doc() -> anyhow::Result<()> {
1152    /// // NOTE: Read the warning above, then read it again.
1153    /// //       You can do this, but it's a virtual guarantee
1154    /// //       that you shouldn't.
1155    ///
1156    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
1157    ///     .danger_accept_invalid_certs(true);
1158    /// # Ok(())
1159    /// # }
1160    /// ```
1161    #[cfg_attr(tarpaulin, coverage(off))]
1162    pub fn danger_accept_invalid_certs(mut self, enabled: bool) -> BriteVerifyClientBuilder {
1163        self.builder = self.builder.danger_accept_invalid_certs(enabled);
1164        self
1165    }
1166
1167    /// Controls the use of TLS server name indication.
1168    ///
1169    /// Defaults to `true`.
1170    ///
1171    /// #### Example
1172    /// ```no_run
1173    /// # use briteverify_rs::BriteVerifyClientBuilder;
1174    /// #
1175    /// # fn doc() -> anyhow::Result<()> {
1176    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
1177    ///     .tls_sni(false);
1178    /// # Ok(())
1179    /// # }
1180    /// ```
1181    #[cfg_attr(tarpaulin, coverage(off))]
1182    pub fn tls_sni(mut self, enabled: bool) -> BriteVerifyClientBuilder {
1183        self.builder = self.builder.tls_sni(enabled);
1184        self
1185    }
1186
1187    /// Set the minimum required TLS version for connections.
1188    ///
1189    /// By default the TLS backend's own default is used.
1190    ///
1191    /// #### Errors
1192    ///
1193    /// A value of `tls::Version::TLS_1_3` will cause an error with `reqwest`'s
1194    /// `native-tls` or `default-tls` backends. This does not mean the version
1195    /// isn't supported, just that it can't be set as a minimum due to
1196    /// technical limitations.
1197    ///
1198    /// #### Example
1199    /// ```no_run
1200    /// # use briteverify_rs::BriteVerifyClientBuilder;
1201    /// #
1202    /// # fn doc() -> anyhow::Result<()> {
1203    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
1204    ///     .min_tls_version(reqwest::tls::Version::TLS_1_1);
1205    /// # Ok(())
1206    /// # }
1207    /// ```
1208    #[cfg_attr(tarpaulin, coverage(off))]
1209    pub fn min_tls_version(mut self, version: reqwest::tls::Version) -> BriteVerifyClientBuilder {
1210        self.builder = self.builder.min_tls_version(version);
1211        self
1212    }
1213
1214    /// Set the maximum allowed TLS version for connections.
1215    ///
1216    /// By default there's no maximum.
1217    ///
1218    /// #### Errors
1219    ///
1220    /// A value of `tls::Version::TLS_1_3` will cause an error with `reqwest`'s
1221    /// `native-tls` or `default-tls` backends. This does not mean the version
1222    /// isn't supported, just that it can't be set as a maximum due to
1223    /// technical limitations.
1224    ///
1225    /// #### Example
1226    /// ```no_run
1227    /// # use briteverify_rs::BriteVerifyClientBuilder;
1228    /// #
1229    /// # fn doc() -> anyhow::Result<()> {
1230    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
1231    ///     .max_tls_version(reqwest::tls::Version::TLS_1_2);
1232    /// # Ok(())
1233    /// # }
1234    /// ```
1235    #[cfg_attr(tarpaulin, coverage(off))]
1236    pub fn max_tls_version(mut self, version: reqwest::tls::Version) -> BriteVerifyClientBuilder {
1237        self.builder = self.builder.max_tls_version(version);
1238        self
1239    }
1240
1241    /// Disables the trust-dns async resolver.
1242    ///
1243    /// This method exists even if `reqwest`'s optional `trust-dns`
1244    /// feature is not enabled. This can be used to ensure a `BriteVerifyClient`
1245    /// doesn't use the trust-dns async resolver even if another dependency were
1246    /// to enable the optional `trust-dns` feature.
1247    ///
1248    /// #### Example
1249    /// ```no_run
1250    /// # use briteverify_rs::BriteVerifyClientBuilder;
1251    /// #
1252    /// # fn doc() -> anyhow::Result<()> {
1253    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
1254    ///     .no_trust_dns();
1255    /// # Ok(())
1256    /// # }
1257    /// ```
1258    #[cfg_attr(tarpaulin, coverage(off))]
1259    pub fn no_trust_dns(mut self) -> BriteVerifyClientBuilder {
1260        self.builder = self.builder.no_trust_dns();
1261        self
1262    }
1263
1264    /// Restrict the constructed `BriteVerifyClient` using only HTTPS requests.
1265    ///
1266    /// Defaults to false.
1267    ///
1268    /// #### Example
1269    /// ```no_run
1270    /// # use briteverify_rs::BriteVerifyClientBuilder;
1271    /// #
1272    /// # fn doc() -> anyhow::Result<()> {
1273    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
1274    ///     .https_only(true);
1275    /// # Ok(())
1276    /// # }
1277    /// ```
1278    pub fn https_only(mut self, enabled: bool) -> BriteVerifyClientBuilder {
1279        self.builder = self.builder.https_only(enabled);
1280
1281        if enabled {
1282            self.v1_base_url
1283                .set_scheme(http::uri::Scheme::HTTPS.as_str())
1284                .unwrap_or_else(|_| log::error!("Could not set `v1_base_url` scheme to HTTPS"));
1285            self.v3_base_url
1286                .set_scheme(http::uri::Scheme::HTTPS.as_str())
1287                .unwrap_or_else(|_| log::error!("Could not set `v3_base_url` scheme to HTTPS"));
1288        }
1289
1290        self
1291    }
1292
1293    /// Override DNS resolution for specific domains to a particular IP address.
1294    ///
1295    /// ## **Warning**
1296    ///
1297    /// Since the DNS protocol has no notion of ports, if you wish to send
1298    /// traffic to a particular port you must include this port in the URL
1299    /// itself, any port in the overridden address will be ignored and traffic
1300    /// will be sent to the conventional port for the given scheme (e.g. 80 for http).
1301    ///
1302    /// #### Example
1303    /// ```no_run
1304    /// # use briteverify_rs::BriteVerifyClientBuilder;
1305    /// #
1306    /// # fn doc() -> anyhow::Result<()> {
1307    /// let resolver: std::net::SocketAddr = "[::]:53".parse()?;
1308    ///
1309    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
1310    ///     .resolve("my.super-awesome-domain.net", resolver);
1311    /// # Ok(())
1312    /// # }
1313    /// ```
1314    #[cfg_attr(tarpaulin, coverage(off))]
1315    #[cfg_attr(feature = "tracing", tracing::instrument)]
1316    pub fn resolve(
1317        mut self,
1318        domain: &str,
1319        address: std::net::SocketAddr,
1320    ) -> BriteVerifyClientBuilder {
1321        log::debug!("DNS resolver installed for: '{domain}' -> {:?}", &address);
1322        self.builder = self.builder.resolve(domain, address);
1323        self
1324    }
1325
1326    /// Override DNS resolution for specific domains to a set of particular IP addresses.
1327    ///
1328    /// ## **Warning**
1329    ///
1330    /// Since the DNS protocol has no notion of ports, if you wish to send
1331    /// traffic to a particular port you must include this port in the URL
1332    /// itself, any port in the overridden addresses will be ignored and traffic
1333    /// will be sent to the conventional port for the given scheme (e.g. 80 for http).
1334    ///
1335    /// #### Example
1336    /// ```no_run
1337    /// # use briteverify_rs::BriteVerifyClientBuilder;
1338    /// #
1339    /// # fn doc() -> anyhow::Result<()> {
1340    /// let resolvers: [std::net::SocketAddr; 3] = [
1341    ///     "1.1.1.1:53".parse()?,
1342    ///     "8.8.8.8:53".parse()?,
1343    ///     "2001:4860:4860::8844:53".parse()?,
1344    /// ];
1345    ///
1346    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
1347    ///     .resolve_to_addrs("my.super-awesome-domain.net", &resolvers);
1348    /// # Ok(())
1349    /// # }
1350    /// ```
1351    #[cfg_attr(tarpaulin, coverage(off))]
1352    pub fn resolve_to_addrs(
1353        mut self,
1354        domain: &str,
1355        addresses: &[std::net::SocketAddr],
1356    ) -> BriteVerifyClientBuilder {
1357        self.builder = self.builder.resolve_to_addrs(domain, addresses);
1358        self
1359    }
1360
1361    /// Override the DNS resolver implementation.
1362    ///
1363    /// Pass an [`Arc`](std::sync::Arc) wrapping any object that implements
1364    /// [`Resolve`](reqwest::dns::Resolve). Overrides for specific names passed
1365    /// to [`resolve`] and [`resolve_to_addrs`] will still be applied on top of this
1366    /// resolver.
1367    ///
1368    /// [`resolve`]: #method.resolve
1369    /// [`resolve_to_addrs`]: #method.resolve_to_addrs
1370    ///
1371    /// #### Example
1372    /// ```ignore
1373    /// # use briteverify_rs::BriteVerifyClientBuilder;
1374    /// #
1375    /// # fn doc<Resolver: reqwest::dns::Resolve + 'static>() -> anyhow::Result<()> {
1376    /// # type Resolver = ();
1377    /// // NOTE: expected type of `Resolver` is reqwest::dns::Resolve + 'static
1378    /// //       when used, the actual object will likely be specific to your implementation
1379    /// let my_resolver: Resolver = ();
1380    ///
1381    /// let builder: BriteVerifyClientBuilder = BriteVerifyClientBuilder::new()
1382    ///     .dns_resolver(std::sync::Arc::new(my_resolver));
1383    /// # Ok(())
1384    /// # }
1385    /// ```
1386    #[cfg_attr(tarpaulin, coverage(off))]
1387    pub fn dns_resolver<R: reqwest::dns::Resolve + 'static>(
1388        mut self,
1389        resolver: std::sync::Arc<R>,
1390    ) -> BriteVerifyClientBuilder {
1391        self.builder = self.builder.dns_resolver(resolver);
1392        self
1393    }
1394}
1395
1396// </editor-fold desc="// ClientBuilder ...">
1397
1398// <editor-fold desc="// Client ...">
1399
1400/// `briteverify-rs`'s [`reqwest`](https://docs.rs/reqwest/latest/reqwest/)-based client
1401///
1402/// ## Basic Usage
1403/// ```no_run
1404/// # use std::time::Duration;
1405/// # use briteverify_rs::{BriteVerifyClient, types::AccountCreditBalance};
1406/// #
1407/// # #[tokio::main]
1408/// # async fn doc() -> anyhow::Result<()> {
1409/// let client: BriteVerifyClient = BriteVerifyClient::new("YOUR API KEY")?;
1410///
1411/// let balance: AccountCreditBalance = client.get_account_balance().await?;
1412///
1413/// println!("{balance:#?}");
1414/// # Ok(())
1415/// # }
1416/// ```
1417#[derive(Debug)]
1418#[cfg_attr(test, visible::StructFields(pub))]
1419pub struct BriteVerifyClient {
1420    client: reqwest::Client,
1421    v1_base_url: url::Url,
1422    v3_base_url: url::Url,
1423    retry_enabled: bool,
1424}
1425
1426impl Deref for BriteVerifyClient {
1427    type Target = reqwest::Client;
1428
1429    fn deref(&self) -> &Self::Target {
1430        &self.client
1431    }
1432}
1433
1434impl TryFrom<reqwest::Client> for BriteVerifyClient {
1435    type Error = errors::BriteVerifyClientError;
1436
1437    fn try_from(client: reqwest::Client) -> Result<Self, Self::Error> {
1438        if crate::utils::has_auth_header(&client) {
1439            Ok(Self {
1440                client,
1441                retry_enabled: true,
1442                v1_base_url: V1_API_BASE_URL.parse::<url::Url>().unwrap(),
1443                v3_base_url: V3_API_BASE_URL.parse::<url::Url>().unwrap(),
1444            })
1445        } else {
1446            Err(errors::BriteVerifyClientError::MissingApiKey)
1447        }
1448    }
1449}
1450
1451impl BriteVerifyClient {
1452    // <editor-fold desc="// Constructors ... ">
1453
1454    /// Create a new [`BriteVerifyClient`][BriteVerifyClient] instance
1455    ///
1456    /// #### Example
1457    /// ```no_run
1458    /// # use briteverify_rs::BriteVerifyClient;
1459    /// #
1460    /// # fn doc() -> anyhow::Result<()> {
1461    /// let client: BriteVerifyClient = BriteVerifyClient::new("YOUR API KEY")?;
1462    /// # Ok(())
1463    /// # }
1464    /// ```
1465    pub fn new<ApiKey: ToString>(api_key: ApiKey) -> Result<Self, errors::BriteVerifyClientError> {
1466        Self::builder().api_key(api_key).build()
1467    }
1468
1469    /// Create a new [builder][BriteVerifyClientBuilder] to incrementally
1470    /// build a [`BriteVerifyClient`][BriteVerifyClient] with a customised
1471    /// configuration
1472    ///
1473    /// #### Example
1474    /// ```no_run
1475    /// # use briteverify_rs::{BriteVerifyClient, BriteVerifyClientBuilder};
1476    /// #
1477    /// # fn doc() -> anyhow::Result<()> {
1478    /// let builder: BriteVerifyClientBuilder = BriteVerifyClient::builder();
1479    ///
1480    /// // ... call various builder methods
1481    ///
1482    /// let client: BriteVerifyClient = builder.build()?;
1483    /// # Ok(())
1484    /// # }
1485    /// ```
1486    pub fn builder() -> BriteVerifyClientBuilder {
1487        BriteVerifyClientBuilder::new()
1488    }
1489
1490    // </editor-fold desc="// Constructors ... ">
1491
1492    // <editor-fold desc="// Internal Utility Methods ... ">
1493
1494    /// [internal-implementation]
1495    /// Build and send the supplied request
1496    ///
1497    /// If `retry_enabled` is true, rate limit error responses
1498    /// will be automatically handled by sleeping until the rate
1499    /// limit expires and re-sending the request
1500    async fn _build_and_send(
1501        &self,
1502        builder: reqwest::RequestBuilder,
1503    ) -> Result<reqwest::Response, errors::BriteVerifyClientError> {
1504        loop {
1505            let response = (match builder.try_clone() {
1506                Some(instance) => instance,
1507                None => break Err(errors::BriteVerifyClientError::UnclonableRequest),
1508            })
1509            .send()
1510            .await?;
1511
1512            match (&self.retry_enabled, response.status()) {
1513                (_, StatusCode::UNAUTHORIZED) => {
1514                    break Err(errors::BriteVerifyClientError::InvalidApiKey);
1515                }
1516                (&true, StatusCode::TOO_MANY_REQUESTS) => {
1517                    let retry_after = 1 + response
1518                        .headers()
1519                        .get("retry-after")
1520                        .and_then(|value| value.to_str().ok())
1521                        .and_then(|value| value.parse::<u64>().ok())
1522                        .unwrap_or(60);
1523
1524                    log::warn!(
1525                        "Request to '{}' responded 429, waiting {} seconds before retry...",
1526                        response.url(),
1527                        &retry_after
1528                    );
1529
1530                    Delay::new(Duration::from_secs(retry_after)).await;
1531                }
1532                _ => {
1533                    break Ok(response);
1534                }
1535            }
1536        }
1537    }
1538
1539    /// [internal-implementation]
1540    /// Actually perform a single-transaction verification
1541    #[allow(clippy::too_many_arguments)]
1542    async fn _full_verify<
1543        EmailAddress: ToString,
1544        PhoneNumber: ToString,
1545        AddressLine1: ToString,
1546        AddressLine2: ToString,
1547        CityName: ToString,
1548        StateNameOrAbbr: ToString,
1549        ZipCode: ToString,
1550    >(
1551        &self,
1552        email: Option<EmailAddress>,
1553        phone: Option<PhoneNumber>,
1554        address1: Option<AddressLine1>,
1555        address2: Option<AddressLine2>,
1556        city: Option<CityName>,
1557        state: Option<StateNameOrAbbr>,
1558        zip: Option<ZipCode>,
1559    ) -> Result<types::VerificationResponse, errors::BriteVerifyClientError> {
1560        let request = types::VerificationRequest::from_values(
1561            email, phone, address1, address2, city, state, zip,
1562        )?;
1563
1564        let url = self.v1_base_url.append_path("fullverify");
1565
1566        let response = self._build_and_send(self.post(url).json(&request)).await?;
1567
1568        match response.status() {
1569            StatusCode::OK => Ok(response.json::<types::VerificationResponse>().await?),
1570            _ => Err(errors::BriteVerifyClientError::UnusableResponse(Box::new(
1571                response,
1572            ))),
1573        }
1574    }
1575
1576    /// [internal-implementation]
1577    /// Actually fetch a given [`VerificationListState`](types::VerificationListState)
1578    #[cfg_attr(feature = "tracing", tracing::instrument)]
1579    async fn _get_list_state<ListId: ToString + Debug, ExternalId: std::fmt::Display + Debug>(
1580        &self,
1581        list_id: ListId,
1582        external_id: Option<ExternalId>,
1583    ) -> Result<types::VerificationListState, errors::BriteVerifyClientError> {
1584        let list_id = list_id.to_string();
1585        let url = external_id
1586            .map(|ext_id| {
1587                self.v3_base_url
1588                    .extend_path(["accounts".to_string(), ext_id.to_string()])
1589            })
1590            .as_ref()
1591            .unwrap_or(&self.v3_base_url)
1592            .extend_path(["lists", &list_id]);
1593
1594        let response = self._build_and_send(self.get(url)).await?;
1595
1596        match response.status() {
1597            StatusCode::OK => Ok(response.json::<types::VerificationListState>().await?),
1598            StatusCode::NOT_FOUND => Err(errors::BriteVerifyClientError::BulkListNotFound(
1599                Box::new(types::BulkListCRUDError {
1600                    list_id: Some(list_id),
1601                    ..response.json::<types::BulkListCRUDError>().await?
1602                }),
1603            )),
1604            _ => Err(errors::BriteVerifyClientError::UnusableResponse(Box::new(
1605                response,
1606            ))),
1607        }
1608    }
1609
1610    /// [internal-implementation]
1611    /// Retrieve the specified page of results from the specified
1612    /// bulk verification list
1613    #[cfg_attr(feature = "tracing", tracing::instrument)]
1614    async fn _get_result_page(
1615        &self,
1616        list_id: String,
1617        page_number: u64,
1618    ) -> Result<types::BulkVerificationResponse, errors::BriteVerifyClientError> {
1619        let page_url = self.v3_base_url.extend_path([
1620            "lists",
1621            &list_id,
1622            "export",
1623            page_number.to_string().as_str(),
1624        ]);
1625
1626        let response = self._build_and_send(self.get(page_url)).await?;
1627
1628        match response.status() {
1629            StatusCode::OK => Ok(response.json::<types::BulkVerificationResponse>().await?),
1630            _ => Err(errors::BriteVerifyClientError::UnusableResponse(Box::new(
1631                response,
1632            ))),
1633        }
1634    }
1635
1636    /// [internal-implementation]
1637    /// Create a new or mutate an extant bulk verification list
1638    #[cfg_attr(feature = "tracing", tracing::instrument)]
1639    async fn _create_or_update_list<
1640        ListId: ToString + Debug,
1641        Contact: Into<types::VerificationRequest> + Debug,
1642        Directive: Into<types::BulkListDirective> + Debug,
1643        ContactCollection: IntoIterator<Item = Contact> + Debug,
1644    >(
1645        &self,
1646        list_id: Option<ListId>,
1647        contacts: ContactCollection,
1648        directive: Directive,
1649    ) -> Result<types::CreateListResponse, errors::BriteVerifyClientError> {
1650        // TODO(the-wondersmith): Apply bulk "rate" limit to supplied contacts
1651        //                        Bulk rate limits are:
1652        //                          - 100k Emails per page
1653        //                          - 1M Email addresses per job (or 20 pages of 50k)
1654
1655        let directive = directive.into();
1656        let request = types::BulkVerificationRequest::new(contacts, directive);
1657
1658        let mut url = self.v3_base_url.append_path("lists");
1659
1660        if let Some(id) = list_id.as_ref() {
1661            url = url.append_path(id.to_string());
1662        }
1663
1664        let response = self._build_and_send(self.post(url).json(&request)).await?;
1665
1666        match response.status() {
1667            StatusCode::OK | StatusCode::CREATED => {
1668                Ok(response.json::<types::CreateListResponse>().await?)
1669            }
1670            StatusCode::NOT_FOUND | StatusCode::BAD_REQUEST => {
1671                Err(errors::BriteVerifyClientError::BulkListNotFound(Box::new(
1672                    types::BulkListCRUDError {
1673                        list_id: list_id.as_ref().map(|id| id.to_string()),
1674                        ..response.json::<types::BulkListCRUDError>().await?
1675                    },
1676                )))
1677            }
1678            _ => Err(errors::BriteVerifyClientError::UnusableResponse(Box::new(
1679                response,
1680            ))),
1681        }
1682    }
1683
1684    // </editor-fold desc="// Internal Utility Methods ... ">
1685
1686    // <editor-fold desc="// Real-Time Single Transaction Endpoints ... ">
1687
1688    /// Get your current account credit balance
1689    /// [[ref](https://docs.briteverify.com/#07beceb3-2961-4d5b-93a4-9cfeb30f42fa)]
1690    ///
1691    /// #### Example
1692    /// ```no_run
1693    /// # use briteverify_rs::BriteVerifyClient;
1694    /// #
1695    /// # async fn doc() -> anyhow::Result<()> {
1696    /// # let client: BriteVerifyClient = BriteVerifyClient::new("YOUR API KEY")?;
1697    /// let balance: u32 = client.current_credits().await?;
1698    ///
1699    /// println!("Current BriteVerify API credit balance: {balance}");
1700    /// # Ok(())
1701    /// # }
1702    /// ```
1703    #[cfg_attr(feature = "tracing", tracing::instrument)]
1704    pub async fn current_credits(&self) -> Result<u32> {
1705        Ok(self.get_account_balance().await?.credits)
1706    }
1707
1708    /// Get the total number of credits your account currently has in reserve
1709    /// [[ref](https://docs.briteverify.com/#07beceb3-2961-4d5b-93a4-9cfeb30f42fa)]
1710    ///
1711    /// #### Example
1712    /// ```no_run
1713    /// # use briteverify_rs::BriteVerifyClient;
1714    /// #
1715    /// # async fn doc() -> anyhow::Result<()> {
1716    /// # let client: BriteVerifyClient = BriteVerifyClient::new("YOUR API KEY")?;
1717    /// let reserved: u32 = client.current_credits_in_reserve().await?;
1718    ///
1719    /// println!("Current BriteVerify API reserve credit balance: {reserved}");
1720    /// # Ok(())
1721    /// # }
1722    /// ```
1723    #[cfg_attr(feature = "tracing", tracing::instrument)]
1724    pub async fn current_credits_in_reserve(&self) -> Result<u32> {
1725        Ok(self.get_account_balance().await?.credits_in_reserve)
1726    }
1727
1728    /// Get your account credit balance, total number of credits
1729    /// in reserve, and the timestamp of when your balance was
1730    /// most recently recorded
1731    /// [[ref](https://docs.briteverify.com/#07beceb3-2961-4d5b-93a4-9cfeb30f42fa)]
1732    ///
1733    /// #### Example
1734    /// ```no_run
1735    /// # use briteverify_rs::{BriteVerifyClient, types::AccountCreditBalance};
1736    /// #
1737    /// # async fn doc() -> anyhow::Result<()> {
1738    /// # let client: BriteVerifyClient = BriteVerifyClient::new("YOUR API KEY")?;
1739    /// let balance_report: AccountCreditBalance = client.get_account_balance().await?;
1740    ///
1741    /// println!("Current BriteVerify API credit data: {balance_report}");
1742    /// # Ok(())
1743    /// # }
1744    /// ```
1745    #[cfg_attr(feature = "tracing", tracing::instrument)]
1746    pub async fn get_account_balance(
1747        &self,
1748    ) -> Result<types::AccountCreditBalance, errors::BriteVerifyClientError> {
1749        let url = format!("{}/accounts/credits", &self.v3_base_url);
1750        let response = self._build_and_send(self.get(url)).await?;
1751
1752        match response.status() {
1753            StatusCode::OK => Ok(response.json::<types::AccountCreditBalance>().await?),
1754            _ => Err(errors::BriteVerifyClientError::UnusableResponse(Box::new(
1755                response,
1756            ))),
1757        }
1758    }
1759
1760    /// Verify a "complete" contact record
1761    /// [[ref](https://docs.briteverify.com/#a7246384-e91e-48a9-8aed-7b71cb74dd42)]
1762    ///
1763    /// #### Example
1764    /// ```no_run
1765    /// # use briteverify_rs::{BriteVerifyClient, types::VerificationResponse};
1766    /// #
1767    /// # async fn doc() -> anyhow::Result<()> {
1768    /// # let client: BriteVerifyClient = BriteVerifyClient::new("YOUR API KEY")?;
1769    /// let verified: VerificationResponse = client.verify_contact(
1770    ///     "test@example.com",
1771    ///     "+15555555555",
1772    ///     "123 Main St",
1773    ///     Some("P.O. Box 456"),
1774    ///     "Any Town",
1775    ///     "CA",
1776    ///     "90210",
1777    /// ).await?;
1778    ///
1779    /// println!("Verified contact data: {verified:#?}");
1780    /// # Ok(())
1781    /// # }
1782    /// ```
1783    #[allow(clippy::too_many_arguments)]
1784    #[cfg_attr(feature = "tracing", tracing::instrument)]
1785    pub async fn verify_contact<
1786        EmailAddress: ToString + Debug,
1787        PhoneNumber: ToString + Debug,
1788        AddressLine1: ToString + Debug,
1789        AddressLine2: ToString + Debug,
1790        CityName: ToString + Debug,
1791        StateNameOrAbbr: ToString + Debug,
1792        ZipCode: ToString + Debug,
1793    >(
1794        &self,
1795        email: EmailAddress,
1796        phone: PhoneNumber,
1797        address1: AddressLine1,
1798        address2: Option<AddressLine2>,
1799        city: CityName,
1800        state: StateNameOrAbbr,
1801        zip: ZipCode,
1802    ) -> Result<types::VerificationResponse, errors::BriteVerifyClientError> {
1803        let response = self
1804            ._full_verify(
1805                Some(email),
1806                Some(phone),
1807                Some(address1),
1808                address2,
1809                Some(city),
1810                Some(state),
1811                Some(zip),
1812            )
1813            .await;
1814
1815        match response {
1816            Ok(data) => Ok(data),
1817            Err(error) => Err(error),
1818        }
1819    }
1820
1821    /// Verify a single email address
1822    /// [[ref](https://docs.briteverify.com/#e5dd413c-6411-4078-8b4c-0e787f6a9325)]
1823    ///
1824    /// #### Example
1825    /// ```no_run
1826    /// # use briteverify_rs::{BriteVerifyClient, types::EmailVerificationArray};
1827    /// #
1828    /// # async fn doc() -> anyhow::Result<()> {
1829    /// # let client: BriteVerifyClient = BriteVerifyClient::new("YOUR API KEY")?;
1830    /// let response: EmailVerificationArray = client.verify_email("test@example.com").await?;
1831    ///
1832    /// println!("Verified email: {response:#?}");
1833    /// # Ok(())
1834    /// # }
1835    /// ```
1836    #[cfg_attr(feature = "tracing", tracing::instrument)]
1837    pub async fn verify_email<EmailAddress: ToString + Debug>(
1838        &self,
1839        email: EmailAddress,
1840    ) -> Result<types::EmailVerificationArray, errors::BriteVerifyClientError> {
1841        let response = self
1842            ._full_verify(
1843                Some(email),
1844                Nullable::None,
1845                Nullable::None,
1846                Nullable::None,
1847                Nullable::None,
1848                Nullable::None,
1849                Nullable::None,
1850            )
1851            .await?;
1852
1853        match response.email {
1854            Some(data) => Ok(data),
1855            None => Err(
1856                errors::BriteVerifyClientError::MismatchedVerificationResponse(Box::new(response)),
1857            ),
1858        }
1859    }
1860
1861    /// Verify a single phone number
1862    /// [[ref](https://docs.briteverify.com/#86e335f4-d1b2-4902-9051-4506a48a6b94)]
1863    ///
1864    /// #### Example
1865    /// ```no_run
1866    /// # use briteverify_rs::{BriteVerifyClient, types::PhoneNumberVerificationArray};
1867    /// #
1868    /// # async fn doc() -> anyhow::Result<()> {
1869    /// # let client: BriteVerifyClient = BriteVerifyClient::new("YOUR API KEY")?;
1870    /// let response: PhoneNumberVerificationArray = client.verify_phone_number("+15555555555").await?;
1871    ///
1872    /// println!("Verified phone number: {response:#?}");
1873    /// # Ok(())
1874    /// # }
1875    /// ```
1876    #[cfg_attr(feature = "tracing", tracing::instrument)]
1877    pub async fn verify_phone_number<PhoneNumber: ToString + Debug>(
1878        &self,
1879        phone: PhoneNumber,
1880    ) -> Result<types::PhoneNumberVerificationArray, errors::BriteVerifyClientError> {
1881        let response = self
1882            ._full_verify(
1883                Nullable::None,
1884                Some(phone),
1885                Nullable::None,
1886                Nullable::None,
1887                Nullable::None,
1888                Nullable::None,
1889                Nullable::None,
1890            )
1891            .await?;
1892
1893        match response.phone {
1894            Some(data) => Ok(data),
1895            None => Err(
1896                errors::BriteVerifyClientError::MismatchedVerificationResponse(Box::new(response)),
1897            ),
1898        }
1899    }
1900
1901    /// Verify a single street address
1902    /// [[ref](https://docs.briteverify.com/#f588d8d3-8250-4a8a-9e58-f89c81af6bed)]
1903    ///
1904    /// #### Example
1905    /// ```no_run
1906    /// # use briteverify_rs::{BriteVerifyClient, types::AddressVerificationArray};
1907    /// #
1908    /// # async fn doc() -> anyhow::Result<()> {
1909    /// # let client: BriteVerifyClient = BriteVerifyClient::new("YOUR API KEY")?;
1910    /// let verified: AddressVerificationArray = client.verify_street_address(
1911    ///     "123 Main St",
1912    ///     Some("P.O. Box 456"),
1913    ///     "Any Town",
1914    ///     "CA",
1915    ///     "90210",
1916    /// ).await?;
1917    ///
1918    /// println!("Verified address: {verified:#?}");
1919    /// # Ok(())
1920    /// # }
1921    /// ```
1922    #[cfg_attr(feature = "tracing", tracing::instrument)]
1923    pub async fn verify_street_address<
1924        AddressLine1: ToString + Debug,
1925        AddressLine2: ToString + Debug,
1926        CityName: ToString + Debug,
1927        StateNameOrAbbr: ToString + Debug,
1928        ZipCode: ToString + Debug,
1929    >(
1930        &self,
1931        address1: AddressLine1,
1932        address2: Option<AddressLine2>,
1933        city: CityName,
1934        state: StateNameOrAbbr,
1935        zip: ZipCode,
1936    ) -> Result<types::AddressVerificationArray, errors::BriteVerifyClientError> {
1937        let response = self
1938            ._full_verify(
1939                Nullable::None,
1940                Nullable::None,
1941                Some(address1),
1942                address2,
1943                Some(city),
1944                Some(state),
1945                Some(zip),
1946            )
1947            .await?;
1948
1949        match response.address {
1950            Some(data) => Ok(data),
1951            None => Err(
1952                errors::BriteVerifyClientError::MismatchedVerificationResponse(Box::new(response)),
1953            ),
1954        }
1955    }
1956
1957    // </editor-fold desc="// Real-Time Single Transaction Endpoints ... ">
1958
1959    // <editor-fold desc="// Bulk Verification (v3) Endpoints ... ">
1960
1961    /// Retrieve the complete, unfiltered list of all bulk verification
1962    /// lists created within the last 7 calendar days
1963    /// [[ref](https://docs.briteverify.com/#0b5a2a7a-4062-4327-ab0a-4675592e3cd6)]
1964    ///
1965    /// #### Example
1966    /// ```no_run
1967    /// # use briteverify_rs::{BriteVerifyClient, types::GetListStatesResponse};
1968    /// #
1969    /// # async fn doc() -> anyhow::Result<()> {
1970    /// # let client: BriteVerifyClient = BriteVerifyClient::new("YOUR API KEY")?;
1971    /// let lists: GetListStatesResponse = client.get_lists().await?;
1972    ///
1973    /// println!("Available bulk verification lists: {lists:#?}");
1974    /// # Ok(())
1975    /// # }
1976    /// ```
1977    #[cfg_attr(feature = "tracing", tracing::instrument)]
1978    pub async fn get_lists(
1979        &self,
1980    ) -> Result<types::GetListStatesResponse, errors::BriteVerifyClientError> {
1981        self.get_filtered_lists(
1982            <Option<u32>>::None,
1983            <Option<chrono::NaiveDate>>::None,
1984            <Option<types::BatchState>>::None,
1985            Nullable::None,
1986        )
1987        .await
1988    }
1989
1990    /// Retrieve the complete list of all bulk verification lists created
1991    /// within the last 7 calendar days filtered by the specified criteria
1992    /// [[ref](https://docs.briteverify.com/#0b5a2a7a-4062-4327-ab0a-4675592e3cd6)]
1993    ///
1994    /// #### Example
1995    /// ```no_run
1996    /// # use chrono::Datelike;
1997    /// use chrono::{NaiveDate, Utc};
1998    /// use briteverify_rs::{BriteVerifyClient, types::GetListStatesResponse};
1999    /// #
2000    /// # async fn doc() -> anyhow::Result<()> {
2001    /// # let client: BriteVerifyClient = BriteVerifyClient::new("YOUR API KEY")?;
2002    ///
2003    /// let today: NaiveDate = Utc::now().date_naive();
2004    ///
2005    /// let page: Option<u32> = Some(5u32);
2006    /// let state: Option<&str> = Some("open");
2007    /// let date: Option<NaiveDate> = today.with_day(today.day() - 2);
2008    /// let ext_id: Option<&str> = None;
2009    ///
2010    /// let lists: GetListStatesResponse = client.get_filtered_lists(page, date, state, ext_id).await?;
2011    ///
2012    /// println!("Filtered bulk verification lists: {lists:#?}");
2013    /// # Ok(())
2014    /// # }
2015    /// ```
2016    #[cfg_attr(feature = "tracing", tracing::instrument)]
2017    pub async fn get_filtered_lists<
2018        'header,
2019        Date: chrono::Datelike + Debug,
2020        Page: Into<u32> + Debug,
2021        State: Clone + Debug + Into<types::BatchState>,
2022        ExternalId: std::fmt::Display + Debug,
2023    >(
2024        &self,
2025        page: Option<Page>,
2026        date: Option<Date>,
2027        state: Option<State>,
2028        ext_id: Option<ExternalId>,
2029    ) -> Result<types::GetListStatesResponse, errors::BriteVerifyClientError> {
2030        let mut params: Vec<(&'header str, String)> = Vec::new();
2031
2032        if let Some(page) = page {
2033            params.push(("page", page.into().to_string()));
2034        }
2035
2036        if let Some(date) = date {
2037            params.push((
2038                "date",
2039                format!("{}-{:0>2}-{:0>2}", date.year(), date.month(), date.day()),
2040            ));
2041        }
2042
2043        if let Some(state) = state {
2044            let filter = state.clone().into();
2045
2046            if matches!(filter, types::BatchState::Unknown) {
2047                log::warn!("Declining to include unknown list state as request filter: {state:#?}");
2048            } else {
2049                params.push(("state", filter.to_string()));
2050            }
2051        }
2052
2053        let url = ext_id
2054            .map(|id| {
2055                self.v3_base_url
2056                    .extend_path(["accounts".to_string(), id.to_string()])
2057            })
2058            .as_ref()
2059            .unwrap_or(&self.v3_base_url)
2060            .append_path("lists");
2061
2062        let mut request = self.get(url);
2063
2064        if !params.is_empty() {
2065            request = request.query(&params);
2066        }
2067
2068        let response = self._build_and_send(request).await?;
2069
2070        match response.status() {
2071            StatusCode::OK => Ok(response.json::<types::GetListStatesResponse>().await?),
2072            _ => Err(errors::BriteVerifyClientError::UnusableResponse(Box::new(
2073                response,
2074            ))),
2075        }
2076    }
2077
2078    /// Retrieve the complete list of all bulk verification lists filtered
2079    /// by the specified date [[ref](https://docs.briteverify.com/#0b5a2a7a-4062-4327-ab0a-4675592e3cd6)]
2080    ///
2081    /// ___
2082    /// **NOTE:** Regardless of specified date, the BriteVerify API
2083    /// does not appear to persist bulk verification lists older than
2084    /// 7 calendar days
2085    /// ___
2086    ///
2087    /// #### Example
2088    /// ```no_run
2089    /// # use chrono::Datelike;
2090    /// use chrono::{NaiveDate, Utc};
2091    /// use briteverify_rs::{BriteVerifyClient, types::GetListStatesResponse};
2092    /// #
2093    /// # async fn doc() -> anyhow::Result<()> {
2094    /// # let client: BriteVerifyClient = BriteVerifyClient::new("YOUR API KEY")?;
2095    ///
2096    /// let today: NaiveDate = Utc::now().date_naive();
2097    /// let date: NaiveDate = today.with_day(today.day() - 2).unwrap();
2098    ///
2099    /// let lists: GetListStatesResponse = client.get_lists_by_date(date.clone()).await?;
2100    ///
2101    /// println!("Bulk verification lists for '{date}': {lists:#?}");
2102    /// # Ok(())
2103    /// # }
2104    /// ```
2105    #[cfg_attr(feature = "tracing", tracing::instrument)]
2106    pub async fn get_lists_by_date<Date: chrono::Datelike + Debug>(
2107        &self,
2108        date: Date,
2109    ) -> Result<types::GetListStatesResponse, errors::BriteVerifyClientError> {
2110        self.get_filtered_lists(
2111            <Option<u32>>::None,
2112            Some(date),
2113            <Option<types::BatchState>>::None,
2114            Nullable::None,
2115        )
2116        .await
2117    }
2118
2119    /// Retrieve the specified "page" of bulk verification lists
2120    /// [[ref](https://docs.briteverify.com/#0b5a2a7a-4062-4327-ab0a-4675592e3cd6)]
2121    ///
2122    /// #### Example
2123    /// ```no_run
2124    /// # use chrono::Datelike;
2125    /// use briteverify_rs::{BriteVerifyClient, types::GetListStatesResponse};
2126    /// #
2127    /// # async fn doc() -> anyhow::Result<()> {
2128    /// # let client: BriteVerifyClient = BriteVerifyClient::new("YOUR API KEY")?;
2129    ///
2130    /// let page: u32 = 2;
2131    /// let lists: GetListStatesResponse = client.get_lists_by_page(page).await?;
2132    ///
2133    /// println!("Bulk verification lists page {page}: {lists:#?}");
2134    /// # Ok(())
2135    /// # }
2136    /// ```
2137    #[cfg_attr(feature = "tracing", tracing::instrument)]
2138    pub async fn get_lists_by_page<Page: Into<u32> + Debug>(
2139        &self,
2140        page: Page,
2141    ) -> Result<types::GetListStatesResponse, errors::BriteVerifyClientError> {
2142        self.get_filtered_lists(
2143            Some(page),
2144            <Option<chrono::NaiveDate>>::None,
2145            <Option<types::BatchState>>::None,
2146            Nullable::None,
2147        )
2148        .await
2149    }
2150
2151    /// Retrieve the complete list of all bulk verification lists created
2152    /// within the last 7 calendar days whose status matches the specified
2153    /// value
2154    /// [[ref](https://docs.briteverify.com/#0b5a2a7a-4062-4327-ab0a-4675592e3cd6)]
2155    ///
2156    /// #### Example
2157    /// ```no_run
2158    /// # use chrono::Datelike;
2159    /// use briteverify_rs::{BriteVerifyClient, types::{BatchState, GetListStatesResponse}};
2160    /// #
2161    /// # async fn doc() -> anyhow::Result<()> {
2162    /// # let client: BriteVerifyClient = BriteVerifyClient::new("YOUR API KEY")?;
2163    ///
2164    /// let state: BatchState = BatchState::Closed;
2165    /// let lists: GetListStatesResponse = client.get_lists_by_state(state).await?;
2166    ///
2167    /// println!("Bulk verification lists w/ state '{state}': {lists:#?}");
2168    /// # Ok(())
2169    /// # }
2170    /// ```
2171    #[cfg_attr(feature = "tracing", tracing::instrument)]
2172    pub async fn get_lists_by_state(
2173        &self,
2174        state: types::BatchState,
2175    ) -> Result<types::GetListStatesResponse, errors::BriteVerifyClientError> {
2176        if !state.is_unknown() {
2177            self.get_filtered_lists(
2178                <Option<u32>>::None,
2179                <Option<chrono::NaiveDate>>::None,
2180                Some(state),
2181                Nullable::None,
2182            )
2183            .await
2184        } else {
2185            let message = "to request lists using 'unknown' as list state filter";
2186
2187            log::warn!("Declining {message}");
2188
2189            Ok(types::GetListStatesResponse {
2190                message: Some(format!("Declined {message}")),
2191                lists: Vec::new(),
2192            })
2193        }
2194    }
2195
2196    /// Create a new bulk verification list with the supplied records
2197    /// and (optionally) queue it for immediate processing
2198    /// [[ref](https://docs.briteverify.com/#38b4c9eb-31b1-4b8e-9295-a783d8043bc1)]
2199    ///
2200    /// #### Examples
2201    ///
2202    /// ##### Create Empty List
2203    /// ```no_run
2204    /// # use briteverify_rs::BriteVerifyClient;
2205    /// use briteverify_rs::types::{CreateListResponse, VerificationRequest};
2206    /// #
2207    /// # async fn doc() -> anyhow::Result<()> {
2208    /// # let client: BriteVerifyClient = BriteVerifyClient::new("YOUR API KEY")?;
2209    ///
2210    /// let contacts = <Option<Vec<VerificationRequest>>>::None;
2211    /// let list: CreateListResponse = client.create_list(contacts, false).await?;
2212    ///
2213    /// println!("New bulk verification list: {list:#?}");
2214    /// # Ok(())
2215    /// # }
2216    /// ```
2217    ///
2218    /// ##### Create Populated List & Start Immediately
2219    /// ```no_run
2220    /// use briteverify_rs::{
2221    /// #    BriteVerifyClient,
2222    ///     types::{
2223    ///       CreateListResponse,
2224    ///       VerificationRequest,
2225    ///     },
2226    /// };
2227    /// # async fn doc() -> anyhow::Result<()> {
2228    /// # let client: BriteVerifyClient = BriteVerifyClient::new("YOUR API KEY")?;
2229    ///
2230    /// let contacts: [VerificationRequest; 2] = [
2231    ///     VerificationRequest::try_from("test@example.com")?,
2232    ///     VerificationRequest::try_from("+15555555555")?
2233    /// ];
2234    ///
2235    /// let list: CreateListResponse = client.create_list(Some(contacts), true).await?;
2236    ///
2237    /// println!("New bulk verification list: {list:#?}");
2238    /// # Ok(())
2239    /// # }
2240    /// ```
2241    #[cfg_attr(feature = "tracing", tracing::instrument)]
2242    pub async fn create_list<
2243        Contact: Into<types::VerificationRequest> + Debug,
2244        ContactCollection: IntoIterator<Item = Contact> + Debug,
2245    >(
2246        &self,
2247        contacts: Option<ContactCollection>,
2248        auto_start: bool,
2249    ) -> Result<types::CreateListResponse, errors::BriteVerifyClientError> {
2250        // TODO(the-wondersmith): Apply bulk "rate" limit to supplied contacts
2251        //                        Bulk rate limits are:
2252        //                          - 100k Emails per page
2253        //                          - 1M Email addresses per job (or 20 pages of 50k)
2254
2255        if let Some(data) = contacts {
2256            self._create_or_update_list(
2257                Nullable::None, // no explicit list id
2258                data,           // supplied contacts
2259                auto_start,     // untouched auto-start value
2260            )
2261            .await
2262        } else {
2263            self._create_or_update_list(
2264                Nullable::None,                           // no explicit list id
2265                Vec::<types::VerificationRequest>::new(), // no contacts
2266                false, // without contacts, we can't auto-start no matter what
2267            )
2268            .await
2269        }
2270    }
2271
2272    /// Append records to the specified bulk verification list and (optionally)
2273    /// queue it for immediate processing
2274    /// [[ref](https://docs.briteverify.com/#38b4c9eb-31b1-4b8e-9295-a783d8043bc1:~:text=customer%2DID/lists-,list_id,-(optional))]
2275    ///
2276    /// #### Example
2277    /// ```no_run
2278    /// use briteverify_rs::{
2279    /// #    BriteVerifyClient,
2280    ///     types::{
2281    ///       UpdateListResponse,
2282    ///       VerificationRequest,
2283    ///     },
2284    /// };
2285    /// # async fn doc() -> anyhow::Result<()> {
2286    /// # let client: BriteVerifyClient = BriteVerifyClient::new("YOUR API KEY")?;
2287    ///
2288    /// let contacts: [VerificationRequest; 2] = [
2289    ///     VerificationRequest::try_from("some-email@bounce-me.net")?,
2290    ///     VerificationRequest::try_from("another-email@a-real-domain.org")?,
2291    /// ];
2292    ///
2293    /// let list: UpdateListResponse = client.update_list("some-list-id", contacts, false).await?;
2294    ///
2295    /// println!("Updated bulk verification list: {list:#?}");
2296    /// # Ok(())
2297    /// # }
2298    /// ```
2299    #[cfg_attr(feature = "tracing", tracing::instrument)]
2300    pub async fn update_list<
2301        ListId: ToString + Debug,
2302        Contact: Into<types::VerificationRequest> + Debug,
2303        ContactCollection: IntoIterator<Item = Contact> + Debug,
2304    >(
2305        &self,
2306        list_id: ListId,
2307        contacts: ContactCollection,
2308        auto_start: bool,
2309    ) -> Result<types::UpdateListResponse, errors::BriteVerifyClientError> {
2310        // TODO(the-wondersmith): Apply bulk "rate" limit to supplied contacts
2311        //                        Bulk rate limits are:
2312        //                          - 100k Emails per page
2313        //                          - 1M Email addresses per job (or 20 pages of 50k)
2314        self._create_or_update_list(Some(list_id), contacts, auto_start)
2315            .await
2316    }
2317
2318    /// Retrieve current "state" of the specified bulk verification list
2319    /// [[ref](https://docs.briteverify.com/#b09c09dc-e11e-44a8-b53d-9f1fd9c6792d)]
2320    ///
2321    /// #### Example
2322    /// ```no_run
2323    /// # use briteverify_rs::BriteVerifyClient;
2324    /// use briteverify_rs::types::VerificationListState;
2325    /// #
2326    /// # async fn doc() -> anyhow::Result<()> {
2327    /// # let client: BriteVerifyClient = BriteVerifyClient::new("YOUR API KEY")?;    ///
2328    ///
2329    /// let list_id: &str = "some-list-id";
2330    /// let list: VerificationListState = client.get_list_by_id(list_id).await?;
2331    ///
2332    /// println!("Bulk verification list '{list_id}': {list:#?}");
2333    /// # Ok(())
2334    /// # }
2335    /// ```
2336    #[cfg_attr(feature = "tracing", tracing::instrument)]
2337    pub async fn get_list_by_id<ListId: ToString + Debug>(
2338        &self,
2339        list_id: ListId,
2340    ) -> Result<types::VerificationListState, errors::BriteVerifyClientError> {
2341        self._get_list_state(list_id, Nullable::None).await
2342    }
2343
2344    /// Retrieve current "state" of a bulk verification list tied to an
2345    /// externally supplied / customer-specific identifier
2346    /// [[ref](https://docs.briteverify.com/#b09c09dc-e11e-44a8-b53d-9f1fd9c6792d)]
2347    ///
2348    /// #### Example
2349    /// ```no_run
2350    /// # use briteverify_rs::BriteVerifyClient;
2351    /// use briteverify_rs::types::VerificationListState;
2352    /// #
2353    /// # async fn doc() -> anyhow::Result<()> {
2354    /// # let client: BriteVerifyClient = BriteVerifyClient::new("YOUR API KEY")?;
2355    ///
2356    /// let list_id: &str = "some-list-id";
2357    /// let customer_id: &str = "some-customer-id";
2358    /// let list: VerificationListState = client.get_list_by_external_id(list_id, customer_id).await?;
2359    ///
2360    /// println!("Bulk verification list '{list_id}': {list:#?}");
2361    /// # Ok(())
2362    /// # }
2363    /// ```
2364    #[cfg_attr(feature = "tracing", tracing::instrument)]
2365    pub async fn get_list_by_external_id<
2366        ListId: ToString + Debug,
2367        ExternalId: std::fmt::Display + Debug,
2368    >(
2369        &self,
2370        list_id: ListId,
2371        external_id: ExternalId,
2372    ) -> Result<types::VerificationListState, errors::BriteVerifyClientError> {
2373        self._get_list_state(list_id, Some(external_id)).await
2374    }
2375
2376    /// Delete the specified batch verification list
2377    /// [[ref](https://docs.briteverify.com/#6c9b9c05-a4a0-435e-a064-af7d9476719d)]
2378    ///
2379    /// ___
2380    /// **NOTE:** This action *cannot* be reversed and
2381    /// once completed, the list will *no longer exist*.
2382    /// The list must be in one of the following states
2383    /// to be deleted:
2384    /// - [`BatchState::Prepped`](types::enums::BatchState::Prepped)
2385    /// - [`BatchState::Complete`](types::enums::BatchState::Complete)
2386    /// - [`BatchState::Delivered`](types::enums::BatchState::Delivered)
2387    /// - [`BatchState::ImportError`](types::enums::BatchState::ImportError)
2388    /// ___
2389    ///
2390    /// #### Example
2391    /// ```no_run
2392    /// # use briteverify_rs::BriteVerifyClient;
2393    /// use briteverify_rs::types::DeleteListResponse;
2394    /// #
2395    /// # async fn doc() -> anyhow::Result<()> {
2396    /// # let client: BriteVerifyClient = BriteVerifyClient::new("YOUR API KEY")?;
2397    ///
2398    /// let list_id: &str = "some-list-id";
2399    /// let response: DeleteListResponse = client.delete_list_by_id(list_id).await?;
2400    ///
2401    /// println!("Bulk verification list '{list_id}' final state: {response:#?}");
2402    /// # Ok(())
2403    /// # }
2404    /// ```
2405    #[cfg_attr(feature = "tracing", tracing::instrument)]
2406    pub async fn delete_list_by_id<ListId: ToString + Debug>(
2407        &self,
2408        list_id: ListId,
2409    ) -> Result<types::DeleteListResponse, errors::BriteVerifyClientError> {
2410        let list_id: String = list_id.to_string();
2411        let url = self.v3_base_url.extend_path(["lists", &list_id]);
2412
2413        let response = self.delete(url).send().await?;
2414
2415        match response.status() {
2416            StatusCode::OK | StatusCode::ACCEPTED | StatusCode::NO_CONTENT => {
2417                Ok(response.json::<types::DeleteListResponse>().await?)
2418            }
2419            StatusCode::NOT_FOUND => Err(errors::BriteVerifyClientError::BulkListNotFound(
2420                Box::new(types::BulkListCRUDError {
2421                    list_id: Some(list_id),
2422                    ..response.json::<types::BulkListCRUDError>().await?
2423                }),
2424            )),
2425            _ => Err(errors::BriteVerifyClientError::UnusableResponse(Box::new(
2426                response,
2427            ))),
2428        }
2429    }
2430
2431    /// Abandon the specified unprocessed bulk verification list
2432    /// [[ref](https://docs.briteverify.com/#6c9b9c05-a4a0-435e-a064-af7d9476719d:~:text=To-,abandon,-an%20open%20list)]
2433    ///
2434    /// ___
2435    /// **NOTE:** This action is only applicable to lists
2436    /// that have *not yet* begun processing. For any list
2437    /// that has already been "started", the equivalent
2438    /// action would be [`delete_list_by_id`].
2439    /// ___
2440    ///
2441    /// [`delete_list_by_id`]: #delete_list_by_id
2442    ///
2443    /// #### Example
2444    /// ```no_run
2445    /// # use briteverify_rs::BriteVerifyClient;
2446    /// use briteverify_rs::types::UpdateListResponse;
2447    /// #
2448    /// # async fn doc() -> anyhow::Result<()> {
2449    /// # let client: BriteVerifyClient = BriteVerifyClient::new("YOUR API KEY")?;
2450    ///
2451    /// let list_id: &str = "some-list-id";
2452    /// let response: UpdateListResponse = client.terminate_list_by_id(list_id).await?;
2453    ///
2454    /// println!("Bulk verification list '{list_id}' final state: {response:#?}");
2455    /// # Ok(())
2456    /// # }
2457    /// ```
2458    #[cfg_attr(feature = "tracing", tracing::instrument)]
2459    pub async fn terminate_list_by_id<ListId: ToString + Debug>(
2460        &self,
2461        list_id: ListId,
2462    ) -> Result<types::UpdateListResponse, errors::BriteVerifyClientError> {
2463        self._create_or_update_list(
2464            Some(list_id),
2465            <Vec<types::VerificationRequest>>::new(),
2466            types::BulkListDirective::Terminate,
2467        )
2468        .await
2469    }
2470
2471    /// Queue the specified (open) bulk verification list for immediate processing
2472    /// [[ref](https://docs.briteverify.com/#0a0cc29d-6d9f-4b0d-9aa5-4166775a8831:~:text=immediately%20start%20a%20list)]
2473    ///
2474    /// #### Example
2475    /// ```no_run
2476    /// # use briteverify_rs::BriteVerifyClient;
2477    /// use briteverify_rs::types::UpdateListResponse;
2478    /// #
2479    /// # async fn doc() -> anyhow::Result<()> {
2480    /// # let client: BriteVerifyClient = BriteVerifyClient::new("YOUR API KEY")?;
2481    ///
2482    /// let list_id: &str = "some-list-id";
2483    /// let response: UpdateListResponse = client.queue_list_for_processing(list_id).await?;
2484    ///
2485    /// println!("Bulk verification list '{list_id}' state: {response:#?}");
2486    /// # Ok(())
2487    /// # }
2488    /// ```
2489    #[cfg_attr(feature = "tracing", tracing::instrument)]
2490    pub async fn queue_list_for_processing<ListId: ToString + Debug>(
2491        &self,
2492        list_id: ListId,
2493    ) -> Result<types::UpdateListResponse, errors::BriteVerifyClientError> {
2494        self._create_or_update_list(
2495            Some(list_id),
2496            <Vec<types::VerificationRequest>>::new(),
2497            types::BulkListDirective::Start,
2498        )
2499        .await
2500    }
2501
2502    /// Get the verification results for the specified bulk verification list
2503    /// [[ref](https://docs.briteverify.com/#0a0cc29d-6d9f-4b0d-9aa5-4166775a8831)]
2504    ///
2505    /// ___
2506    /// **NOTE:** Verification results are only available once
2507    /// a list has finished verifying in its entirety. It is not
2508    /// possible to retrieve verification results piecemeal.
2509    /// ___
2510    ///
2511    /// #### Example
2512    /// ```no_run
2513    /// # use briteverify_rs::BriteVerifyClient;
2514    /// use briteverify_rs::types::BulkVerificationResult;
2515    /// #
2516    /// # async fn doc() -> anyhow::Result<()> {
2517    /// # let client: BriteVerifyClient = BriteVerifyClient::new("YOUR API KEY")?;
2518    ///
2519    /// let list_id: &str = "some-list-id";
2520    /// let data: Vec<BulkVerificationResult> = client.get_results_by_list_id(list_id).await?;
2521    ///
2522    /// println!("Bulk verification list '{list_id}' results: {data:#?}");
2523    /// # Ok(())
2524    /// # }
2525    /// ```
2526    #[cfg_attr(feature = "tracing", tracing::instrument)]
2527    pub async fn get_results_by_list_id<ListId: ToString + Debug>(
2528        &self,
2529        list_id: ListId,
2530    ) -> Result<Vec<types::BulkVerificationResult>, errors::BriteVerifyClientError> {
2531        let list_id = list_id.to_string();
2532        let list_status = self.get_list_by_id(&list_id).await?;
2533
2534        if list_status.page_count.is_none() {
2535            return Err(errors::BriteVerifyClientError::Other(anyhow::Error::msg(
2536                "Missing page count!",
2537            )));
2538        }
2539
2540        let page_count = std::cmp::max(1u64, list_status.page_count.unwrap());
2541
2542        let pages: Vec<_> = futures_util::future::join_all(
2543            (1..=page_count).map(|page_number| self._get_result_page(list_id.clone(), page_number)),
2544        )
2545        .await
2546        .into_iter()
2547        .filter(|page| {
2548            if let Err(error) = page {
2549                log::error!("{error:#?}");
2550                false
2551            } else {
2552                true
2553            }
2554        })
2555        .map(|task_result| task_result.unwrap().results)
2556        .collect();
2557
2558        let results: Vec<types::BulkVerificationResult> = itertools::concat(pages);
2559
2560        Ok(results)
2561    }
2562
2563    // </editor-fold desc="// Bulk Verification (v3) Endpoints ... ">
2564}
2565
2566// </editor-fold desc="// Client ...">
2567
2568// <editor-fold desc="// Test Utilities ...">
2569
2570#[cfg(any(test, tarpaulin, feature = "ci"))]
2571impl BriteVerifyClientBuilder {
2572    #[doc(hidden)]
2573    /// Get the current `v1_base_url` value
2574    #[cfg_attr(tarpaulin, coverage(off))]
2575    pub fn v1_url_mut(&mut self) -> &mut url::Url {
2576        &mut self.v1_base_url
2577    }
2578
2579    #[doc(hidden)]
2580    /// Get the current `v3_base_url` value
2581    #[cfg_attr(tarpaulin, coverage(off))]
2582    pub fn v3_url_mut(&mut self) -> &mut url::Url {
2583        &mut self.v3_base_url
2584    }
2585
2586    #[doc(hidden)]
2587    /// Get host portion of the current `v1_base_url` value
2588    #[cfg_attr(tarpaulin, coverage(off))]
2589    pub fn v1_url_host(&self) -> String {
2590        self.v1_base_url.host_str().unwrap().to_string()
2591    }
2592
2593    #[doc(hidden)]
2594    /// Get host portion of the current `v3_base_url` value
2595    #[cfg_attr(tarpaulin, coverage(off))]
2596    pub fn v3_url_host(&self) -> String {
2597        self.v3_base_url.host_str().unwrap().to_string()
2598    }
2599
2600    #[doc(hidden)]
2601    /// Set the target port of the current `v1_base_url`
2602    #[cfg_attr(tarpaulin, coverage(off))]
2603    pub fn set_v1_url_port(mut self, port: u16) -> Self {
2604        self.v1_url_mut().set_port(Some(port)).unwrap_or(());
2605        self
2606    }
2607
2608    #[doc(hidden)]
2609    /// Set the target port of the current `v3_base_url`
2610    #[cfg_attr(tarpaulin, coverage(off))]
2611    pub fn set_v3_url_port(mut self, port: u16) -> Self {
2612        self.v3_url_mut().set_port(Some(port)).unwrap_or(());
2613        self
2614    }
2615
2616    #[doc(hidden)]
2617    /// Set the scheme of the current `v1_base_url`
2618    #[cfg_attr(tarpaulin, coverage(off))]
2619    pub fn set_v1_url_scheme(mut self, scheme: http::uri::Scheme) -> Self {
2620        self.v1_url_mut().set_scheme(scheme.as_str()).unwrap_or(());
2621        self
2622    }
2623
2624    #[doc(hidden)]
2625    /// Set the scheme of the current `v3_base_url`
2626    #[cfg_attr(tarpaulin, coverage(off))]
2627    pub fn set_v3_url_scheme(mut self, scheme: http::uri::Scheme) -> Self {
2628        self.v3_url_mut().set_scheme(scheme.as_str()).unwrap_or(());
2629        self
2630    }
2631
2632    #[doc(hidden)]
2633    /// Force DNS resolution for the current `v1_base_url` to the IP address
2634    #[cfg_attr(tarpaulin, coverage(off))]
2635    pub fn resolve_v1_url_to(mut self, address: SocketAddr) -> Self {
2636        let v1_host = self.v1_url_host();
2637
2638        self.builder = self.builder.resolve(&v1_host, address);
2639
2640        self
2641    }
2642
2643    #[doc(hidden)]
2644    /// Force DNS resolution for the current `v3_base_url` to the IP address
2645    #[cfg_attr(tarpaulin, coverage(off))]
2646    pub fn resolve_v3_url_to(mut self, address: SocketAddr) -> Self {
2647        let v3_host = self.v3_url_host();
2648
2649        self.builder = self.builder.resolve(&v3_host, address);
2650
2651        self
2652    }
2653}
2654
2655#[cfg(any(test, tarpaulin, feature = "ci"))]
2656impl BriteVerifyClient {
2657    #[doc(hidden)]
2658    /// Use a client instance's [`_build_and_send`](BriteVerifyClient::_build_and_send)
2659    /// method directly in a unit or end-to-end test
2660    #[cfg_attr(tarpaulin, coverage(off))]
2661    pub async fn build_and_send(
2662        &self,
2663        request: reqwest::RequestBuilder,
2664    ) -> Result<reqwest::Response, errors::BriteVerifyClientError> {
2665        self._build_and_send(request).await
2666    }
2667}
2668
2669// </editor-fold desc="// Test Utilities ...">
2670
2671// <editor-fold desc="// I/O-Free Tests ...">
2672
2673#[cfg(test)]
2674mod tests {
2675    // Third-Party Dependencies
2676    use anyhow::Result;
2677    use http::uri::Scheme;
2678    use pretty_assertions::{assert_eq, assert_ne, assert_str_eq};
2679
2680    // Crate-Level Imports
2681    use super::{
2682        BriteVerifyClient, BriteVerifyClientBuilder, HeaderMap, HeaderValue, AUTHORIZATION,
2683    };
2684
2685    // <editor-fold desc="// Constants ...">
2686
2687    const GOOD_KEY: &str = "I guess it's better to be lucky than good";
2688    const BAD_KEY: &str =
2689        "\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\r";
2690
2691    // </editor-fold desc="// Constants ...">
2692
2693    // <editor-fold desc="// Tests ...">
2694
2695    #[rstest::rstest]
2696    /// Test that the `BriteVerifyClientBuilder`'s `new`
2697    /// method properly creates the expected client instance
2698    fn test_new_bv_client() {
2699        let client = BriteVerifyClient::new(BAD_KEY);
2700
2701        assert!(
2702            client.as_ref().is_err_and(|error| matches!(
2703                error,
2704                super::errors::BriteVerifyClientError::InvalidHeaderValue(_)
2705            )),
2706            "Expected Err(BriteVerifyClientError::InvalidHeaderValue), got: {:#?}",
2707            client
2708        );
2709
2710        let client = BriteVerifyClient::new(GOOD_KEY);
2711
2712        assert!(
2713            client.as_ref().is_ok_and(|instance| {
2714                let client_repr = format!("{:?}", instance);
2715                !client_repr.contains(GOOD_KEY)
2716            }),
2717            "Expected the configured API key to be obfuscated, but got: {:#?}",
2718            client
2719        );
2720    }
2721
2722    //noinspection HttpUrlsUsage
2723    #[rstest::rstest]
2724    /// Test that the `BriteVerifyClientBuilder`'s `http_only`
2725    /// method properly mutates the appropriate inner `v?_base_url`
2726    /// fields and passes the expected values through to constructed
2727    /// client instances
2728    fn test_builder_http_only() {
2729        // Create a new builder instance with no base url(s) set
2730        let builder = BriteVerifyClient::builder();
2731
2732        // Regardless of the value of the supplied flag, if the value
2733        // of `v?_base_url` is currently `None`, the default base url(s)
2734        // will be implicitly configured as the base url value(s).
2735        //
2736        // However, because toggling `http_only` to `false` only *enables*
2737        // HTTP-scheme urls (as opposed to explicitly *disabling* HTTPS ones),
2738        // no mutation of the existing url scheme will be performed. Therefore
2739        // when starting with a value of `None` for either base url and toggling
2740        // `http_only` to `false`, the url scheme should always remain HTTPS
2741        let builder = builder.https_only(false);
2742
2743        // when none -> base url w/ https scheme
2744        assert_str_eq!(builder.v1_base_url.scheme(), Scheme::HTTPS.as_str());
2745        assert_str_eq!(builder.v3_base_url.scheme(), Scheme::HTTPS.as_str());
2746
2747        let (v1_url, v3_url) = (builder.v1_base_url.clone(), builder.v3_base_url.clone());
2748
2749        // Toggling `https_only` to `true` should not change the configured URL
2750        // but regardless of their previous scheme, they should now both be HTTPS
2751        // (or *still* HTTPS, in this case)
2752        let builder = builder.https_only(true);
2753
2754        // when some && enabled -> self.v?_base_url w/ https scheme
2755        assert_eq!(&builder.v1_base_url, &v1_url);
2756        assert_eq!(&builder.v3_base_url, &v3_url);
2757
2758        // There's no restriction against setting HTTP-scheme
2759        // values as base urls when `http_only` is `true` built
2760        // into the `Builder` itself, but doing so would cause
2761        // the constructed reqwest client to throw an error the
2762        // first time it's instructed to make a request, so the
2763        // lack of tight coupling between `BriteVerifyClientBuilder`
2764        // and `reqwest::ClientBuilder` is acceptable in this
2765        // specific regard.
2766        //
2767        // tl;dr we're gonna set the base urls to HTTP-scheme
2768        // values before we toggle `https_only` again so we can
2769        // verify that *disabling* HTTPS doesn't mutate the currently
2770        // configured base urls unexpectedly.'
2771        let builder = builder
2772            .v1_base_url("http://example.com/api/v1")
2773            .v3_base_url("http://example.com/api/v3");
2774
2775        assert_str_eq!(builder.v1_base_url.scheme(), Scheme::HTTP.as_str());
2776        assert_str_eq!(builder.v3_base_url.scheme(), Scheme::HTTP.as_str());
2777
2778        let (v1_url, v3_url) = (builder.v1_base_url.clone(), builder.v3_base_url.clone());
2779
2780        // when some && disabled -> self.v?_base_url w/ untouched scheme
2781        let builder = builder.https_only(false);
2782
2783        assert_eq!(&builder.v1_base_url, &v1_url);
2784        assert_eq!(&builder.v3_base_url, &v3_url);
2785
2786        // Finally, when the currently configured base urls are
2787        // non-`None` http-scheme values, toggling `https_only`
2788        // should mutate *ONLY* the scheme of the configured urls
2789        // to be HTTPS.
2790
2791        // when some && enabled -> self.v?_base_url w/ https scheme
2792        let builder = builder.https_only(true);
2793
2794        assert_str_eq!(builder.v1_base_url.scheme(), Scheme::HTTPS.as_str());
2795        assert_str_eq!(builder.v3_base_url.scheme(), Scheme::HTTPS.as_str());
2796
2797        assert_str_eq!(
2798            v1_url
2799                .as_str()
2800                .strip_prefix("http://")
2801                .map_or(v1_url.to_string(), |url| url.to_string()),
2802            builder
2803                .v1_base_url
2804                .as_str()
2805                .strip_prefix("https://")
2806                .map_or(builder.v1_base_url.as_str().to_string(), |url| {
2807                    url.to_string()
2808                })
2809        );
2810        assert_str_eq!(
2811            v3_url
2812                .as_str()
2813                .strip_prefix("http://")
2814                .map_or(v3_url.to_string(), |url| url.to_string()),
2815            builder
2816                .v3_base_url
2817                .as_str()
2818                .strip_prefix("https://")
2819                .map_or(builder.v3_base_url.as_str().to_string(), |url| {
2820                    url.to_string()
2821                })
2822        );
2823    }
2824
2825    #[rstest::rstest]
2826    /// Test that the `BriteVerifyClientBuilder`'s `api_key` method
2827    /// sets/clears the inner `api_key` and `error` fields as expected
2828    fn test_builder_api_key_handling() {
2829        // ⇣ this *should* cause an `InvalidHeaderValue` error to be set
2830        let builder = BriteVerifyClient::builder().api_key(BAD_KEY);
2831
2832        // when error -> clears api_key, sets error
2833        assert!(builder.error.is_some());
2834        assert!(builder.api_key.is_none());
2835
2836        let builder = builder.api_key(GOOD_KEY);
2837
2838        // when ok -> sets api_key, clears error (if set)
2839        assert!(builder.error.is_none());
2840        assert!(builder
2841            .api_key
2842            .as_ref()
2843            .is_some_and(|value| value.is_sensitive()));
2844
2845        assert!(builder.build().is_ok());
2846
2847        assert!(BriteVerifyClient::builder()
2848            .api_key(BAD_KEY)
2849            .build()
2850            .is_err())
2851    }
2852
2853    #[rstest::rstest]
2854    /// Test that the `BriteVerifyClientBuilder`'s `v?_base_url`
2855    /// methods properly set/clear the appropriate inner `v?_base_url`
2856    /// and `error` fields and pass the expected values through
2857    /// to their constructed client instances
2858    fn test_builder_base_url_handling() {
2859        let builder = BriteVerifyClient::builder()
2860            .v1_base_url("https://testing.example.com:443")
2861            .v3_base_url("https://testing.example.com:443");
2862
2863        assert!(builder.error.is_none());
2864        assert_ne!(super::V1_API_BASE_URL, builder.v1_base_url.as_str());
2865        assert_ne!(super::V3_API_BASE_URL, builder.v3_base_url.as_str());
2866
2867        let (v1_url, v3_url) = (builder.v1_base_url.clone(), builder.v3_base_url.clone());
2868
2869        let builder = builder.v1_base_url("").v3_base_url("");
2870
2871        // The base URLs should be unchanged, but an error should now
2872        // be set, and it *should* specifically be an `InvalidBaseUrl`
2873        assert_str_eq!(v1_url.as_str(), builder.v1_base_url.as_str());
2874        assert_str_eq!(v3_url.as_str(), builder.v3_base_url.as_str());
2875        assert!(
2876            builder.error.as_ref().is_some_and(|error| matches!(
2877                error,
2878                super::errors::BriteVerifyClientError::InvalidBaseUrl(_)
2879            )),
2880            "Expected an `InvalidBaseUrl` error, got: {:?}",
2881            builder.error.as_ref()
2882        );
2883    }
2884
2885    #[rstest::rstest]
2886    /// Test the `BriteVerifyClient`'s
2887    /// `From<reqwest::Client>` implementation
2888    fn test_bv_client_from_reqwest_client() -> Result<()> {
2889        let client = BriteVerifyClient::try_from(reqwest::Client::new());
2890
2891        assert!(
2892            client.as_ref().is_err_and(|error| matches!(
2893                error,
2894                super::errors::BriteVerifyClientError::MissingApiKey
2895            )),
2896            "Expected Err(BriteVerifyClientError::MissingApiKey), got: {:#?}",
2897            client,
2898        );
2899
2900        let headers = HeaderMap::from_iter(
2901            [(
2902                AUTHORIZATION,
2903                HeaderValue::from_str("ApiKey: well, that's certainly good to know")?,
2904            )]
2905            .into_iter(),
2906        );
2907        let client = BriteVerifyClient::try_from(
2908            reqwest::Client::builder()
2909                .default_headers(headers)
2910                .build()?,
2911        );
2912
2913        Ok(assert!(
2914            client.as_ref().is_ok(),
2915            "Expected Ok(BriteVerifyClient), got: {:#?}",
2916            client
2917        ))
2918    }
2919
2920    #[rstest::rstest]
2921    /// Test the `BriteVerifyClientBuilder`'s
2922    /// `From<reqwest::ClientBuilder>` implementation
2923    fn test_bv_builder_from_reqwest_builder() -> Result<()> {
2924        let req_builder = BriteVerifyClientBuilder::from(reqwest::Client::builder());
2925
2926        assert!(req_builder.api_key.is_none());
2927        assert!(req_builder.build().is_err());
2928
2929        let req_builder = BriteVerifyClientBuilder::from(
2930            reqwest::Client::builder().default_headers(HeaderMap::from_iter(
2931                [(
2932                    AUTHORIZATION,
2933                    HeaderValue::from_str("ApiKey: fate protects little children and fools")?,
2934                )]
2935                .into_iter(),
2936            )),
2937        );
2938
2939        assert!(req_builder
2940            .api_key
2941            .as_ref()
2942            .is_some_and(|val| !val.is_sensitive()));
2943        Ok(assert!(req_builder.build().is_ok()))
2944    }
2945
2946    // </editor-fold desc="// Tests ...">
2947}
2948
2949// </editor-fold desc="// I/O-Free Tests ...">