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}