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//! # async fn sample() -> anyhow::Result<()> {
32//! pub use examples::Client; // Placeholder for examples
33//! let client = Client::builder().build().await?;
34//! # 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//! # async fn sample() -> anyhow::Result<()> {
43//! pub use examples::Client; // Placeholder for examples
44//! let client = Client::builder()
45//!     .with_endpoint("https://private.googleapis.com")
46//!     .build().await?;
47//! # 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/// ```
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/// # async fn sample() -> Result<(), Error> {
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(()) }
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/// # async fn sample() -> anyhow::Result<()> {
132/// use examples::Client; // Placeholder for examples
133/// let builder = Client::builder();
134/// # 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/// # async fn sample() -> anyhow::Result<()> {
143/// use examples::Client; // Placeholder for examples
144/// let client = Client::builder().build().await?;
145/// # 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/// # async fn sample() -> anyhow::Result<()> {
154/// use examples::Client; // Placeholder for examples
155/// let client = Client::builder()
156///     .with_endpoint("http://private.googleapis.com")
157///     .build().await?;
158/// # 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    /// # async fn sample() -> anyhow::Result<()> {
173    /// use examples::Client; // Placeholder for examples
174    /// let client = Client::builder()
175    ///     .build().await?;
176    /// # 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    /// # async fn sample() -> anyhow::Result<()> {
191    /// use examples::Client; // Placeholder for examples
192    /// let client = Client::builder()
193    ///     .with_endpoint("http://private.googleapis.com")
194    ///     .build().await?;
195    /// # 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 observability signals for the client.
203    ///
204    /// # Example
205    /// ```
206    /// # use google_cloud_gax::client_builder::examples;
207    /// # use google_cloud_gax::client_builder::Result;
208    /// # async fn sample() -> anyhow::Result<()> {
209    /// use examples::Client; // Placeholder for examples
210    /// let client = Client::builder()
211    ///     .with_tracing()
212    ///     .build().await?;
213    /// // For observing traces and logs, you must also enable a tracing subscriber in your `main` function,
214    /// // for example:
215    /// //     tracing_subscriber::fmt::init();
216    /// // For observing metrics, you must also install an OpenTelemetry meter provider in your `main` function,
217    /// // for example:
218    /// //     opentelemetry::global::set_meter_provider(provider.clone());
219    /// # Ok(()) }
220    /// ```
221    ///
222    /// <div class="warning">
223    ///
224    /// Observability signals at any level may contain sensitive data such as resource names, full
225    /// URLs, and error messages.
226    ///
227    /// Before configuring subscribers or exporters for traces and logs, review the contents of the
228    /// spans and consult the [tracing] framework documentation to set up filters and formatters to
229    /// prevent leaking sensitive information, depending on your intended use case.
230    ///
231    /// [OpenTelemetry Semantic Conventions]: https://opentelemetry.io/docs/concepts/semantic-conventions/
232    /// [tracing]: https://docs.rs/tracing/latest/tracing/
233    ///
234    /// </div>
235    ///
236    /// The libraries are instrumented to generate the following signals:
237    ///
238    /// 1. `INFO` spans for each logical client request. Typically a single method call in the client
239    ///    struct gets such a span.
240    /// 1. A histogram metric measuring the elapsed time for each logical client request.
241    /// 1. `WARN` logs for each logical client requests that fail.
242    /// 1. `INFO` spans for each low-level attempt RPC attempt. Typically a single method in the client
243    ///    struct gets one such span, but there may be more if the library had to retry the RPC.
244    /// 1. `DEBUG` logs for each low-level attempt that fails.
245    ///
246    /// These spans and logs follow [OpenTelemetry Semantic Conventions] with additional Google
247    /// Cloud attributes. Both the spans and logs and are should be suitable for production
248    /// monitoring.
249    ///
250    /// The libraries also have `DEBUG` spans for each request, these include the full request body,
251    /// and the full response body for successful requests, and the full error message, with
252    /// details, for failed requests. Consider the contents of these requests and responses before
253    /// enabling them in production environments, as the request or responses may include sensitive
254    /// data. These `DEBUG` spans use the client library crate followed by `::tracing` as their
255    /// target and the method name as the span name. You can use the name and/or target to set up
256    /// your filters.
257    ///
258    /// # More information
259    ///
260    /// The [Enable logging] guide shows you how to initialize a subscriber to
261    /// log events to the console.
262    ///
263    /// [Enable logging]: https://docs.cloud.google.com/rust/enable-logging
264    pub fn with_tracing(mut self) -> Self {
265        self.config.tracing = true;
266        self
267    }
268
269    /// Configure the authentication credentials.
270    ///
271    /// Most Google Cloud services require authentication, though some services
272    /// allow for anonymous access, and some services provide emulators where
273    /// no authentication is required. More information about valid credentials
274    /// types can be found in the [google-cloud-auth] crate documentation.
275    ///
276    /// ```
277    /// # use google_cloud_gax::client_builder::examples;
278    /// # use google_cloud_gax::client_builder::Result;
279    /// # async fn sample() -> anyhow::Result<()> {
280    /// use examples::Client; // Placeholder for examples
281    /// // Placeholder, normally use google_cloud_auth::credentials
282    /// use examples::credentials;
283    /// let client = Client::builder()
284    ///     .with_credentials(
285    ///         credentials::mds::Builder::new()
286    ///             .scopes(["https://www.googleapis.com/auth/cloud-platform.read-only"])
287    ///             .build())
288    ///     .build().await?;
289    /// # Ok(()) }
290    /// ```
291    ///
292    /// [google-cloud-auth]: https://docs.rs/google-cloud-auth
293    pub fn with_credentials<T: Into<Cr>>(mut self, v: T) -> Self {
294        self.config.cred = Some(v.into());
295        self
296    }
297
298    /// Configure the universe domain.
299    ///
300    /// The universe domain is the default service domain for a given cloud universe.
301    /// The default value is "googleapis.com".
302    // TODO(#3646): Make this public and let example run when universe domain support is done.
303    #[allow(dead_code)]
304    pub(crate) fn with_universe_domain<V: Into<String>>(mut self, v: V) -> Self {
305        self.config.universe_domain = Some(v.into());
306        self
307    }
308
309    /// Configure the retry policy.
310    ///
311    /// The client libraries can automatically retry operations that fail. The
312    /// retry policy controls what errors are considered retryable, sets limits
313    /// on the number of attempts or the time trying to make attempts.
314    ///
315    /// ```
316    /// # use google_cloud_gax::client_builder::examples;
317    /// # use google_cloud_gax as gax;
318    /// # use google_cloud_gax::client_builder::Result;
319    /// # async fn sample() -> anyhow::Result<()> {
320    /// use examples::Client; // Placeholder for examples
321    /// use gax::retry_policy::{AlwaysRetry, RetryPolicyExt};
322    /// let client = Client::builder()
323    ///     .with_retry_policy(AlwaysRetry.with_attempt_limit(3))
324    ///     .build().await?;
325    /// # Ok(()) }
326    /// ```
327    pub fn with_retry_policy<V: Into<RetryPolicyArg>>(mut self, v: V) -> Self {
328        self.config.retry_policy = Some(v.into().into());
329        self
330    }
331
332    /// Configure the retry backoff policy.
333    ///
334    /// The client libraries can automatically retry operations that fail. The
335    /// backoff policy controls how long to wait in between retry attempts.
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    /// # async fn sample() -> anyhow::Result<()> {
342    /// use examples::Client; // Placeholder for examples
343    /// use gax::exponential_backoff::ExponentialBackoff;
344    /// use std::time::Duration;
345    /// let policy = ExponentialBackoff::default();
346    /// let client = Client::builder()
347    ///     .with_backoff_policy(policy)
348    ///     .build().await?;
349    /// # Ok(()) }
350    /// ```
351    pub fn with_backoff_policy<V: Into<BackoffPolicyArg>>(mut self, v: V) -> Self {
352        self.config.backoff_policy = Some(v.into().into());
353        self
354    }
355
356    /// Configure the retry throttler.
357    ///
358    /// Advanced applications may want to configure a retry throttler to
359    /// [Address Cascading Failures] and when [Handling Overload] conditions.
360    /// The client libraries throttle their retry loop, using a policy to
361    /// control the throttling algorithm. Use this method to fine tune or
362    /// customize the default retry throtler.
363    ///
364    /// [Handling Overload]: https://sre.google/sre-book/handling-overload/
365    /// [Address Cascading Failures]: https://sre.google/sre-book/addressing-cascading-failures/
366    ///
367    /// ```
368    /// # use google_cloud_gax::client_builder::examples;
369    /// # use google_cloud_gax as gax;
370    /// # use google_cloud_gax::client_builder::Result;
371    /// # async fn sample() -> anyhow::Result<()> {
372    /// use examples::Client; // Placeholder for examples
373    /// use gax::retry_throttler::AdaptiveThrottler;
374    /// let client = Client::builder()
375    ///     .with_retry_throttler(AdaptiveThrottler::default())
376    ///     .build().await?;
377    /// # Ok(()) }
378    /// ```
379    pub fn with_retry_throttler<V: Into<RetryThrottlerArg>>(mut self, v: V) -> Self {
380        self.config.retry_throttler = v.into().into();
381        self
382    }
383
384    /// Configure the polling error policy.
385    ///
386    /// Some clients support long-running operations, the client libraries can
387    /// automatically poll these operations until they complete. Polling may
388    /// fail due to transient errors and applications may want to continue the
389    /// polling loop despite such errors. The polling error policy controls
390    /// which errors are treated as recoverable, and may limit the number
391    /// of attempts and/or the total time polling the operation.
392    ///
393    /// ```
394    /// # use google_cloud_gax::client_builder::examples;
395    /// # use google_cloud_gax as gax;
396    /// # use google_cloud_gax::client_builder::Result;
397    /// # async fn sample() -> anyhow::Result<()> {
398    /// use examples::Client; // Placeholder for examples
399    /// use gax::polling_error_policy::Aip194Strict;
400    /// use gax::polling_error_policy::PollingErrorPolicyExt;
401    /// use std::time::Duration;
402    /// let client = Client::builder()
403    ///     .with_polling_error_policy(Aip194Strict
404    ///         .with_time_limit(Duration::from_secs(15 * 60))
405    ///         .with_attempt_limit(50))
406    ///     .build().await?;
407    /// # Ok(()) }
408    /// ```
409    pub fn with_polling_error_policy<V: Into<PollingErrorPolicyArg>>(mut self, v: V) -> Self {
410        self.config.polling_error_policy = Some(v.into().0);
411        self
412    }
413
414    /// Configure the polling backoff policy.
415    ///
416    /// Some clients support long-running operations, the client libraries can
417    /// automatically poll these operations until they complete. The polling
418    /// backoff policy controls how long the client waits between polling
419    /// attempts.
420    ///
421    /// ```
422    /// # use google_cloud_gax::client_builder::examples;
423    /// # use google_cloud_gax as gax;
424    /// # use google_cloud_gax::client_builder::Result;
425    /// # async fn sample() -> anyhow::Result<()> {
426    /// use examples::Client; // Placeholder for examples
427    /// use gax::exponential_backoff::ExponentialBackoff;
428    /// use std::time::Duration;
429    /// let policy = ExponentialBackoff::default();
430    /// let client = Client::builder()
431    ///     .with_polling_backoff_policy(policy)
432    ///     .build().await?;
433    /// # Ok(()) }
434    /// ```
435    pub fn with_polling_backoff_policy<V: Into<PollingBackoffPolicyArg>>(mut self, v: V) -> Self {
436        self.config.polling_backoff_policy = Some(v.into().0);
437        self
438    }
439}
440
441#[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
442pub mod internal {
443    use super::*;
444
445    pub trait ClientFactory {
446        type Client;
447        type Credentials;
448        fn build(
449            self,
450            config: internal::ClientConfig<Self::Credentials>,
451        ) -> impl Future<Output = Result<Self::Client>>;
452    }
453
454    pub fn new_builder<F, Cr, C>(factory: F) -> super::ClientBuilder<F, Cr>
455    where
456        F: ClientFactory<Client = C, Credentials = Cr>,
457    {
458        super::ClientBuilder {
459            factory,
460            config: ClientConfig::default(),
461        }
462    }
463
464    /// Configure a client.
465    ///
466    /// A client represents a connection to a Google Cloud Service. Each service
467    /// has one or more client types. The default configuration for each client
468    /// should work for most applications. But some applications may need to
469    /// override the default endpoint, the default authentication credentials,
470    /// the retry policies, and/or other behaviors of the client.
471    #[derive(Clone, Debug)]
472    #[non_exhaustive]
473    pub struct ClientConfig<Cr> {
474        pub endpoint: Option<String>,
475        pub universe_domain: Option<String>,
476        pub cred: Option<Cr>,
477        pub tracing: bool,
478        pub retry_policy: Option<Arc<dyn RetryPolicy>>,
479        pub backoff_policy: Option<Arc<dyn BackoffPolicy>>,
480        pub retry_throttler: SharedRetryThrottler,
481        pub polling_error_policy: Option<Arc<dyn PollingErrorPolicy>>,
482        pub polling_backoff_policy: Option<Arc<dyn PollingBackoffPolicy>>,
483        pub disable_automatic_decompression: bool,
484        pub disable_follow_redirects: bool,
485        pub grpc_subchannel_count: Option<usize>,
486        pub grpc_request_buffer_capacity: Option<usize>,
487        pub grpc_max_header_list_size: Option<u32>,
488    }
489
490    impl<Cr> std::default::Default for ClientConfig<Cr> {
491        fn default() -> Self {
492            use crate::retry_throttler::AdaptiveThrottler;
493            use std::sync::{Arc, Mutex};
494            Self {
495                endpoint: None,
496                universe_domain: None,
497                cred: None,
498                tracing: false,
499                retry_policy: None,
500                backoff_policy: None,
501                retry_throttler: Arc::new(Mutex::new(AdaptiveThrottler::default())),
502                polling_error_policy: None,
503                polling_backoff_policy: None,
504                disable_automatic_decompression: false,
505                disable_follow_redirects: false,
506                grpc_subchannel_count: None,
507                grpc_request_buffer_capacity: None,
508                grpc_max_header_list_size: None,
509            }
510        }
511    }
512
513    /// Configure automatic decompression.
514    ///
515    /// By default, the client libraries automatically decompress responses.
516    /// Internal users can disable this behavior if they need to access the raw
517    /// compressed bytes.
518    pub fn with_automatic_decompression<F, Cr>(
519        mut builder: super::ClientBuilder<F, Cr>,
520        v: bool,
521    ) -> super::ClientBuilder<F, Cr> {
522        builder.config.disable_automatic_decompression = !v;
523        builder
524    }
525
526    /// Configure HTTP redirects.
527    ///
528    /// By default, the client libraries automatically follow HTTP redirects.
529    /// Internal users can disable this behavior if they need to handle redirects
530    /// manually (e.g. for 308 Resume Incomplete).
531    pub fn with_follow_redirects<F, Cr>(
532        mut builder: super::ClientBuilder<F, Cr>,
533        v: bool,
534    ) -> super::ClientBuilder<F, Cr> {
535        builder.config.disable_follow_redirects = !v;
536        builder
537    }
538}
539
540#[doc(hidden)]
541pub mod examples {
542    //! This module contains helper types used in the rustdoc examples.
543    //!
544    //! The examples require relatively complex types to be useful.
545
546    type Config = super::internal::ClientConfig<Credentials>;
547    use super::Result;
548
549    /// A client type for use in examples.
550    ///
551    /// This type is used in examples as a placeholder for a real client. It
552    /// does not work, but illustrates how to use `ClientBuilder`.
553    #[allow(dead_code)]
554    pub struct Client(Config);
555    impl Client {
556        /// Create a builder to initialize new instances of this client.
557        pub fn builder() -> client::Builder {
558            super::internal::new_builder(client::Factory)
559        }
560
561        async fn new(config: super::internal::ClientConfig<Credentials>) -> Result<Self> {
562            Ok(Self(config))
563        }
564    }
565    mod client {
566        pub type Builder = super::super::ClientBuilder<Factory, super::Credentials>;
567        pub struct Factory;
568        impl super::super::internal::ClientFactory for Factory {
569            type Credentials = super::Credentials;
570            type Client = super::Client;
571            async fn build(
572                self,
573                config: crate::client_builder::internal::ClientConfig<Self::Credentials>,
574            ) -> super::Result<Self::Client> {
575                Self::Client::new(config).await
576            }
577        }
578    }
579
580    #[derive(Clone, Debug, Default, PartialEq)]
581    pub struct Credentials {
582        pub scopes: Vec<String>,
583    }
584
585    pub mod credentials {
586        pub mod mds {
587            #[derive(Clone, Default)]
588            pub struct Builder(super::super::Credentials);
589            impl Builder {
590                pub fn new() -> Self {
591                    Self(super::super::Credentials::default())
592                }
593                pub fn build(self) -> super::super::Credentials {
594                    self.0
595                }
596                pub fn scopes<I, V>(mut self, iter: I) -> Self
597                where
598                    I: IntoIterator<Item = V>,
599                    V: Into<String>,
600                {
601                    self.0.scopes = iter.into_iter().map(|v| v.into()).collect();
602                    self
603                }
604            }
605        }
606    }
607
608    // We use the examples as scaffolding for the tests.
609    #[cfg(test)]
610    mod tests {
611        use super::*;
612
613        #[tokio::test]
614        async fn build_default() {
615            let client = Client::builder().build().await.unwrap();
616            let config = client.0;
617            assert_eq!(config.endpoint, None);
618            assert_eq!(config.cred, None);
619            assert!(!config.tracing);
620            assert!(
621                format!("{:?}", &config).contains("AdaptiveThrottler"),
622                "{config:?}"
623            );
624            assert!(config.retry_policy.is_none(), "{config:?}");
625            assert!(config.backoff_policy.is_none(), "{config:?}");
626            assert!(config.polling_error_policy.is_none(), "{config:?}");
627            assert!(config.polling_backoff_policy.is_none(), "{config:?}");
628            assert!(!config.disable_automatic_decompression, "{config:?}");
629            assert!(!config.disable_follow_redirects, "{config:?}");
630        }
631
632        #[tokio::test]
633        async fn endpoint() {
634            let client = Client::builder()
635                .with_endpoint("http://example.com")
636                .build()
637                .await
638                .unwrap();
639            let config = client.0;
640            assert_eq!(config.endpoint.as_deref(), Some("http://example.com"));
641        }
642
643        #[tokio::test]
644        async fn tracing() {
645            let client = Client::builder().with_tracing().build().await.unwrap();
646            let config = client.0;
647            assert!(config.tracing);
648        }
649
650        #[tokio::test]
651        async fn automatic_decompression() {
652            let client = Client::builder();
653            let client = super::super::internal::with_automatic_decompression(client, false)
654                .build()
655                .await
656                .unwrap();
657            let config = client.0;
658            assert!(config.disable_automatic_decompression);
659
660            let client = Client::builder();
661            let client = super::super::internal::with_automatic_decompression(client, true)
662                .build()
663                .await
664                .unwrap();
665            let config = client.0;
666            assert!(!config.disable_automatic_decompression);
667        }
668
669        #[tokio::test]
670        async fn follow_redirects() {
671            let client = Client::builder();
672            let client = super::super::internal::with_follow_redirects(client, false)
673                .build()
674                .await
675                .unwrap();
676            let config = client.0;
677            assert!(config.disable_follow_redirects);
678
679            let client = Client::builder();
680            let client = super::super::internal::with_follow_redirects(client, true)
681                .build()
682                .await
683                .unwrap();
684            let config = client.0;
685            assert!(!config.disable_follow_redirects);
686        }
687
688        #[tokio::test]
689        async fn credentials() {
690            let client = Client::builder()
691                .with_credentials(
692                    credentials::mds::Builder::new()
693                        .scopes(["test-scope"])
694                        .build(),
695                )
696                .build()
697                .await
698                .unwrap();
699            let config = client.0;
700            let cred = config.cred.unwrap();
701            assert_eq!(cred.scopes, vec!["test-scope".to_string()]);
702        }
703
704        #[tokio::test]
705        async fn universe_domain() {
706            let client = Client::builder()
707                .with_universe_domain("some-universe-domain.com")
708                .build()
709                .await
710                .unwrap();
711            let config = client.0;
712            assert_eq!(
713                config.universe_domain,
714                Some("some-universe-domain.com".to_string())
715            );
716        }
717
718        #[tokio::test]
719        async fn retry_policy() {
720            use crate::retry_policy::RetryPolicyExt;
721            let client = Client::builder()
722                .with_retry_policy(crate::retry_policy::AlwaysRetry.with_attempt_limit(3))
723                .build()
724                .await
725                .unwrap();
726            let config = client.0;
727            assert!(config.retry_policy.is_some(), "{config:?}");
728        }
729
730        #[tokio::test]
731        async fn backoff_policy() {
732            let client = Client::builder()
733                .with_backoff_policy(crate::exponential_backoff::ExponentialBackoff::default())
734                .build()
735                .await
736                .unwrap();
737            let config = client.0;
738            assert!(config.backoff_policy.is_some(), "{config:?}");
739        }
740
741        #[tokio::test]
742        async fn retry_throttler() {
743            use crate::retry_throttler::CircuitBreaker;
744            let client = Client::builder()
745                .with_retry_throttler(CircuitBreaker::default())
746                .build()
747                .await
748                .unwrap();
749            let config = client.0;
750            assert!(
751                format!("{:?}", &config).contains("CircuitBreaker"),
752                "{config:?}"
753            );
754        }
755
756        #[tokio::test]
757        async fn polling_error_policy() {
758            use crate::polling_error_policy::PollingErrorPolicyExt;
759            let client = Client::builder()
760                .with_polling_error_policy(
761                    crate::polling_error_policy::AlwaysContinue.with_attempt_limit(3),
762                )
763                .build()
764                .await
765                .unwrap();
766            let config = client.0;
767            assert!(config.polling_error_policy.is_some(), "{config:?}");
768        }
769
770        #[tokio::test]
771        async fn polling_backoff_policy() {
772            let client = Client::builder()
773                .with_polling_backoff_policy(
774                    crate::exponential_backoff::ExponentialBackoff::default(),
775                )
776                .build()
777                .await
778                .unwrap();
779            let config = client.0;
780            assert!(config.polling_backoff_policy.is_some(), "{config:?}");
781        }
782    }
783}
784
785#[cfg(test)]
786mod tests {
787    use super::*;
788    use std::error::Error as _;
789
790    #[test]
791    fn error_credentials() {
792        let source = wkt::TimestampError::OutOfRange;
793        let error = Error::cred(source);
794        assert!(error.is_default_credentials(), "{error:?}");
795        assert!(error.to_string().contains("default credentials"), "{error}");
796        let got = error
797            .source()
798            .and_then(|e| e.downcast_ref::<wkt::TimestampError>());
799        assert!(
800            matches!(got, Some(wkt::TimestampError::OutOfRange)),
801            "{error:?}"
802        );
803    }
804
805    #[test]
806    fn transport() {
807        let source = wkt::TimestampError::OutOfRange;
808        let error = Error::transport(source);
809        assert!(error.is_transport(), "{error:?}");
810        assert!(error.to_string().contains("transport client"), "{error}");
811        let got = error
812            .source()
813            .and_then(|e| e.downcast_ref::<wkt::TimestampError>());
814        assert!(
815            matches!(got, Some(wkt::TimestampError::OutOfRange)),
816            "{error:?}"
817        );
818    }
819}