Skip to main content

google_cloud_gax/
client_builder.rs

1// Copyright 2025 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Provide types for client construction.
16//!
17//! Some applications need to construct clients with custom configuration, for
18//! example, they may need to override the endpoint or the authentication
19//! credentials. The Google Cloud client libraries for Rust use a generic
20//! builder type to provide such functionality. The types in this module
21//! implement the client builders.
22//!
23//! Applications should not create builders directly, instead each client type
24//! defines a `builder()` function to obtain the correct type of builder.
25//!
26//! ## Example: create a client with the default configuration.
27//!
28//! ```
29//! # use google_cloud_gax::client_builder::examples;
30//! # use google_cloud_gax::client_builder::Result;
31//! # tokio_test::block_on(async {
32//! pub use examples::Client; // Placeholder for examples
33//! let client = Client::builder().build().await?;
34//! # Result::<()>::Ok(()) });
35//! ```
36//!
37//! ## Example: create a client with a different endpoint
38//!
39//! ```
40//! # use google_cloud_gax::client_builder::examples;
41//! # use google_cloud_gax::client_builder::Result;
42//! # tokio_test::block_on(async {
43//! pub use examples::Client; // Placeholder for examples
44//! let client = Client::builder()
45//!     .with_endpoint("https://private.googleapis.com")
46//!     .build().await?;
47//! # Result::<()>::Ok(()) });
48//! ```
49
50use crate::backoff_policy::{BackoffPolicy, BackoffPolicyArg};
51use crate::polling_backoff_policy::{PollingBackoffPolicy, PollingBackoffPolicyArg};
52use crate::polling_error_policy::{PollingErrorPolicy, PollingErrorPolicyArg};
53use crate::retry_policy::{RetryPolicy, RetryPolicyArg};
54use crate::retry_throttler::{RetryThrottlerArg, SharedRetryThrottler};
55use std::sync::Arc;
56
57/// The result type for this module.
58pub type Result<T> = std::result::Result<T, Error>;
59
60/// Indicates a problem while constructing a client.
61///
62/// # Examples
63/// ```no_run
64/// # use google_cloud_gax::client_builder::examples;
65/// use google_cloud_gax::client_builder::Error as Error;
66/// use examples::Client; // Placeholder for examples
67/// # tokio_test::block_on(async {
68/// let client = match Client::builder().build().await {
69///     Ok(c) => c,
70///     Err(e) if e.is_default_credentials() => {
71///         println!("error during client initialization: {e}");
72///         println!("troubleshoot using https://cloud.google.com/docs/authentication/client-libraries");
73///         return Err(e);
74///     }
75///     Err(e) => {
76///         println!("error during client initialization {e}");
77///         return Err(e);
78///     }
79/// };
80/// # Ok::<(), Error>(()) });
81/// ```
82#[derive(thiserror::Error, Debug)]
83#[error(transparent)]
84pub struct Error(ErrorKind);
85
86impl Error {
87    /// If true, the client could not initialize the default credentials.
88    pub fn is_default_credentials(&self) -> bool {
89        matches!(&self.0, ErrorKind::DefaultCredentials(_))
90    }
91
92    /// If true, the client could not initialize the transport client.
93    pub fn is_transport(&self) -> bool {
94        matches!(&self.0, ErrorKind::Transport(_))
95    }
96
97    /// Not part of the public API, subject to change without notice.
98    #[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
99    pub fn cred<T: Into<BoxError>>(source: T) -> Self {
100        Self(ErrorKind::DefaultCredentials(source.into()))
101    }
102
103    /// Not part of the public API, subject to change without notice.
104    #[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
105    pub fn transport<T: Into<BoxError>>(source: T) -> Self {
106        Self(ErrorKind::Transport(source.into()))
107    }
108}
109
110#[derive(thiserror::Error, Debug)]
111enum ErrorKind {
112    #[error("could not create default credentials")]
113    DefaultCredentials(#[source] BoxError),
114    #[error("could not initialize transport client")]
115    Transport(#[source] BoxError),
116}
117
118type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
119
120/// A generic builder for clients.
121///
122/// In the Google Cloud client libraries for Rust a "client" represents a
123/// connection to a specific service. Each client library defines one or more
124/// client types. All the clients are initialized using a `ClientBuilder`.
125///
126/// Applications obtain a builder with the correct generic types using the
127/// `builder()` method on each client:
128/// ```
129/// # use google_cloud_gax::client_builder::examples;
130/// # use google_cloud_gax::client_builder::Result;
131/// # tokio_test::block_on(async {
132/// use examples::Client; // Placeholder for examples
133/// let builder = Client::builder();
134/// # Result::<()>::Ok(()) });
135/// ```
136///
137/// To create a client with the default configuration just invoke the
138/// `.build()` method:
139/// ```
140/// # use google_cloud_gax::client_builder::examples;
141/// # use google_cloud_gax::client_builder::Result;
142/// # tokio_test::block_on(async {
143/// use examples::Client; // Placeholder for examples
144/// let client = Client::builder().build().await?;
145/// # Result::<()>::Ok(()) });
146/// ```
147///
148/// As usual, the builder offers several method to configure the client, and a
149/// `.build()` method to construct the client:
150/// ```
151/// # use google_cloud_gax::client_builder::examples;
152/// # use google_cloud_gax::client_builder::Result;
153/// # tokio_test::block_on(async {
154/// use examples::Client; // Placeholder for examples
155/// let client = Client::builder()
156///     .with_endpoint("http://private.googleapis.com")
157///     .build().await?;
158/// # Result::<()>::Ok(()) });
159/// ```
160#[derive(Clone, Debug)]
161pub struct ClientBuilder<F, Cr> {
162    config: internal::ClientConfig<Cr>,
163    factory: F,
164}
165
166impl<F, Cr> ClientBuilder<F, Cr> {
167    /// Creates a new client.
168    ///
169    /// ```
170    /// # use google_cloud_gax::client_builder::examples;
171    /// # use google_cloud_gax::client_builder::Result;
172    /// # tokio_test::block_on(async {
173    /// use examples::Client; // Placeholder for examples
174    /// let client = Client::builder()
175    ///     .build().await?;
176    /// # Result::<()>::Ok(()) });
177    /// ```
178    pub async fn build<C>(self) -> Result<C>
179    where
180        F: internal::ClientFactory<Client = C, Credentials = Cr>,
181    {
182        self.factory.build(self.config).await
183    }
184
185    /// Sets the endpoint.
186    ///
187    /// ```
188    /// # use google_cloud_gax::client_builder::examples;
189    /// # use google_cloud_gax::client_builder::Result;
190    /// # tokio_test::block_on(async {
191    /// use examples::Client; // Placeholder for examples
192    /// let client = Client::builder()
193    ///     .with_endpoint("http://private.googleapis.com")
194    ///     .build().await?;
195    /// # Result::<()>::Ok(()) });
196    /// ```
197    pub fn with_endpoint<V: Into<String>>(mut self, v: V) -> Self {
198        self.config.endpoint = Some(v.into());
199        self
200    }
201
202    /// Enables tracing.
203    ///
204    /// The client libraries can be dynamically instrumented with the Tokio
205    /// [tracing] framework. Setting this flag enables this instrumentation.
206    ///
207    /// ```
208    /// # use google_cloud_gax::client_builder::examples;
209    /// # use google_cloud_gax::client_builder::Result;
210    /// # tokio_test::block_on(async {
211    /// use examples::Client; // Placeholder for examples
212    /// let client = Client::builder()
213    ///     .with_tracing()
214    ///     .build().await?;
215    /// # Result::<()>::Ok(()) });
216    /// ```
217    ///
218    /// [tracing]: https://docs.rs/tracing/latest/tracing/
219    pub fn with_tracing(mut self) -> Self {
220        self.config.tracing = true;
221        self
222    }
223
224    /// Configure the authentication credentials.
225    ///
226    /// Most Google Cloud services require authentication, though some services
227    /// allow for anonymous access, and some services provide emulators where
228    /// no authentication is required. More information about valid credentials
229    /// types can be found in the [google-cloud-auth] crate documentation.
230    ///
231    /// ```
232    /// # use google_cloud_gax::client_builder::examples;
233    /// # use google_cloud_gax::client_builder::Result;
234    /// # tokio_test::block_on(async {
235    /// use examples::Client; // Placeholder for examples
236    /// // Placeholder, normally use google_cloud_auth::credentials
237    /// use examples::credentials;
238    /// let client = Client::builder()
239    ///     .with_credentials(
240    ///         credentials::mds::Builder::new()
241    ///             .scopes(["https://www.googleapis.com/auth/cloud-platform.read-only"])
242    ///             .build())
243    ///     .build().await?;
244    /// # Result::<()>::Ok(()) });
245    /// ```
246    ///
247    /// [google-cloud-auth]: https://docs.rs/google-cloud-auth
248    pub fn with_credentials<T: Into<Cr>>(mut self, v: T) -> Self {
249        self.config.cred = Some(v.into());
250        self
251    }
252
253    /// Configure the retry policy.
254    ///
255    /// The client libraries can automatically retry operations that fail. The
256    /// retry policy controls what errors are considered retryable, sets limits
257    /// on the number of attempts or the time trying to make attempts.
258    ///
259    /// ```
260    /// # use google_cloud_gax::client_builder::examples;
261    /// # use google_cloud_gax as gax;
262    /// # use google_cloud_gax::client_builder::Result;
263    /// # tokio_test::block_on(async {
264    /// use examples::Client; // Placeholder for examples
265    /// use gax::retry_policy::{AlwaysRetry, RetryPolicyExt};
266    /// let client = Client::builder()
267    ///     .with_retry_policy(AlwaysRetry.with_attempt_limit(3))
268    ///     .build().await?;
269    /// # Result::<()>::Ok(()) });
270    /// ```
271    pub fn with_retry_policy<V: Into<RetryPolicyArg>>(mut self, v: V) -> Self {
272        self.config.retry_policy = Some(v.into().into());
273        self
274    }
275
276    /// Configure the retry backoff policy.
277    ///
278    /// The client libraries can automatically retry operations that fail. The
279    /// backoff policy controls how long to wait in between retry attempts.
280    ///
281    /// ```
282    /// # use google_cloud_gax::client_builder::examples;
283    /// # use google_cloud_gax as gax;
284    /// # use google_cloud_gax::client_builder::Result;
285    /// # tokio_test::block_on(async {
286    /// use examples::Client; // Placeholder for examples
287    /// use gax::exponential_backoff::ExponentialBackoff;
288    /// use std::time::Duration;
289    /// let policy = ExponentialBackoff::default();
290    /// let client = Client::builder()
291    ///     .with_backoff_policy(policy)
292    ///     .build().await?;
293    /// # Result::<()>::Ok(()) });
294    /// ```
295    pub fn with_backoff_policy<V: Into<BackoffPolicyArg>>(mut self, v: V) -> Self {
296        self.config.backoff_policy = Some(v.into().into());
297        self
298    }
299
300    /// Configure the retry throttler.
301    ///
302    /// Advanced applications may want to configure a retry throttler to
303    /// [Address Cascading Failures] and when [Handling Overload] conditions.
304    /// The client libraries throttle their retry loop, using a policy to
305    /// control the throttling algorithm. Use this method to fine tune or
306    /// customize the default retry throtler.
307    ///
308    /// [Handling Overload]: https://sre.google/sre-book/handling-overload/
309    /// [Address Cascading Failures]: https://sre.google/sre-book/addressing-cascading-failures/
310    ///
311    /// ```
312    /// # use google_cloud_gax::client_builder::examples;
313    /// # use google_cloud_gax as gax;
314    /// # use google_cloud_gax::client_builder::Result;
315    /// # tokio_test::block_on(async {
316    /// use examples::Client; // Placeholder for examples
317    /// use gax::retry_throttler::AdaptiveThrottler;
318    /// let client = Client::builder()
319    ///     .with_retry_throttler(AdaptiveThrottler::default())
320    ///     .build().await?;
321    /// # Result::<()>::Ok(()) });
322    /// ```
323    pub fn with_retry_throttler<V: Into<RetryThrottlerArg>>(mut self, v: V) -> Self {
324        self.config.retry_throttler = v.into().into();
325        self
326    }
327
328    /// Configure the polling error policy.
329    ///
330    /// Some clients support long-running operations, the client libraries can
331    /// automatically poll these operations until they complete. Polling may
332    /// fail due to transient errors and applications may want to continue the
333    /// polling loop despite such errors. The polling error policy controls
334    /// which errors are treated as recoverable, and may limit the number
335    /// of attempts and/or the total time polling the operation.
336    ///
337    /// ```
338    /// # use google_cloud_gax::client_builder::examples;
339    /// # use google_cloud_gax as gax;
340    /// # use google_cloud_gax::client_builder::Result;
341    /// # tokio_test::block_on(async {
342    /// use examples::Client; // Placeholder for examples
343    /// use gax::polling_error_policy::Aip194Strict;
344    /// use gax::polling_error_policy::PollingErrorPolicyExt;
345    /// use std::time::Duration;
346    /// let client = Client::builder()
347    ///     .with_polling_error_policy(Aip194Strict
348    ///         .with_time_limit(Duration::from_secs(15 * 60))
349    ///         .with_attempt_limit(50))
350    ///     .build().await?;
351    /// # Result::<()>::Ok(()) });
352    /// ```
353    pub fn with_polling_error_policy<V: Into<PollingErrorPolicyArg>>(mut self, v: V) -> Self {
354        self.config.polling_error_policy = Some(v.into().0);
355        self
356    }
357
358    /// Configure the polling backoff policy.
359    ///
360    /// Some clients support long-running operations, the client libraries can
361    /// automatically poll these operations until they complete. The polling
362    /// backoff policy controls how long the client waits between polling
363    /// attempts.
364    ///
365    /// ```
366    /// # use google_cloud_gax::client_builder::examples;
367    /// # use google_cloud_gax as gax;
368    /// # use google_cloud_gax::client_builder::Result;
369    /// # tokio_test::block_on(async {
370    /// use examples::Client; // Placeholder for examples
371    /// use gax::exponential_backoff::ExponentialBackoff;
372    /// use std::time::Duration;
373    /// let policy = ExponentialBackoff::default();
374    /// let client = Client::builder()
375    ///     .with_polling_backoff_policy(policy)
376    ///     .build().await?;
377    /// # Result::<()>::Ok(()) });
378    /// ```
379    pub fn with_polling_backoff_policy<V: Into<PollingBackoffPolicyArg>>(mut self, v: V) -> Self {
380        self.config.polling_backoff_policy = Some(v.into().0);
381        self
382    }
383}
384
385#[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
386pub mod internal {
387    use super::*;
388
389    pub trait ClientFactory {
390        type Client;
391        type Credentials;
392        fn build(
393            self,
394            config: internal::ClientConfig<Self::Credentials>,
395        ) -> impl Future<Output = Result<Self::Client>>;
396    }
397
398    pub fn new_builder<F, Cr, C>(factory: F) -> super::ClientBuilder<F, Cr>
399    where
400        F: ClientFactory<Client = C, Credentials = Cr>,
401    {
402        super::ClientBuilder {
403            factory,
404            config: ClientConfig::default(),
405        }
406    }
407
408    /// Configure a client.
409    ///
410    /// A client represents a connection to a Google Cloud Service. Each service
411    /// has one or more client types. The default configuration for each client
412    /// should work for most applications. But some applications may need to
413    /// override the default endpoint, the default authentication credentials,
414    /// the retry policies, and/or other behaviors of the client.
415    #[derive(Clone, Debug)]
416    #[non_exhaustive]
417    pub struct ClientConfig<Cr> {
418        pub endpoint: Option<String>,
419        pub cred: Option<Cr>,
420        pub tracing: bool,
421        pub retry_policy: Option<Arc<dyn RetryPolicy>>,
422        pub backoff_policy: Option<Arc<dyn BackoffPolicy>>,
423        pub retry_throttler: SharedRetryThrottler,
424        pub polling_error_policy: Option<Arc<dyn PollingErrorPolicy>>,
425        pub polling_backoff_policy: Option<Arc<dyn PollingBackoffPolicy>>,
426        pub disable_automatic_decompression: bool,
427        pub disable_follow_redirects: bool,
428        pub grpc_subchannel_count: Option<usize>,
429        pub grpc_request_buffer_capacity: Option<usize>,
430    }
431
432    impl<Cr> std::default::Default for ClientConfig<Cr> {
433        fn default() -> Self {
434            use crate::retry_throttler::AdaptiveThrottler;
435            use std::sync::{Arc, Mutex};
436            Self {
437                endpoint: None,
438                cred: None,
439                tracing: false,
440                retry_policy: None,
441                backoff_policy: None,
442                retry_throttler: Arc::new(Mutex::new(AdaptiveThrottler::default())),
443                polling_error_policy: None,
444                polling_backoff_policy: None,
445                disable_automatic_decompression: false,
446                disable_follow_redirects: false,
447                grpc_subchannel_count: None,
448                grpc_request_buffer_capacity: None,
449            }
450        }
451    }
452
453    /// Configure automatic decompression.
454    ///
455    /// By default, the client libraries automatically decompress responses.
456    /// Internal users can disable this behavior if they need to access the raw
457    /// compressed bytes.
458    pub fn with_automatic_decompression<F, Cr>(
459        mut builder: super::ClientBuilder<F, Cr>,
460        v: bool,
461    ) -> super::ClientBuilder<F, Cr> {
462        builder.config.disable_automatic_decompression = !v;
463        builder
464    }
465
466    /// Configure HTTP redirects.
467    ///
468    /// By default, the client libraries automatically follow HTTP redirects.
469    /// Internal users can disable this behavior if they need to handle redirects
470    /// manually (e.g. for 308 Resume Incomplete).
471    pub fn with_follow_redirects<F, Cr>(
472        mut builder: super::ClientBuilder<F, Cr>,
473        v: bool,
474    ) -> super::ClientBuilder<F, Cr> {
475        builder.config.disable_follow_redirects = !v;
476        builder
477    }
478}
479
480#[doc(hidden)]
481pub mod examples {
482    //! This module contains helper types used in the rustdoc examples.
483    //!
484    //! The examples require relatively complex types to be useful.
485
486    type Config = super::internal::ClientConfig<Credentials>;
487    use super::Result;
488
489    /// A client type for use in examples.
490    ///
491    /// This type is used in examples as a placeholder for a real client. It
492    /// does not work, but illustrates how to use `ClientBuilder`.
493    #[allow(dead_code)]
494    pub struct Client(Config);
495    impl Client {
496        /// Create a builder to initialize new instances of this client.
497        pub fn builder() -> client::Builder {
498            super::internal::new_builder(client::Factory)
499        }
500
501        async fn new(config: super::internal::ClientConfig<Credentials>) -> Result<Self> {
502            Ok(Self(config))
503        }
504    }
505    mod client {
506        pub type Builder = super::super::ClientBuilder<Factory, super::Credentials>;
507        pub struct Factory;
508        impl super::super::internal::ClientFactory for Factory {
509            type Credentials = super::Credentials;
510            type Client = super::Client;
511            async fn build(
512                self,
513                config: crate::client_builder::internal::ClientConfig<Self::Credentials>,
514            ) -> super::Result<Self::Client> {
515                Self::Client::new(config).await
516            }
517        }
518    }
519
520    #[derive(Clone, Debug, Default, PartialEq)]
521    pub struct Credentials {
522        pub scopes: Vec<String>,
523    }
524
525    pub mod credentials {
526        pub mod mds {
527            #[derive(Clone, Default)]
528            pub struct Builder(super::super::Credentials);
529            impl Builder {
530                pub fn new() -> Self {
531                    Self(super::super::Credentials::default())
532                }
533                pub fn build(self) -> super::super::Credentials {
534                    self.0
535                }
536                pub fn scopes<I, V>(mut self, iter: I) -> Self
537                where
538                    I: IntoIterator<Item = V>,
539                    V: Into<String>,
540                {
541                    self.0.scopes = iter.into_iter().map(|v| v.into()).collect();
542                    self
543                }
544            }
545        }
546    }
547
548    // We use the examples as scaffolding for the tests.
549    #[cfg(test)]
550    mod tests {
551        use super::*;
552
553        #[tokio::test]
554        async fn build_default() {
555            let client = Client::builder().build().await.unwrap();
556            let config = client.0;
557            assert_eq!(config.endpoint, None);
558            assert_eq!(config.cred, None);
559            assert!(!config.tracing);
560            assert!(
561                format!("{:?}", &config).contains("AdaptiveThrottler"),
562                "{config:?}"
563            );
564            assert!(config.retry_policy.is_none(), "{config:?}");
565            assert!(config.backoff_policy.is_none(), "{config:?}");
566            assert!(config.polling_error_policy.is_none(), "{config:?}");
567            assert!(config.polling_backoff_policy.is_none(), "{config:?}");
568            assert!(!config.disable_automatic_decompression, "{config:?}");
569            assert!(!config.disable_follow_redirects, "{config:?}");
570        }
571
572        #[tokio::test]
573        async fn endpoint() {
574            let client = Client::builder()
575                .with_endpoint("http://example.com")
576                .build()
577                .await
578                .unwrap();
579            let config = client.0;
580            assert_eq!(config.endpoint.as_deref(), Some("http://example.com"));
581        }
582
583        #[tokio::test]
584        async fn tracing() {
585            let client = Client::builder().with_tracing().build().await.unwrap();
586            let config = client.0;
587            assert!(config.tracing);
588        }
589
590        #[tokio::test]
591        async fn automatic_decompression() {
592            let client = Client::builder();
593            let client = super::super::internal::with_automatic_decompression(client, false)
594                .build()
595                .await
596                .unwrap();
597            let config = client.0;
598            assert!(config.disable_automatic_decompression);
599
600            let client = Client::builder();
601            let client = super::super::internal::with_automatic_decompression(client, true)
602                .build()
603                .await
604                .unwrap();
605            let config = client.0;
606            assert!(!config.disable_automatic_decompression);
607        }
608
609        #[tokio::test]
610        async fn follow_redirects() {
611            let client = Client::builder();
612            let client = super::super::internal::with_follow_redirects(client, false)
613                .build()
614                .await
615                .unwrap();
616            let config = client.0;
617            assert!(config.disable_follow_redirects);
618
619            let client = Client::builder();
620            let client = super::super::internal::with_follow_redirects(client, true)
621                .build()
622                .await
623                .unwrap();
624            let config = client.0;
625            assert!(!config.disable_follow_redirects);
626        }
627
628        #[tokio::test]
629        async fn credentials() {
630            let client = Client::builder()
631                .with_credentials(
632                    credentials::mds::Builder::new()
633                        .scopes(["test-scope"])
634                        .build(),
635                )
636                .build()
637                .await
638                .unwrap();
639            let config = client.0;
640            let cred = config.cred.unwrap();
641            assert_eq!(cred.scopes, vec!["test-scope".to_string()]);
642        }
643
644        #[tokio::test]
645        async fn retry_policy() {
646            use crate::retry_policy::RetryPolicyExt;
647            let client = Client::builder()
648                .with_retry_policy(crate::retry_policy::AlwaysRetry.with_attempt_limit(3))
649                .build()
650                .await
651                .unwrap();
652            let config = client.0;
653            assert!(config.retry_policy.is_some(), "{config:?}");
654        }
655
656        #[tokio::test]
657        async fn backoff_policy() {
658            let client = Client::builder()
659                .with_backoff_policy(crate::exponential_backoff::ExponentialBackoff::default())
660                .build()
661                .await
662                .unwrap();
663            let config = client.0;
664            assert!(config.backoff_policy.is_some(), "{config:?}");
665        }
666
667        #[tokio::test]
668        async fn retry_throttler() {
669            use crate::retry_throttler::CircuitBreaker;
670            let client = Client::builder()
671                .with_retry_throttler(CircuitBreaker::default())
672                .build()
673                .await
674                .unwrap();
675            let config = client.0;
676            assert!(
677                format!("{:?}", &config).contains("CircuitBreaker"),
678                "{config:?}"
679            );
680        }
681
682        #[tokio::test]
683        async fn polling_error_policy() {
684            use crate::polling_error_policy::PollingErrorPolicyExt;
685            let client = Client::builder()
686                .with_polling_error_policy(
687                    crate::polling_error_policy::AlwaysContinue.with_attempt_limit(3),
688                )
689                .build()
690                .await
691                .unwrap();
692            let config = client.0;
693            assert!(config.polling_error_policy.is_some(), "{config:?}");
694        }
695
696        #[tokio::test]
697        async fn polling_backoff_policy() {
698            let client = Client::builder()
699                .with_polling_backoff_policy(
700                    crate::exponential_backoff::ExponentialBackoff::default(),
701                )
702                .build()
703                .await
704                .unwrap();
705            let config = client.0;
706            assert!(config.polling_backoff_policy.is_some(), "{config:?}");
707        }
708    }
709}
710
711#[cfg(test)]
712mod tests {
713    use super::*;
714    use std::error::Error as _;
715
716    #[test]
717    fn error_credentials() {
718        let source = wkt::TimestampError::OutOfRange;
719        let error = Error::cred(source);
720        assert!(error.is_default_credentials(), "{error:?}");
721        assert!(error.to_string().contains("default credentials"), "{error}");
722        let got = error
723            .source()
724            .and_then(|e| e.downcast_ref::<wkt::TimestampError>());
725        assert!(
726            matches!(got, Some(wkt::TimestampError::OutOfRange)),
727            "{error:?}"
728        );
729    }
730
731    #[test]
732    fn transport() {
733        let source = wkt::TimestampError::OutOfRange;
734        let error = Error::transport(source);
735        assert!(error.is_transport(), "{error:?}");
736        assert!(error.to_string().contains("transport client"), "{error}");
737        let got = error
738            .source()
739            .and_then(|e| e.downcast_ref::<wkt::TimestampError>());
740        assert!(
741            matches!(got, Some(wkt::TimestampError::OutOfRange)),
742            "{error:?}"
743        );
744    }
745}