Skip to main content

google_cloud_gax/
options.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_error_policy::{PollingErrorPolicy, PollingErrorPolicyArg};
31use crate::retry_policy::{RetryPolicy, RetryPolicyArg};
32use crate::retry_throttler::{RetryThrottlerArg, SharedRetryThrottler};
33use std::sync::Arc;
34
35/// A set of options configuring a single request.
36///
37/// Application only use this class directly in mocks, where they may want to
38/// verify their application has configured all the right request parameters and
39/// options.
40///
41/// All other code uses this type indirectly, via the per-request builders.
42#[derive(Clone, Debug, Default)]
43pub struct RequestOptions {
44    idempotent: Option<bool>,
45    user_agent: Option<String>,
46    attempt_timeout: Option<std::time::Duration>,
47    retry_policy: Option<Arc<dyn RetryPolicy>>,
48    backoff_policy: Option<Arc<dyn BackoffPolicy>>,
49    retry_throttler: Option<SharedRetryThrottler>,
50    polling_error_policy: Option<Arc<dyn PollingErrorPolicy>>,
51    polling_backoff_policy: Option<Arc<dyn PollingBackoffPolicy>>,
52    extensions: http::Extensions,
53}
54
55impl RequestOptions {
56    /// Gets the idempotency
57    pub fn idempotent(&self) -> Option<bool> {
58        self.idempotent
59    }
60
61    /// Treat the RPC underlying RPC in this method as idempotent.
62    ///
63    /// If a retry policy is configured, the policy may examine the idempotency
64    /// and the error details to decide if the error is retryable. Typically
65    /// [idempotent] RPCs are safe to retry under more error conditions
66    /// than non-idempotent RPCs.
67    ///
68    /// The client libraries provide a default for RPC idempotency, based on the
69    /// HTTP method (`GET`, `POST`, `DELETE`, etc.).
70    ///
71    /// [idempotent]: https://en.wikipedia.org/wiki/Idempotence
72    pub fn set_idempotency(&mut self, value: bool) {
73        self.idempotent = Some(value);
74    }
75
76    /// Set the idempotency for the underlying RPC unless it is already set.
77    ///
78    /// If [set_idempotency][Self::set_idempotency] was already called this
79    /// method has no effect. Otherwise it sets the idempotency. The client
80    /// libraries use this to provide a default idempotency value.
81    pub(crate) fn set_default_idempotency(&mut self, default: bool) {
82        self.idempotent.get_or_insert(default);
83    }
84
85    /// Prepends this prefix to the user agent header value.
86    pub fn set_user_agent<T: Into<String>>(&mut self, v: T) {
87        self.user_agent = Some(v.into());
88    }
89
90    /// Gets the current user-agent prefix
91    pub fn user_agent(&self) -> &Option<String> {
92        &self.user_agent
93    }
94
95    /// Sets the per-attempt timeout.
96    ///
97    /// When using a retry loop, this affects the timeout for each attempt. The
98    /// overall timeout for a request is set by the retry policy.
99    pub fn set_attempt_timeout<T: Into<std::time::Duration>>(&mut self, v: T) {
100        self.attempt_timeout = Some(v.into());
101    }
102
103    /// Gets the current per-attempt timeout.
104    pub fn attempt_timeout(&self) -> &Option<std::time::Duration> {
105        &self.attempt_timeout
106    }
107
108    /// Get the current retry policy override, if any.
109    pub fn retry_policy(&self) -> &Option<Arc<dyn RetryPolicy>> {
110        &self.retry_policy
111    }
112
113    /// Sets the retry policy configuration.
114    pub fn set_retry_policy<V: Into<RetryPolicyArg>>(&mut self, v: V) {
115        self.retry_policy = Some(v.into().into());
116    }
117
118    /// Get the current backoff policy override, if any.
119    pub fn backoff_policy(&self) -> &Option<Arc<dyn BackoffPolicy>> {
120        &self.backoff_policy
121    }
122
123    /// Sets the backoff policy configuration.
124    pub fn set_backoff_policy<V: Into<BackoffPolicyArg>>(&mut self, v: V) {
125        self.backoff_policy = Some(v.into().into());
126    }
127
128    /// Get the current retry throttler override, if any.
129    pub fn retry_throttler(&self) -> &Option<SharedRetryThrottler> {
130        &self.retry_throttler
131    }
132
133    /// Sets the retry throttling configuration.
134    pub fn set_retry_throttler<V: Into<RetryThrottlerArg>>(&mut self, v: V) {
135        self.retry_throttler = Some(v.into().into());
136    }
137
138    /// Get the current polling policy override, if any.
139    pub fn polling_error_policy(&self) -> &Option<Arc<dyn PollingErrorPolicy>> {
140        &self.polling_error_policy
141    }
142
143    /// Sets the polling policy configuration.
144    pub fn set_polling_error_policy<V: Into<PollingErrorPolicyArg>>(&mut self, v: V) {
145        self.polling_error_policy = Some(v.into().0);
146    }
147
148    /// Get the current polling backoff policy override, if any.
149    pub fn polling_backoff_policy(&self) -> &Option<Arc<dyn PollingBackoffPolicy>> {
150        &self.polling_backoff_policy
151    }
152
153    /// Sets the backoff policy configuration.
154    pub fn set_polling_backoff_policy<V: Into<PollingBackoffPolicyArg>>(&mut self, v: V) {
155        self.polling_backoff_policy = Some(v.into().0);
156    }
157}
158
159/// Implementations of this trait provide setters to configure request options.
160///
161/// The Google Cloud Client Libraries for Rust provide a builder for each RPC.
162/// These builders can be used to set the request parameters, e.g., the name of
163/// the resource targeted by the RPC, as well as any options affecting the
164/// request, such as additional headers or timeouts.
165pub trait RequestOptionsBuilder: internal::RequestBuilder {
166    /// If `v` is `true`, treat the RPC underlying this method as idempotent.
167    fn with_idempotency(self, v: bool) -> Self;
168
169    /// Set the user agent header.
170    fn with_user_agent<V: Into<String>>(self, v: V) -> Self;
171
172    /// Sets the per-attempt timeout.
173    ///
174    /// When using a retry loop, this affects the timeout for each attempt. The
175    /// overall timeout for a request is set by the retry policy.
176    fn with_attempt_timeout<V: Into<std::time::Duration>>(self, v: V) -> Self;
177
178    /// Sets the retry policy configuration.
179    fn with_retry_policy<V: Into<RetryPolicyArg>>(self, v: V) -> Self;
180
181    /// Sets the backoff policy configuration.
182    fn with_backoff_policy<V: Into<BackoffPolicyArg>>(self, v: V) -> Self;
183
184    /// Sets the retry throttler configuration.
185    fn with_retry_throttler<V: Into<RetryThrottlerArg>>(self, v: V) -> Self;
186
187    /// Sets the polling error policy configuration.
188    fn with_polling_error_policy<V: Into<PollingErrorPolicyArg>>(self, v: V) -> Self;
189
190    /// Sets the polling backoff policy configuration.
191    fn with_polling_backoff_policy<V: Into<PollingBackoffPolicyArg>>(self, v: V) -> Self;
192}
193
194#[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
195pub mod internal {
196    //! This module contains implementation details. It is not part of the
197    //! public API. Types and functions in this module may be changed or removed
198    //! without warnings. Applications should not use any types contained
199    //! within.
200    use super::RequestOptions;
201
202    /// Simplify implementation of the [super::RequestOptionsBuilder] trait in
203    /// generated code.
204    ///
205    /// This is an implementation detail, most applications have little need to
206    /// worry about or use this trait.
207    pub trait RequestBuilder {
208        fn request_options(&mut self) -> &mut RequestOptions;
209    }
210
211    pub fn set_default_idempotency(mut options: RequestOptions, default: bool) -> RequestOptions {
212        options.set_default_idempotency(default);
213        options
214    }
215
216    mod sealed {
217        pub trait OptionsExt {}
218    }
219
220    /// Access the `RequestOption` extensions.
221    ///
222    /// The client library internals can use this trait to attach extension
223    /// values to the request options. Possibly passing (nearly) arbitrary types
224    /// between layers.
225    ///
226    /// This is useful when (for example) the tracing layer wants to pass
227    /// information to the HTTP or gRPC client, without having to change all the
228    /// intermediate types, which may include public interfaces.
229    pub trait RequestOptionsExt: sealed::OptionsExt {
230        /// Gets an extension value.
231        fn get_extension<T>(&self) -> Option<&T>
232        where
233            T: Send + Sync + 'static;
234
235        /// Sets an extension value.
236        fn insert_extension<T>(self, value: T) -> Self
237        where
238            T: Clone + Send + Sync + 'static;
239    }
240
241    impl sealed::OptionsExt for RequestOptions {}
242    impl RequestOptionsExt for RequestOptions {
243        fn get_extension<T>(&self) -> Option<&T>
244        where
245            T: Send + Sync + 'static,
246        {
247            self.extensions.get::<T>()
248        }
249
250        fn insert_extension<T>(mut self, value: T) -> Self
251        where
252            T: Clone + Send + Sync + 'static,
253        {
254            let _ = self.extensions.insert(value);
255            self
256        }
257    }
258
259    #[derive(Debug, Clone, Default, PartialEq)]
260    pub struct PathTemplate(pub &'static str);
261
262    #[derive(Debug, Clone, Default, PartialEq)]
263    pub struct ResourceName(pub String);
264
265    // Cannot remove this function, as that would break any client libraries
266    // that are released and use this function.
267    #[deprecated]
268    pub fn set_path_template(
269        options: RequestOptions,
270        path_template: &'static str,
271    ) -> RequestOptions {
272        options.insert_extension(PathTemplate(path_template))
273    }
274
275    // Cannot remove this function, as that would break any client libraries
276    // that are released and use this function.
277    #[deprecated]
278    pub fn get_path_template(options: &RequestOptions) -> Option<&'static str> {
279        options.get_extension::<PathTemplate>().map(|e| e.0)
280    }
281}
282
283/// Implements the sealed [RequestOptionsBuilder] trait.
284impl<T> RequestOptionsBuilder for T
285where
286    T: internal::RequestBuilder,
287{
288    fn with_idempotency(mut self, v: bool) -> Self {
289        self.request_options().set_idempotency(v);
290        self
291    }
292
293    fn with_user_agent<V: Into<String>>(mut self, v: V) -> Self {
294        self.request_options().set_user_agent(v);
295        self
296    }
297
298    fn with_attempt_timeout<V: Into<std::time::Duration>>(mut self, v: V) -> Self {
299        self.request_options().set_attempt_timeout(v);
300        self
301    }
302
303    fn with_retry_policy<V: Into<RetryPolicyArg>>(mut self, v: V) -> Self {
304        self.request_options().set_retry_policy(v);
305        self
306    }
307
308    fn with_backoff_policy<V: Into<BackoffPolicyArg>>(mut self, v: V) -> Self {
309        self.request_options().set_backoff_policy(v);
310        self
311    }
312
313    fn with_retry_throttler<V: Into<RetryThrottlerArg>>(mut self, v: V) -> Self {
314        self.request_options().set_retry_throttler(v);
315        self
316    }
317
318    fn with_polling_error_policy<V: Into<PollingErrorPolicyArg>>(mut self, v: V) -> Self {
319        self.request_options().set_polling_error_policy(v);
320        self
321    }
322
323    fn with_polling_backoff_policy<V: Into<PollingBackoffPolicyArg>>(mut self, v: V) -> Self {
324        self.request_options().set_polling_backoff_policy(v);
325        self
326    }
327}
328
329#[cfg(test)]
330mod tests {
331    use super::internal::*;
332    use super::*;
333    use crate::exponential_backoff::ExponentialBackoffBuilder;
334    use crate::polling_error_policy;
335    use crate::retry_policy::LimitedAttemptCount;
336    use crate::retry_throttler::AdaptiveThrottler;
337    use static_assertions::{assert_impl_all, assert_not_impl_all};
338    use std::panic::{RefUnwindSafe, UnwindSafe};
339    use std::time::Duration;
340
341    #[derive(Debug, Default)]
342    struct TestBuilder {
343        request_options: RequestOptions,
344    }
345    impl RequestBuilder for TestBuilder {
346        fn request_options(&mut self) -> &mut RequestOptions {
347            &mut self.request_options
348        }
349    }
350
351    #[test]
352    fn traits() {
353        assert_impl_all!(RequestOptions: Clone, Send, Sync, Unpin, std::fmt::Debug);
354        assert_not_impl_all!(RequestOptions: RefUnwindSafe, UnwindSafe);
355    }
356
357    #[test]
358    fn request_options() {
359        let mut opts = RequestOptions::default();
360
361        assert_eq!(opts.idempotent, None);
362        opts.set_idempotency(true);
363        assert_eq!(opts.idempotent(), Some(true));
364        opts.set_idempotency(false);
365        assert_eq!(opts.idempotent(), Some(false));
366
367        opts.set_user_agent("test-only");
368        assert_eq!(opts.user_agent().as_deref(), Some("test-only"));
369        assert_eq!(opts.attempt_timeout(), &None);
370
371        let d = Duration::from_secs(123);
372        opts.set_attempt_timeout(d);
373        assert_eq!(opts.user_agent().as_deref(), Some("test-only"));
374        assert_eq!(opts.attempt_timeout(), &Some(d));
375
376        opts.set_retry_policy(LimitedAttemptCount::new(3));
377        assert!(opts.retry_policy().is_some(), "{opts:?}");
378
379        opts.set_backoff_policy(ExponentialBackoffBuilder::new().clamp());
380        assert!(opts.backoff_policy().is_some(), "{opts:?}");
381
382        opts.set_retry_throttler(AdaptiveThrottler::default());
383        assert!(opts.retry_throttler().is_some(), "{opts:?}");
384
385        opts.set_polling_error_policy(polling_error_policy::Aip194Strict);
386        assert!(opts.polling_error_policy().is_some(), "{opts:?}");
387
388        opts.set_polling_backoff_policy(ExponentialBackoffBuilder::new().clamp());
389        assert!(opts.polling_backoff_policy().is_some(), "{opts:?}");
390    }
391
392    #[test]
393    fn request_options_idempotency() {
394        let opts = set_default_idempotency(RequestOptions::default(), true);
395        assert_eq!(opts.idempotent(), Some(true));
396        let opts = set_default_idempotency(opts, false);
397        assert_eq!(opts.idempotent(), Some(true));
398
399        let opts = set_default_idempotency(RequestOptions::default(), false);
400        assert_eq!(opts.idempotent(), Some(false));
401        let opts = set_default_idempotency(opts, true);
402        assert_eq!(opts.idempotent(), Some(false));
403    }
404
405    #[test]
406    fn request_options_ext() {
407        #[derive(Debug, Clone, PartialEq)]
408        struct TestA(&'static str);
409        #[derive(Debug, Clone, PartialEq)]
410        struct TestB(u32);
411
412        let opts = RequestOptions::default();
413        assert!(opts.get_extension::<TestA>().is_none(), "{opts:?}");
414        assert!(opts.get_extension::<TestB>().is_none(), "{opts:?}");
415        let opts = opts.insert_extension(TestA("1"));
416        assert_eq!(opts.get_extension::<TestA>(), Some(&TestA("1")), "{opts:?}");
417        assert!(opts.get_extension::<TestB>().is_none(), "{opts:?}");
418        let opts = opts
419            .insert_extension(TestA("2"))
420            .insert_extension(TestB(42));
421        assert_eq!(opts.get_extension::<TestA>(), Some(&TestA("2")), "{opts:?}");
422        assert_eq!(opts.get_extension::<TestB>(), Some(&TestB(42)), "{opts:?}");
423    }
424
425    #[test]
426    fn request_options_builder() -> anyhow::Result<()> {
427        let mut builder = TestBuilder::default();
428        assert_eq!(builder.request_options().user_agent(), &None);
429        assert_eq!(builder.request_options().attempt_timeout(), &None);
430
431        let mut builder = TestBuilder::default().with_idempotency(true);
432        assert_eq!(builder.request_options().idempotent(), Some(true));
433        let mut builder = TestBuilder::default().with_idempotency(false);
434        assert_eq!(builder.request_options().idempotent(), Some(false));
435
436        let mut builder = TestBuilder::default().with_user_agent("test-only");
437        assert_eq!(
438            builder.request_options().user_agent().as_deref(),
439            Some("test-only")
440        );
441        assert_eq!(builder.request_options().attempt_timeout(), &None);
442
443        let d = Duration::from_secs(123);
444        let mut builder = TestBuilder::default().with_attempt_timeout(d);
445        assert_eq!(builder.request_options().user_agent(), &None);
446        assert_eq!(builder.request_options().attempt_timeout(), &Some(d));
447
448        let mut builder = TestBuilder::default().with_retry_policy(LimitedAttemptCount::new(3));
449        assert!(
450            builder.request_options().retry_policy().is_some(),
451            "{builder:?}"
452        );
453
454        let mut builder =
455            TestBuilder::default().with_backoff_policy(ExponentialBackoffBuilder::new().build()?);
456        assert!(
457            builder.request_options().backoff_policy().is_some(),
458            "{builder:?}"
459        );
460
461        let mut builder = TestBuilder::default().with_retry_throttler(AdaptiveThrottler::default());
462        assert!(
463            builder.request_options().retry_throttler().is_some(),
464            "{builder:?}"
465        );
466
467        let mut builder =
468            TestBuilder::default().with_polling_error_policy(polling_error_policy::Aip194Strict);
469        assert!(
470            builder.request_options().polling_error_policy().is_some(),
471            "{builder:?}"
472        );
473
474        let mut builder = TestBuilder::default()
475            .with_polling_backoff_policy(ExponentialBackoffBuilder::new().build()?);
476        assert!(
477            builder.request_options().polling_backoff_policy().is_some(),
478            "{builder:?}"
479        );
480
481        Ok(())
482    }
483}