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