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