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