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}