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}
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    /// Get the current path template, if any.
159    pub(crate) fn path_template(&self) -> Option<&'static str> {
160        self.path_template
161    }
162
163    /// Sets the path template for the request URL.
164    pub(crate) fn set_path_template(&mut self, v: &'static str) {
165        self.path_template = Some(v);
166    }
167}
168
169/// Implementations of this trait provide setters to configure request options.
170///
171/// The Google Cloud Client Libraries for Rust provide a builder for each RPC.
172/// These builders can be used to set the request parameters, e.g., the name of
173/// the resource targeted by the RPC, as well as any options affecting the
174/// request, such as additional headers or timeouts.
175pub trait RequestOptionsBuilder: internal::RequestBuilder {
176    /// If `v` is `true`, treat the RPC underlying this method as idempotent.
177    fn with_idempotency(self, v: bool) -> Self;
178
179    /// Set the user agent header.
180    fn with_user_agent<V: Into<String>>(self, v: V) -> Self;
181
182    /// Sets the per-attempt timeout.
183    ///
184    /// When using a retry loop, this affects the timeout for each attempt. The
185    /// overall timeout for a request is set by the retry policy.
186    fn with_attempt_timeout<V: Into<std::time::Duration>>(self, v: V) -> Self;
187
188    /// Sets the retry policy configuration.
189    fn with_retry_policy<V: Into<RetryPolicyArg>>(self, v: V) -> Self;
190
191    /// Sets the backoff policy configuration.
192    fn with_backoff_policy<V: Into<BackoffPolicyArg>>(self, v: V) -> Self;
193
194    /// Sets the retry throttler configuration.
195    fn with_retry_throttler<V: Into<RetryThrottlerArg>>(self, v: V) -> Self;
196
197    /// Sets the polling error policy configuration.
198    fn with_polling_error_policy<V: Into<PollingErrorPolicyArg>>(self, v: V) -> Self;
199
200    /// Sets the polling backoff policy configuration.
201    fn with_polling_backoff_policy<V: Into<PollingBackoffPolicyArg>>(self, v: V) -> Self;
202}
203
204#[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
205pub mod internal {
206    //! This module contains implementation details. It is not part of the
207    //! public API. Types and functions in this module may be changed or removed
208    //! without warnings. Applications should not use any types contained
209    //! within.
210    use super::RequestOptions;
211
212    /// Simplify implementation of the [super::RequestOptionsBuilder] trait in
213    /// generated code.
214    ///
215    /// This is an implementation detail, most applications have little need to
216    /// worry about or use this trait.
217    pub trait RequestBuilder {
218        fn request_options(&mut self) -> &mut RequestOptions;
219    }
220
221    pub fn set_default_idempotency(mut options: RequestOptions, default: bool) -> RequestOptions {
222        options.set_default_idempotency(default);
223        options
224    }
225
226    pub fn set_path_template(
227        mut options: RequestOptions,
228        path_template: &'static str,
229    ) -> RequestOptions {
230        options.set_path_template(path_template);
231        options
232    }
233
234    pub fn get_path_template(options: &RequestOptions) -> Option<&'static str> {
235        options.path_template()
236    }
237}
238
239/// Implements the sealed [RequestOptionsBuilder] trait.
240impl<T> RequestOptionsBuilder for T
241where
242    T: internal::RequestBuilder,
243{
244    fn with_idempotency(mut self, v: bool) -> Self {
245        self.request_options().set_idempotency(v);
246        self
247    }
248
249    fn with_user_agent<V: Into<String>>(mut self, v: V) -> Self {
250        self.request_options().set_user_agent(v);
251        self
252    }
253
254    fn with_attempt_timeout<V: Into<std::time::Duration>>(mut self, v: V) -> Self {
255        self.request_options().set_attempt_timeout(v);
256        self
257    }
258
259    fn with_retry_policy<V: Into<RetryPolicyArg>>(mut self, v: V) -> Self {
260        self.request_options().set_retry_policy(v);
261        self
262    }
263
264    fn with_backoff_policy<V: Into<BackoffPolicyArg>>(mut self, v: V) -> Self {
265        self.request_options().set_backoff_policy(v);
266        self
267    }
268
269    fn with_retry_throttler<V: Into<RetryThrottlerArg>>(mut self, v: V) -> Self {
270        self.request_options().set_retry_throttler(v);
271        self
272    }
273
274    fn with_polling_error_policy<V: Into<PollingErrorPolicyArg>>(mut self, v: V) -> Self {
275        self.request_options().set_polling_error_policy(v);
276        self
277    }
278
279    fn with_polling_backoff_policy<V: Into<PollingBackoffPolicyArg>>(mut self, v: V) -> Self {
280        self.request_options().set_polling_backoff_policy(v);
281        self
282    }
283}
284
285#[cfg(test)]
286mod tests {
287    use super::internal::*;
288    use super::*;
289    use crate::exponential_backoff::ExponentialBackoffBuilder;
290    use crate::polling_error_policy;
291    use crate::retry_policy::LimitedAttemptCount;
292    use crate::retry_throttler::AdaptiveThrottler;
293    use std::time::Duration;
294    type Result = std::result::Result<(), Box<dyn std::error::Error>>;
295
296    #[derive(Debug, Default)]
297    struct TestBuilder {
298        request_options: RequestOptions,
299    }
300    impl RequestBuilder for TestBuilder {
301        fn request_options(&mut self) -> &mut RequestOptions {
302            &mut self.request_options
303        }
304    }
305
306    #[test]
307    fn request_options() {
308        let mut opts = RequestOptions::default();
309
310        assert_eq!(opts.idempotent, None);
311        opts.set_idempotency(true);
312        assert_eq!(opts.idempotent(), Some(true));
313        opts.set_idempotency(false);
314        assert_eq!(opts.idempotent(), Some(false));
315
316        opts.set_user_agent("test-only");
317        assert_eq!(opts.user_agent().as_deref(), Some("test-only"));
318        assert_eq!(opts.attempt_timeout(), &None);
319
320        let d = Duration::from_secs(123);
321        opts.set_attempt_timeout(d);
322        assert_eq!(opts.user_agent().as_deref(), Some("test-only"));
323        assert_eq!(opts.attempt_timeout(), &Some(d));
324
325        opts.set_retry_policy(LimitedAttemptCount::new(3));
326        assert!(opts.retry_policy().is_some(), "{opts:?}");
327
328        opts.set_backoff_policy(ExponentialBackoffBuilder::new().clamp());
329        assert!(opts.backoff_policy().is_some(), "{opts:?}");
330
331        opts.set_retry_throttler(AdaptiveThrottler::default());
332        assert!(opts.retry_throttler().is_some(), "{opts:?}");
333
334        opts.set_polling_error_policy(polling_error_policy::Aip194Strict);
335        assert!(opts.polling_error_policy().is_some(), "{opts:?}");
336
337        opts.set_polling_backoff_policy(ExponentialBackoffBuilder::new().clamp());
338        assert!(opts.polling_backoff_policy().is_some(), "{opts:?}");
339
340        opts.set_path_template("test");
341        assert_eq!(opts.path_template(), Some("test"));
342    }
343
344    #[test]
345    fn request_options_idempotency() {
346        let opts = set_default_idempotency(RequestOptions::default(), true);
347        assert_eq!(opts.idempotent(), Some(true));
348        let opts = set_default_idempotency(opts, false);
349        assert_eq!(opts.idempotent(), Some(true));
350
351        let opts = set_default_idempotency(RequestOptions::default(), false);
352        assert_eq!(opts.idempotent(), Some(false));
353        let opts = set_default_idempotency(opts, true);
354        assert_eq!(opts.idempotent(), Some(false));
355    }
356
357    #[test]
358    fn request_options_path_template() {
359        let opts = RequestOptions::default();
360        assert_eq!(opts.path_template(), None);
361        let opts = set_path_template(opts, "test");
362        assert_eq!(opts.path_template(), Some("test"));
363    }
364
365    #[test]
366    fn request_options_builder() -> Result {
367        let mut builder = TestBuilder::default();
368        assert_eq!(builder.request_options().user_agent(), &None);
369        assert_eq!(builder.request_options().attempt_timeout(), &None);
370
371        let mut builder = TestBuilder::default().with_idempotency(true);
372        assert_eq!(builder.request_options().idempotent(), Some(true));
373        let mut builder = TestBuilder::default().with_idempotency(false);
374        assert_eq!(builder.request_options().idempotent(), Some(false));
375
376        let mut builder = TestBuilder::default().with_user_agent("test-only");
377        assert_eq!(
378            builder.request_options().user_agent().as_deref(),
379            Some("test-only")
380        );
381        assert_eq!(builder.request_options().attempt_timeout(), &None);
382
383        let d = Duration::from_secs(123);
384        let mut builder = TestBuilder::default().with_attempt_timeout(d);
385        assert_eq!(builder.request_options().user_agent(), &None);
386        assert_eq!(builder.request_options().attempt_timeout(), &Some(d));
387
388        let mut builder = TestBuilder::default().with_retry_policy(LimitedAttemptCount::new(3));
389        assert!(
390            builder.request_options().retry_policy().is_some(),
391            "{builder:?}"
392        );
393
394        let mut builder =
395            TestBuilder::default().with_backoff_policy(ExponentialBackoffBuilder::new().build()?);
396        assert!(
397            builder.request_options().backoff_policy().is_some(),
398            "{builder:?}"
399        );
400
401        let mut builder = TestBuilder::default().with_retry_throttler(AdaptiveThrottler::default());
402        assert!(
403            builder.request_options().retry_throttler().is_some(),
404            "{builder:?}"
405        );
406
407        let mut builder =
408            TestBuilder::default().with_polling_error_policy(polling_error_policy::Aip194Strict);
409        assert!(
410            builder.request_options().polling_error_policy().is_some(),
411            "{builder:?}"
412        );
413
414        let mut builder = TestBuilder::default()
415            .with_polling_backoff_policy(ExponentialBackoffBuilder::new().build()?);
416        assert!(
417            builder.request_options().polling_backoff_policy().is_some(),
418            "{builder:?}"
419        );
420
421        Ok(())
422    }
423}