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