google_cloud_gax/options/
mod.rs

1// Copyright 2024 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//! Client configuration and per request options.
16//!
17//! While the client library  defaults are intended to work for most
18//! applications, it is sometimes necessary to change the configuration. Notably
19//! the default endpoint, and the default authentication credentials do not work
20//! for some applications.
21//!
22//! Likewise, applications may need to customize the behavior of some calls made
23//! via a client, even a customized one. Applications sometimes change the
24//! timeout for an specific call, or change the retry configuration. The
25//! `*Builder` returned by each client method implements the
26//! [RequestOptionsBuilder] trait where applications can override some defaults.
27
28use crate::backoff_policy::{BackoffPolicy, BackoffPolicyArg};
29use crate::polling_backoff_policy::{PollingBackoffPolicy, PollingBackoffPolicyArg};
30use crate::polling_policy::{PollingPolicy, PollingPolicyArg};
31use crate::retry_policy::{RetryPolicy, RetryPolicyArg};
32use crate::retry_throttler::{RetryThrottlerArg, RetryThrottlerWrapped};
33use auth::credentials::Credential;
34use std::sync::Arc;
35
36/// A set of options configuring a single request.
37///
38/// Application only use this class directly in mocks, where they may want to
39/// verify their application has configured all the right request parameters and
40/// options.
41///
42/// All other code uses this type indirectly, via the per-request builders.
43#[derive(Clone, Debug, Default)]
44pub struct RequestOptions {
45    pub(crate) idempotent: Option<bool>,
46    user_agent: Option<String>,
47    attempt_timeout: Option<std::time::Duration>,
48    pub(crate) retry_policy: Option<Arc<dyn RetryPolicy>>,
49    pub(crate) backoff_policy: Option<Arc<dyn BackoffPolicy>>,
50    pub(crate) retry_throttler: Option<RetryThrottlerWrapped>,
51    pub(crate) polling_policy: Option<Arc<dyn PollingPolicy>>,
52    pub(crate) polling_backoff_policy: Option<Arc<dyn PollingBackoffPolicy>>,
53}
54
55impl RequestOptions {
56    /// Treat the RPC underlying RPC in this method as idempotent.
57    ///
58    /// If a retry policy is configured, the policy may examine the idempotency
59    /// and the error details to decide if the error is retryable. Typically
60    /// [idempotent] RPCs are safe to retry under more error conditions
61    /// than non-idempotent RPCs.
62    ///
63    /// The client libraries provide a default for RPC idempotency, based on the
64    /// HTTP method (`GET`, `POST`, `DELETE`, etc.).
65    ///
66    /// [idempotent]: https://en.wikipedia.org/wiki/Idempotence
67    pub fn set_idempotency(&mut self, value: bool) {
68        self.idempotent = Some(value);
69    }
70
71    /// Set the idempotency for the underlying RPC unless it is already set.
72    ///
73    /// If [set_idempotency][Self::set_idempotency] was already called this
74    /// method has no effect. Otherwise it sets the idempotency. The client
75    /// libraries use this to provide a default idempotency value.
76    pub fn set_default_idempotency(mut self, default: bool) -> Self {
77        self.idempotent.get_or_insert(default);
78        self
79    }
80
81    /// Prepends this prefix to the user agent header value.
82    pub fn set_user_agent<T: Into<String>>(&mut self, v: T) {
83        self.user_agent = Some(v.into());
84    }
85
86    /// Gets the current user-agent prefix
87    pub fn user_agent(&self) -> &Option<String> {
88        &self.user_agent
89    }
90
91    /// Sets the per-attempt timeout.
92    ///
93    /// When using a retry loop, this affects the timeout for each attempt. The
94    /// overall timeout for a request is set by the retry policy.
95    pub fn set_attempt_timeout<T: Into<std::time::Duration>>(&mut self, v: T) {
96        self.attempt_timeout = Some(v.into());
97    }
98
99    /// Gets the current per-attempt timeout.
100    pub fn attempt_timeout(&self) -> &Option<std::time::Duration> {
101        &self.attempt_timeout
102    }
103
104    /// Sets the retry policy configuration.
105    pub fn set_retry_policy<V: Into<RetryPolicyArg>>(&mut self, v: V) {
106        self.retry_policy = Some(v.into().0);
107    }
108
109    /// Sets the backoff policy configuration.
110    pub fn set_backoff_policy<V: Into<BackoffPolicyArg>>(&mut self, v: V) {
111        self.backoff_policy = Some(v.into().0);
112    }
113
114    /// Sets the retry throttling configuration.
115    pub fn set_retry_throttler<V: Into<RetryThrottlerArg>>(&mut self, v: V) {
116        self.retry_throttler = Some(v.into().0);
117    }
118
119    /// Sets the polling policy configuration.
120    pub fn set_polling_policy<V: Into<PollingPolicyArg>>(&mut self, v: V) {
121        self.polling_policy = Some(v.into().0);
122    }
123
124    /// Sets the backoff policy configuration.
125    pub fn set_polling_backoff_policy<V: Into<PollingBackoffPolicyArg>>(&mut self, v: V) {
126        self.polling_backoff_policy = Some(v.into().0);
127    }
128}
129
130/// Implementations of this trait provide setters to configure request options.
131///
132/// The Google Cloud Client Libraries for Rust provide a builder for each RPC.
133/// These builders can be used to set the request parameters, e.g., the name of
134/// the resource targeted by the RPC, as well as any options affecting the
135/// request, such as additional headers or timeouts.
136pub trait RequestOptionsBuilder {
137    /// If `v` is `true`, treat the RPC underlying this method as idempotent.
138    fn with_idempotency(self, v: bool) -> Self;
139
140    /// Set the user agent header.
141    fn with_user_agent<V: Into<String>>(self, v: V) -> Self;
142
143    /// Sets the per-attempt timeout.
144    ///
145    /// When using a retry loop, this affects the timeout for each attempt. The
146    /// overall timeout for a request is set by the retry policy.
147    fn with_attempt_timeout<V: Into<std::time::Duration>>(self, v: V) -> Self;
148
149    /// Sets the retry policy configuration.
150    fn with_retry_policy<V: Into<RetryPolicyArg>>(self, v: V) -> Self;
151
152    /// Sets the backoff policy configuration.
153    fn with_backoff_policy<V: Into<BackoffPolicyArg>>(self, v: V) -> Self;
154
155    /// Sets the retry throttler configuration.
156    fn with_retry_throttler<V: Into<RetryThrottlerArg>>(self, v: V) -> Self;
157
158    /// Sets the polling policy configuration.
159    fn with_polling_policy<V: Into<PollingPolicyArg>>(self, v: V) -> Self;
160
161    /// Sets the polling backoff policy configuration.
162    fn with_polling_backoff_policy<V: Into<PollingBackoffPolicyArg>>(self, v: V) -> Self;
163}
164
165/// Simplify implementation of the [RequestOptionsBuilder] trait in generated
166/// code.
167///
168/// This is an implementation detail, most applications have little need to
169/// worry about or use this trait.
170pub trait RequestBuilder {
171    fn request_options(&mut self) -> &mut RequestOptions;
172}
173
174/// Implements the [RequestOptionsBuilder] trait for any [RequestBuilder]
175/// implementation.
176impl<T> RequestOptionsBuilder for T
177where
178    T: RequestBuilder,
179{
180    fn with_idempotency(mut self, v: bool) -> Self {
181        self.request_options().set_idempotency(v);
182        self
183    }
184
185    fn with_user_agent<V: Into<String>>(mut self, v: V) -> Self {
186        self.request_options().set_user_agent(v);
187        self
188    }
189
190    fn with_attempt_timeout<V: Into<std::time::Duration>>(mut self, v: V) -> Self {
191        self.request_options().set_attempt_timeout(v);
192        self
193    }
194
195    fn with_retry_policy<V: Into<RetryPolicyArg>>(mut self, v: V) -> Self {
196        self.request_options().set_retry_policy(v);
197        self
198    }
199
200    fn with_backoff_policy<V: Into<BackoffPolicyArg>>(mut self, v: V) -> Self {
201        self.request_options().set_backoff_policy(v);
202        self
203    }
204
205    fn with_retry_throttler<V: Into<RetryThrottlerArg>>(mut self, v: V) -> Self {
206        self.request_options().set_retry_throttler(v);
207        self
208    }
209
210    fn with_polling_policy<V: Into<PollingPolicyArg>>(mut self, v: V) -> Self {
211        self.request_options().set_polling_policy(v);
212        self
213    }
214
215    fn with_polling_backoff_policy<V: Into<PollingBackoffPolicyArg>>(mut self, v: V) -> Self {
216        self.request_options().set_polling_backoff_policy(v);
217        self
218    }
219}
220
221/// Configure a client.
222///
223/// A client represents a connection to a Google Cloud Service. Each service
224/// has one or more client types. The default configuration for each client
225/// should work for most applications. But some applications may need to
226/// override the default endpoint, the default authentication credentials,
227/// the retry policies, and/or other behaviors of the client.
228pub struct ClientConfig {
229    pub(crate) endpoint: Option<String>,
230    pub(crate) cred: Option<Credential>,
231    pub(crate) tracing: bool,
232    pub(crate) retry_policy: Option<Arc<dyn RetryPolicy>>,
233    pub(crate) backoff_policy: Option<Arc<dyn BackoffPolicy>>,
234    pub(crate) retry_throttler: RetryThrottlerWrapped,
235    pub(crate) polling_policy: Option<Arc<dyn PollingPolicy>>,
236    pub(crate) polling_backoff_policy: Option<Arc<dyn PollingBackoffPolicy>>,
237}
238
239const LOGGING_VAR: &str = "GOOGLE_CLOUD_RUST_LOGGING";
240
241impl ClientConfig {
242    /// Returns a default [ClientConfig].
243    pub fn new() -> Self {
244        Self::default()
245    }
246
247    pub fn tracing_enabled(&self) -> bool {
248        if self.tracing {
249            return true;
250        }
251        std::env::var(LOGGING_VAR)
252            .map(|v| v == "true")
253            .unwrap_or(false)
254    }
255
256    /// Sets an endpoint that overrides the default endpoint for a service.
257    pub fn set_endpoint<T: Into<String>>(mut self, v: T) -> Self {
258        self.endpoint = Some(v.into());
259        self
260    }
261
262    /// Enables tracing.
263    pub fn enable_tracing(mut self) -> Self {
264        self.tracing = true;
265        self
266    }
267
268    /// Disables tracing.
269    pub fn disable_tracing(mut self) -> Self {
270        self.tracing = false;
271        self
272    }
273
274    /// Configure the authentication credentials.
275    pub fn set_credential<T: Into<Option<Credential>>>(mut self, v: T) -> Self {
276        self.cred = v.into();
277        self
278    }
279
280    /// Configure the retry policy.
281    pub fn set_retry_policy<V: Into<RetryPolicyArg>>(mut self, v: V) -> Self {
282        self.retry_policy = Some(v.into().0);
283        self
284    }
285
286    /// Configure the retry backoff policy.
287    pub fn set_backoff_policy<V: Into<BackoffPolicyArg>>(mut self, v: V) -> Self {
288        self.backoff_policy = Some(v.into().0);
289        self
290    }
291
292    /// Configure the retry throttler.
293    pub fn set_retry_throttler<V: Into<RetryThrottlerArg>>(mut self, v: V) -> Self {
294        self.retry_throttler = v.into().0;
295        self
296    }
297
298    /// Configure the polling backoff policy.
299    pub fn set_polling_policy<V: Into<PollingPolicyArg>>(mut self, v: V) -> Self {
300        self.polling_policy = Some(v.into().0);
301        self
302    }
303
304    /// Configure the polling backoff policy.
305    pub fn set_polling_backoff_policy<V: Into<PollingBackoffPolicyArg>>(mut self, v: V) -> Self {
306        self.polling_backoff_policy = Some(v.into().0);
307        self
308    }
309}
310
311impl std::default::Default for ClientConfig {
312    fn default() -> Self {
313        use crate::retry_throttler::AdaptiveThrottler;
314        use std::sync::{Arc, Mutex};
315        Self {
316            endpoint: None,
317            cred: None,
318            tracing: false,
319            retry_policy: None,
320            backoff_policy: None,
321            retry_throttler: Arc::new(Mutex::new(AdaptiveThrottler::default())),
322            polling_policy: None,
323            polling_backoff_policy: None,
324        }
325    }
326}
327
328#[cfg(test)]
329mod test {
330    use super::*;
331    use crate::exponential_backoff::ExponentialBackoffBuilder;
332    use crate::polling_policy;
333    use crate::retry_policy::LimitedAttemptCount;
334    use crate::retry_throttler::AdaptiveThrottler;
335    use std::time::Duration;
336    type Result = std::result::Result<(), Box<dyn std::error::Error>>;
337
338    #[derive(Debug, Default)]
339    struct TestBuilder {
340        request_options: RequestOptions,
341    }
342    impl RequestBuilder for TestBuilder {
343        fn request_options(&mut self) -> &mut RequestOptions {
344            &mut self.request_options
345        }
346    }
347
348    #[test]
349    fn request_options() {
350        let mut opts = RequestOptions::default();
351
352        assert_eq!(opts.idempotent, None);
353        opts.set_idempotency(true);
354        assert_eq!(opts.idempotent, Some(true));
355        opts.set_idempotency(false);
356        assert_eq!(opts.idempotent, Some(false));
357
358        opts.set_user_agent("test-only");
359        assert_eq!(opts.user_agent().as_deref(), Some("test-only"));
360        assert_eq!(opts.attempt_timeout(), &None);
361
362        let d = Duration::from_secs(123);
363        opts.set_attempt_timeout(d);
364        assert_eq!(opts.user_agent().as_deref(), Some("test-only"));
365        assert_eq!(opts.attempt_timeout(), &Some(d));
366
367        opts.set_retry_policy(LimitedAttemptCount::new(3));
368        assert!(opts.retry_policy.is_some(), "{opts:?}");
369
370        opts.set_backoff_policy(ExponentialBackoffBuilder::new().clamp());
371        assert!(opts.backoff_policy.is_some(), "{opts:?}");
372
373        opts.set_retry_throttler(AdaptiveThrottler::default());
374        assert!(opts.retry_throttler.is_some(), "{opts:?}");
375
376        opts.set_polling_policy(polling_policy::Aip194Strict);
377        assert!(opts.polling_policy.is_some(), "{opts:?}");
378
379        opts.set_polling_backoff_policy(ExponentialBackoffBuilder::new().clamp());
380        assert!(opts.polling_backoff_policy.is_some(), "{opts:?}");
381    }
382
383    #[test]
384    fn request_options_idempotency() {
385        let opts = RequestOptions::default().set_default_idempotency(true);
386        assert_eq!(opts.idempotent, Some(true));
387        let opts = opts.set_default_idempotency(false);
388        assert_eq!(opts.idempotent, Some(true));
389
390        let opts = RequestOptions::default().set_default_idempotency(false);
391        assert_eq!(opts.idempotent, Some(false));
392        let opts = opts.set_default_idempotency(true);
393        assert_eq!(opts.idempotent, Some(false));
394    }
395
396    #[test]
397    fn request_options_builder() -> Result {
398        let mut builder = TestBuilder::default();
399        assert_eq!(builder.request_options().user_agent(), &None);
400        assert_eq!(builder.request_options().attempt_timeout(), &None);
401
402        let mut builder = TestBuilder::default().with_idempotency(true);
403        assert_eq!(builder.request_options().idempotent, Some(true));
404        let mut builder = TestBuilder::default().with_idempotency(false);
405        assert_eq!(builder.request_options().idempotent, Some(false));
406
407        let mut builder = TestBuilder::default().with_user_agent("test-only");
408        assert_eq!(
409            builder.request_options().user_agent().as_deref(),
410            Some("test-only")
411        );
412        assert_eq!(builder.request_options().attempt_timeout(), &None);
413
414        let d = Duration::from_secs(123);
415        let mut builder = TestBuilder::default().with_attempt_timeout(d);
416        assert_eq!(builder.request_options().user_agent(), &None);
417        assert_eq!(builder.request_options().attempt_timeout(), &Some(d));
418
419        let mut builder = TestBuilder::default().with_retry_policy(LimitedAttemptCount::new(3));
420        assert!(
421            builder.request_options().retry_policy.is_some(),
422            "{builder:?}"
423        );
424
425        let mut builder =
426            TestBuilder::default().with_backoff_policy(ExponentialBackoffBuilder::new().build()?);
427        assert!(
428            builder.request_options().backoff_policy.is_some(),
429            "{builder:?}"
430        );
431
432        let mut builder = TestBuilder::default().with_retry_throttler(AdaptiveThrottler::default());
433        assert!(
434            builder.request_options().retry_throttler.is_some(),
435            "{builder:?}"
436        );
437
438        let mut builder = TestBuilder::default().with_polling_policy(polling_policy::Aip194Strict);
439        assert!(
440            builder.request_options().polling_policy.is_some(),
441            "{builder:?}"
442        );
443
444        let mut builder = TestBuilder::default()
445            .with_polling_backoff_policy(ExponentialBackoffBuilder::new().build()?);
446        assert!(
447            builder.request_options().polling_backoff_policy.is_some(),
448            "{builder:?}"
449        );
450
451        Ok(())
452    }
453
454    // This test must run serially because `std::env::remove_var` and
455    // `std::env::set_var` are unsafe otherwise.
456    #[test]
457    #[serial_test::serial]
458    fn config_tracing() {
459        unsafe {
460            std::env::remove_var(LOGGING_VAR);
461        }
462        let config = ClientConfig::new();
463        assert!(!config.tracing_enabled(), "expected tracing to be disabled");
464        let config = ClientConfig::new().enable_tracing();
465        assert!(config.tracing_enabled(), "expected tracing to be enabled");
466        let config = config.disable_tracing();
467        assert!(
468            !config.tracing_enabled(),
469            "expected tracing to be disaabled"
470        );
471
472        unsafe {
473            std::env::set_var(LOGGING_VAR, "true");
474        }
475        let config = ClientConfig::new();
476        assert!(config.tracing_enabled(), "expected tracing to be enabled");
477
478        unsafe {
479            std::env::set_var(LOGGING_VAR, "not-true");
480        }
481        let config = ClientConfig::new();
482        assert!(!config.tracing_enabled(), "expected tracing to be disabled");
483    }
484
485    #[test]
486    fn config_endpoint() {
487        let config = ClientConfig::new().set_endpoint("http://storage.googleapis.com");
488        assert_eq!(
489            config.endpoint,
490            Some("http://storage.googleapis.com".to_string())
491        );
492    }
493
494    #[tokio::test]
495    async fn config_credentials() -> Result {
496        let config =
497            ClientConfig::new().set_credential(auth::credentials::testing::test_credentials());
498        let cred = config.cred.unwrap();
499        let token = cred.get_token().await?;
500        assert_eq!(token.token, "test-only-token");
501        Ok(())
502    }
503
504    #[test]
505    fn config_retry_policy() {
506        let config = ClientConfig::new().set_retry_policy(LimitedAttemptCount::new(5));
507        assert!(config.retry_policy.is_some());
508    }
509
510    #[test]
511    fn config_backoff() {
512        let config =
513            ClientConfig::new().set_backoff_policy(ExponentialBackoffBuilder::new().clamp());
514        assert!(config.backoff_policy.is_some());
515    }
516
517    fn map_lock_err<T>(e: std::sync::PoisonError<T>) -> Box<dyn std::error::Error> {
518        format!("cannot acquire lock {e}").into()
519    }
520
521    #[test]
522    fn config_retry_throttler() -> Result {
523        use crate::retry_throttler::CircuitBreaker;
524        let config = ClientConfig::new();
525        let throttler = config.retry_throttler.lock().map_err(map_lock_err)?;
526        assert!(!throttler.throttle_retry_attempt());
527
528        let config = ClientConfig::new().set_retry_throttler(CircuitBreaker::default());
529        let throttler = config.retry_throttler.lock().map_err(map_lock_err)?;
530        assert!(!throttler.throttle_retry_attempt());
531
532        Ok(())
533    }
534
535    #[test]
536    fn config_polling() {
537        let config = ClientConfig::new().set_polling_policy(polling_policy::AlwaysContinue);
538        assert!(config.polling_policy.is_some());
539    }
540
541    #[test]
542    fn config_polling_backoff() {
543        let config = ClientConfig::new()
544            .set_polling_backoff_policy(ExponentialBackoffBuilder::new().clamp());
545        assert!(config.polling_backoff_policy.is_some());
546    }
547}