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 ///
594 /// Values are stored in an [`Arc`] so that [`Extensions`] can be
595 /// cloned cheaply without requiring extensions to implement [`Clone`].
596 #[derive(Clone, Default)]
597 pub struct Extensions {
598 map: HashMap<TypeId, Arc<dyn Any + Send + Sync>>,
599 }
600
601 impl Extensions {
602 pub fn new() -> Self {
603 Self::default()
604 }
605
606 pub fn insert<T: Send + Sync + 'static>(&mut self, val: T) {
607 self.map.insert(TypeId::of::<T>(), Arc::new(val));
608 }
609
610 pub fn get<T: 'static>(&self) -> Option<&T> {
611 self.map
612 .get(&TypeId::of::<T>())
613 .and_then(|boxed| boxed.as_ref().downcast_ref::<T>())
614 }
615
616 /// Removes the extension of type `T`, returning it as an [`Arc`] if present.
617 pub fn remove<T: Send + Sync + 'static>(&mut self) -> Option<Arc<T>> {
618 self.map
619 .remove(&TypeId::of::<T>())
620 .and_then(|arc| arc.downcast::<T>().ok())
621 }
622 }
623
624 impl Debug for Extensions {
625 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
626 f.debug_struct("Extensions")
627 .field("len", &self.map.len())
628 .finish()
629 }
630 }
631
632 /// Configure automatic decompression.
633 ///
634 /// By default, the client libraries automatically decompress responses.
635 /// Internal users can disable this behavior if they need to access the raw
636 /// compressed bytes.
637 pub fn with_automatic_decompression<F, Cr>(
638 mut builder: super::ClientBuilder<F, Cr>,
639 v: bool,
640 ) -> super::ClientBuilder<F, Cr> {
641 builder.config.disable_automatic_decompression = !v;
642 builder
643 }
644
645 /// Configure HTTP redirects.
646 ///
647 /// By default, the client libraries automatically follow HTTP redirects.
648 /// Internal users can disable this behavior if they need to handle redirects
649 /// manually (e.g. for 308 Resume Incomplete).
650 pub fn with_follow_redirects<F, Cr>(
651 mut builder: super::ClientBuilder<F, Cr>,
652 v: bool,
653 ) -> super::ClientBuilder<F, Cr> {
654 builder.config.disable_follow_redirects = !v;
655 builder
656 }
657}
658
659#[doc(hidden)]
660pub mod examples {
661 //! This module contains helper types used in the rustdoc examples.
662 //!
663 //! The examples require relatively complex types to be useful.
664
665 type Config = super::internal::ClientConfig<Credentials>;
666 use super::Result;
667
668 /// A client type for use in examples.
669 ///
670 /// This type is used in examples as a placeholder for a real client. It
671 /// does not work, but illustrates how to use `ClientBuilder`.
672 #[allow(dead_code)]
673 pub struct Client(Config);
674 impl Client {
675 /// Create a builder to initialize new instances of this client.
676 pub fn builder() -> client::Builder {
677 super::internal::new_builder(client::Factory)
678 }
679
680 async fn new(config: super::internal::ClientConfig<Credentials>) -> Result<Self> {
681 Ok(Self(config))
682 }
683 }
684 mod client {
685 pub type Builder = super::super::ClientBuilder<Factory, super::Credentials>;
686 pub struct Factory;
687 impl super::super::internal::ClientFactory for Factory {
688 type Credentials = super::Credentials;
689 type Client = super::Client;
690 async fn build(
691 self,
692 config: crate::client_builder::internal::ClientConfig<Self::Credentials>,
693 ) -> super::Result<Self::Client> {
694 Self::Client::new(config).await
695 }
696 }
697 }
698
699 #[derive(Clone, Debug, Default, PartialEq)]
700 pub struct Credentials {
701 pub scopes: Vec<String>,
702 }
703
704 pub mod credentials {
705 pub mod mds {
706 #[derive(Clone, Default)]
707 pub struct Builder(super::super::Credentials);
708 impl Builder {
709 pub fn new() -> Self {
710 Self(super::super::Credentials::default())
711 }
712 pub fn build(self) -> super::super::Credentials {
713 self.0
714 }
715 pub fn scopes<I, V>(mut self, iter: I) -> Self
716 where
717 I: IntoIterator<Item = V>,
718 V: Into<String>,
719 {
720 self.0.scopes = iter.into_iter().map(|v| v.into()).collect();
721 self
722 }
723 }
724 }
725 }
726
727 // We use the examples as scaffolding for the tests.
728 #[cfg(test)]
729 mod tests {
730 use super::*;
731 use std::time::Duration;
732
733 #[tokio::test]
734 async fn build_default() {
735 let client = Client::builder().build().await.unwrap();
736 let config = client.0;
737 assert_eq!(config.endpoint, None);
738 assert_eq!(config.cred, None);
739 assert!(!config.tracing);
740 assert!(
741 format!("{config:?}").contains("AdaptiveThrottler"),
742 "{config:?}"
743 );
744 assert!(config.retry_policy.is_none(), "{config:?}");
745 assert!(config.backoff_policy.is_none(), "{config:?}");
746 assert!(config.polling_error_policy.is_none(), "{config:?}");
747 assert!(config.polling_backoff_policy.is_none(), "{config:?}");
748 assert!(!config.disable_automatic_decompression, "{config:?}");
749 assert!(!config.disable_follow_redirects, "{config:?}");
750 }
751
752 #[tokio::test]
753 async fn endpoint() {
754 let client = Client::builder()
755 .with_endpoint("http://example.com")
756 .build()
757 .await
758 .unwrap();
759 let config = client.0;
760 assert_eq!(config.endpoint.as_deref(), Some("http://example.com"));
761 }
762
763 #[tokio::test]
764 async fn tracing() {
765 let client = Client::builder().with_tracing().build().await.unwrap();
766 let config = client.0;
767 assert!(config.tracing);
768 }
769
770 #[tokio::test]
771 async fn automatic_decompression() {
772 let client = Client::builder();
773 let client = super::super::internal::with_automatic_decompression(client, false)
774 .build()
775 .await
776 .unwrap();
777 let config = client.0;
778 assert!(config.disable_automatic_decompression);
779
780 let client = Client::builder();
781 let client = super::super::internal::with_automatic_decompression(client, true)
782 .build()
783 .await
784 .unwrap();
785 let config = client.0;
786 assert!(!config.disable_automatic_decompression);
787 }
788
789 #[tokio::test]
790 async fn follow_redirects() {
791 let client = Client::builder();
792 let client = super::super::internal::with_follow_redirects(client, false)
793 .build()
794 .await
795 .unwrap();
796 let config = client.0;
797 assert!(config.disable_follow_redirects);
798
799 let client = Client::builder();
800 let client = super::super::internal::with_follow_redirects(client, true)
801 .build()
802 .await
803 .unwrap();
804 let config = client.0;
805 assert!(!config.disable_follow_redirects);
806 }
807
808 #[tokio::test]
809 async fn credentials() {
810 let client = Client::builder()
811 .with_credentials(
812 credentials::mds::Builder::new()
813 .scopes(["test-scope"])
814 .build(),
815 )
816 .build()
817 .await
818 .unwrap();
819 let config = client.0;
820 let cred = config.cred.unwrap();
821 assert_eq!(cred.scopes, vec!["test-scope".to_string()]);
822 }
823
824 #[tokio::test]
825 async fn universe_domain() {
826 let client = Client::builder()
827 .with_universe_domain("some-universe-domain.com")
828 .build()
829 .await
830 .unwrap();
831 let config = client.0;
832 assert_eq!(
833 config.universe_domain,
834 Some("some-universe-domain.com".to_string())
835 );
836 }
837
838 #[tokio::test]
839 async fn attempt_timeout() {
840 let timeout = Duration::from_secs(42);
841 let client = Client::builder()
842 .with_attempt_timeout(timeout)
843 .build()
844 .await
845 .unwrap();
846 let config = client.0;
847 assert_eq!(config.attempt_timeout, Some(timeout));
848 }
849
850 #[tokio::test]
851 async fn retry_policy() {
852 use crate::retry_policy::RetryPolicyExt;
853 let client = Client::builder()
854 .with_retry_policy(crate::retry_policy::AlwaysRetry.with_attempt_limit(3))
855 .build()
856 .await
857 .unwrap();
858 let config = client.0;
859 assert!(config.retry_policy.is_some(), "{config:?}");
860 }
861
862 #[tokio::test]
863 async fn backoff_policy() {
864 let client = Client::builder()
865 .with_backoff_policy(crate::exponential_backoff::ExponentialBackoff::default())
866 .build()
867 .await
868 .unwrap();
869 let config = client.0;
870 assert!(config.backoff_policy.is_some(), "{config:?}");
871 }
872
873 #[tokio::test]
874 async fn retry_throttler() {
875 use crate::retry_throttler::CircuitBreaker;
876 let client = Client::builder()
877 .with_retry_throttler(CircuitBreaker::default())
878 .build()
879 .await
880 .unwrap();
881 let config = client.0;
882 assert!(
883 format!("{config:?}").contains("CircuitBreaker"),
884 "{config:?}"
885 );
886 }
887
888 #[tokio::test]
889 async fn polling_error_policy() {
890 use crate::polling_error_policy::PollingErrorPolicyExt;
891 let client = Client::builder()
892 .with_polling_error_policy(
893 crate::polling_error_policy::AlwaysContinue.with_attempt_limit(3),
894 )
895 .build()
896 .await
897 .unwrap();
898 let config = client.0;
899 assert!(config.polling_error_policy.is_some(), "{config:?}");
900 }
901
902 #[tokio::test]
903 async fn polling_backoff_policy() {
904 let client = Client::builder()
905 .with_polling_backoff_policy(
906 crate::exponential_backoff::ExponentialBackoff::default(),
907 )
908 .build()
909 .await
910 .unwrap();
911 let config = client.0;
912 assert!(config.polling_backoff_policy.is_some(), "{config:?}");
913 }
914 }
915}
916
917#[cfg(test)]
918mod tests {
919 use super::*;
920 use std::error::Error as _;
921 use std::sync::Arc;
922
923 #[test]
924 fn error_credentials() {
925 let source = wkt::TimestampError::OutOfRange;
926 let error = Error::cred(source);
927 assert!(error.is_default_credentials(), "{error:?}");
928 assert!(error.to_string().contains("default credentials"), "{error}");
929 let got = error
930 .source()
931 .and_then(|e| e.downcast_ref::<wkt::TimestampError>());
932 assert!(
933 matches!(got, Some(wkt::TimestampError::OutOfRange)),
934 "{error:?}"
935 );
936 }
937
938 #[test]
939 fn transport() {
940 let source = wkt::TimestampError::OutOfRange;
941 let error = Error::transport(source);
942 assert!(error.is_transport(), "{error:?}");
943 assert!(error.to_string().contains("transport client"), "{error}");
944 let got = error
945 .source()
946 .and_then(|e| e.downcast_ref::<wkt::TimestampError>());
947 assert!(
948 matches!(got, Some(wkt::TimestampError::OutOfRange)),
949 "{error:?}"
950 );
951 }
952
953 #[test]
954 fn universe_domain_mismatch() {
955 let error = Error::universe_domain_mismatch("my-universe.com", "googleapis.com");
956 assert!(error.is_universe_domain_mismatch(), "{error:?}");
957 let fmt = format!("{error:?}");
958 assert!(fmt.contains("my-universe.com"), "{fmt}");
959 assert!(fmt.contains("googleapis.com"), "{fmt}");
960 let got = error.source();
961 assert!(got.is_none(), "{got:?}");
962 }
963
964 #[test]
965 fn client_config_clone_with_extensions() {
966 let mut config = internal::ClientConfig::<()>::default();
967 config.extensions.insert(42i32);
968 let cloned = config.clone();
969 assert_eq!(
970 cloned.extensions.get::<i32>(),
971 Some(&42),
972 "cloned config must contain inserted extension"
973 );
974 }
975
976 #[test]
977 fn extensions_remove() {
978 let mut extensions = Extensions::new();
979
980 // Corner case: removal from empty extensions
981 assert!(
982 extensions.remove::<i32>().is_none(),
983 "removing from empty Extensions must return None"
984 );
985
986 extensions.insert(42i32);
987 assert_eq!(
988 extensions.get::<i32>(),
989 Some(&42),
990 "extensions map get must return inserted value"
991 );
992
993 let removed = extensions.remove::<i32>();
994 assert_eq!(
995 removed.as_deref(),
996 Some(&42),
997 "removed value should match the inserted value"
998 );
999 assert_eq!(
1000 extensions.get::<i32>(),
1001 None,
1002 "extensions map get must return None after removal"
1003 );
1004
1005 let second_removal = extensions.remove::<i32>();
1006 assert!(
1007 second_removal.is_none(),
1008 "removing non-existent element should return None"
1009 );
1010 }
1011
1012 #[test]
1013 fn extensions_remove_heterogeneous_preserves_others() {
1014 let mut extensions = Extensions::new();
1015 extensions.insert(42i32);
1016 extensions.insert("spanner".to_string());
1017
1018 assert_eq!(
1019 format!("{extensions:?}"),
1020 "Extensions { len: 2 }",
1021 "debug format must reflect current extension count"
1022 );
1023
1024 let removed = extensions.remove::<i32>();
1025 assert_eq!(
1026 removed.as_deref(),
1027 Some(&42),
1028 "removed i32 extension must match"
1029 );
1030
1031 // Verify other extension type is completely untouched
1032 assert_eq!(
1033 extensions.get::<String>().map(|value| value.as_str()),
1034 Some("spanner"),
1035 "string extension must remain intact after removing i32"
1036 );
1037 assert_eq!(
1038 format!("{extensions:?}"),
1039 "Extensions { len: 1 }",
1040 "debug format must update after removal"
1041 );
1042
1043 // Verify unwrapping ownership from returned Arc
1044 let arc = extensions
1045 .remove::<String>()
1046 .expect("String extension must be present");
1047 let unwrapped = Arc::try_unwrap(arc).expect("Arc strong count should be 1");
1048 assert_eq!(
1049 unwrapped, "spanner",
1050 "unwrapped extension value must match original"
1051 );
1052 assert_eq!(
1053 format!("{extensions:?}"),
1054 "Extensions { len: 0 }",
1055 "debug format must show len 0 when empty"
1056 );
1057 }
1058
1059 #[test]
1060 fn extensions_overwrite_and_remove() {
1061 let mut extensions = Extensions::new();
1062 extensions.insert(10i32);
1063 extensions.insert(20i32);
1064
1065 assert_eq!(
1066 extensions.get::<i32>(),
1067 Some(&20),
1068 "overwritten extension must return latest value"
1069 );
1070
1071 let removed = extensions.remove::<i32>();
1072 assert_eq!(
1073 removed.as_deref(),
1074 Some(&20),
1075 "removed value must be the overwritten value"
1076 );
1077 assert!(
1078 extensions.get::<i32>().is_none(),
1079 "extension must be completely absent after removal"
1080 );
1081 }
1082
1083 #[test]
1084 fn extensions_remove_non_clone() {
1085 struct NonCloneExtension(i32);
1086
1087 let mut extensions = Extensions::new();
1088 extensions.insert(NonCloneExtension(42));
1089
1090 let removed = extensions.remove::<NonCloneExtension>();
1091 assert_eq!(
1092 removed.as_deref().map(|extension| extension.0),
1093 Some(42),
1094 "non-clone extension must be successfully removed"
1095 );
1096 assert!(
1097 extensions.get::<NonCloneExtension>().is_none(),
1098 "extension must no longer be present after removal"
1099 );
1100 }
1101
1102 #[test]
1103 fn extensions_clone_independence_on_remove() {
1104 let mut original = Extensions::new();
1105 original.insert(42i32);
1106
1107 let mut cloned = original.clone();
1108 let removed = cloned.remove::<i32>();
1109 assert_eq!(
1110 removed.as_deref(),
1111 Some(&42),
1112 "removed value from cloned extensions must match"
1113 );
1114 assert_eq!(
1115 cloned.get::<i32>(),
1116 None,
1117 "cloned extensions must not contain the removed value"
1118 );
1119 assert_eq!(
1120 original.get::<i32>(),
1121 Some(&42),
1122 "original extensions must still retain the value after cloned removal"
1123 );
1124 }
1125
1126 #[test]
1127 fn auto_traits() {
1128 use static_assertions::{assert_impl_all, assert_not_impl_any};
1129 use std::panic::{RefUnwindSafe, UnwindSafe};
1130
1131 assert_impl_all!(ClientBuilder<(), ()>: Send, Sync, Clone, std::fmt::Debug, Unpin);
1132 assert_not_impl_any!(ClientBuilder<(), ()>: RefUnwindSafe, UnwindSafe);
1133
1134 assert_impl_all!(internal::ClientConfig<()>: Send, Sync, Clone, std::fmt::Debug, Default, Unpin);
1135 assert_not_impl_any!(internal::ClientConfig<()>: RefUnwindSafe, UnwindSafe);
1136
1137 assert_impl_all!(internal::Extensions: Send, Sync, Clone, std::fmt::Debug, Default, Unpin);
1138 assert_not_impl_any!(internal::Extensions: RefUnwindSafe, UnwindSafe);
1139 }
1140}