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