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(¶ms);
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 ...">