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    #[cfg(google_cloud_unstable_gapic_streaming)]
54    request_stream_channel_capacity: Option<usize>,
55    extensions: http::Extensions,
56}
57
58impl RequestOptions {
59    /// Gets the idempotency
60    pub fn idempotent(&self) -> Option<bool> {
61        self.idempotent
62    }
63
64    /// Treat the RPC underlying RPC in this method as idempotent.
65    ///
66    /// If a retry policy is configured, the policy may examine the idempotency
67    /// and the error details to decide if the error is retryable. Typically
68    /// [idempotent] RPCs are safe to retry under more error conditions
69    /// than non-idempotent RPCs.
70    ///
71    /// The client libraries provide a default for RPC idempotency, based on the
72    /// HTTP method (`GET`, `POST`, `DELETE`, etc.).
73    ///
74    /// [idempotent]: https://en.wikipedia.org/wiki/Idempotence
75    pub fn set_idempotency(&mut self, value: bool) {
76        self.idempotent = Some(value);
77    }
78
79    /// Set the idempotency for the underlying RPC unless it is already set.
80    ///
81    /// If [set_idempotency][Self::set_idempotency] was already called this
82    /// method has no effect. Otherwise it sets the idempotency. The client
83    /// libraries use this to provide a default idempotency value.
84    pub(crate) fn set_default_idempotency(&mut self, default: bool) {
85        self.idempotent.get_or_insert(default);
86    }
87
88    /// Prepends this prefix to the user agent header value.
89    pub fn set_user_agent<T: Into<String>>(&mut self, v: T) {
90        self.user_agent = Some(v.into());
91    }
92
93    /// Gets the current user-agent prefix
94    pub fn user_agent(&self) -> &Option<String> {
95        &self.user_agent
96    }
97
98    /// Sets the [quota project] for the request.
99    ///
100    /// This adds the `x-goog-user-project` header to the request. Note that
101    /// setting this option overrides a credentials' quota project.
102    ///
103    /// [quota project]: https://docs.cloud.google.com/docs/quotas/quota-project
104    pub fn set_quota_project<T: Into<String>>(&mut self, v: T) {
105        self.quota_project = Some(v.into());
106    }
107
108    /// Gets the current quota project.
109    pub fn quota_project(&self) -> &Option<String> {
110        &self.quota_project
111    }
112
113    /// Sets the per-attempt timeout.
114    ///
115    /// When using a retry loop, this affects the timeout for each attempt. The
116    /// overall timeout for a request is set by the retry policy.
117    pub fn set_attempt_timeout<T: Into<std::time::Duration>>(&mut self, v: T) {
118        self.attempt_timeout = Some(v.into());
119    }
120
121    /// Gets the current per-attempt timeout.
122    pub fn attempt_timeout(&self) -> &Option<std::time::Duration> {
123        &self.attempt_timeout
124    }
125
126    /// Get the current retry policy override, if any.
127    pub fn retry_policy(&self) -> &Option<Arc<dyn RetryPolicy>> {
128        &self.retry_policy
129    }
130
131    /// Sets the retry policy configuration.
132    pub fn set_retry_policy<V: Into<RetryPolicyArg>>(&mut self, v: V) {
133        self.retry_policy = Some(v.into().into());
134    }
135
136    /// Get the current backoff policy override, if any.
137    pub fn backoff_policy(&self) -> &Option<Arc<dyn BackoffPolicy>> {
138        &self.backoff_policy
139    }
140
141    /// Sets the backoff policy configuration.
142    pub fn set_backoff_policy<V: Into<BackoffPolicyArg>>(&mut self, v: V) {
143        self.backoff_policy = Some(v.into().into());
144    }
145
146    /// Get the current retry throttler override, if any.
147    pub fn retry_throttler(&self) -> &Option<SharedRetryThrottler> {
148        &self.retry_throttler
149    }
150
151    /// Sets the retry throttling configuration.
152    pub fn set_retry_throttler<V: Into<RetryThrottlerArg>>(&mut self, v: V) {
153        self.retry_throttler = Some(v.into().into());
154    }
155
156    /// Get the current polling policy override, if any.
157    pub fn polling_error_policy(&self) -> &Option<Arc<dyn PollingErrorPolicy>> {
158        &self.polling_error_policy
159    }
160
161    /// Sets the polling policy configuration.
162    pub fn set_polling_error_policy<V: Into<PollingErrorPolicyArg>>(&mut self, v: V) {
163        self.polling_error_policy = Some(v.into().0);
164    }
165
166    /// Get the current polling backoff policy override, if any.
167    pub fn polling_backoff_policy(&self) -> &Option<Arc<dyn PollingBackoffPolicy>> {
168        &self.polling_backoff_policy
169    }
170
171    /// Sets the backoff policy configuration.
172    pub fn set_polling_backoff_policy<V: Into<PollingBackoffPolicyArg>>(&mut self, v: V) {
173        self.polling_backoff_policy = Some(v.into().0);
174    }
175
176    /// Gets the current request stream channel capacity, if set.
177    #[cfg(google_cloud_unstable_gapic_streaming)]
178    pub fn request_stream_channel_capacity(&self) -> Option<usize> {
179        self.request_stream_channel_capacity
180    }
181
182    /// Sets the buffer capacity of the internal request channel for streaming RPCs.
183    ///
184    /// Valid values are between `1` and `usize::MAX >> 3`. Values outside this range will be clamped.
185    #[cfg(google_cloud_unstable_gapic_streaming)]
186    pub fn set_request_stream_channel_capacity(&mut self, capacity: usize) {
187        self.request_stream_channel_capacity =
188            Some(capacity.clamp(1, MAX_REQUEST_CHANNEL_CAPACITY));
189    }
190}
191
192/// The maximum allowed capacity for a bidirectional stream request channel.
193///
194/// This upper limit matches Tokio's internal `mpsc::channel` capacity limit
195/// (`usize::MAX >> 3`).
196#[cfg(google_cloud_unstable_gapic_streaming)]
197const MAX_REQUEST_CHANNEL_CAPACITY: usize = usize::MAX >> 3;
198
199/// Implementations of this trait provide setters to configure request options.
200///
201/// The Google Cloud Client Libraries for Rust provide a builder for each RPC.
202/// These builders can be used to set the request parameters, e.g., the name of
203/// the resource targeted by the RPC, as well as any options affecting the
204/// request, such as additional headers or timeouts.
205pub trait RequestOptionsBuilder: internal::RequestBuilder {
206    /// If `v` is `true`, treat the RPC underlying this method as idempotent.
207    fn with_idempotency(self, v: bool) -> Self;
208
209    /// Set the user agent header.
210    fn with_user_agent<V: Into<String>>(self, v: V) -> Self;
211
212    /// Sets the per-attempt timeout.
213    ///
214    /// When using a retry loop, this affects the timeout for each attempt. The
215    /// overall timeout for a request is set by the retry policy.
216    fn with_attempt_timeout<V: Into<std::time::Duration>>(self, v: V) -> Self;
217
218    /// Sets the retry policy configuration.
219    fn with_retry_policy<V: Into<RetryPolicyArg>>(self, v: V) -> Self;
220
221    /// Sets the backoff policy configuration.
222    fn with_backoff_policy<V: Into<BackoffPolicyArg>>(self, v: V) -> Self;
223
224    /// Sets the retry throttler configuration.
225    fn with_retry_throttler<V: Into<RetryThrottlerArg>>(self, v: V) -> Self;
226
227    /// Sets the polling error policy configuration.
228    fn with_polling_error_policy<V: Into<PollingErrorPolicyArg>>(self, v: V) -> Self;
229
230    /// Sets the polling backoff policy configuration.
231    fn with_polling_backoff_policy<V: Into<PollingBackoffPolicyArg>>(self, v: V) -> Self;
232
233    // Methods with a default implementation.
234    // See https://github.com/googleapis/google-cloud-rust/pull/5490 for context.
235
236    /// Sets the [quota project] for the request.
237    ///
238    /// This adds the `x-goog-user-project` header to the request. Note that
239    /// setting this option overrides a credentials' quota project.
240    ///
241    /// [quota project]: https://docs.cloud.google.com/docs/quotas/quota-project
242    fn with_quota_project<V: Into<String>>(self, _v: V) -> Self
243    where
244        Self: Sized,
245    {
246        unimplemented!();
247    }
248
249    /// Injects a custom HTTP header (or gRPC metadata) into this specific request.
250    ///
251    /// This method will ignore attempts to set system headers used by the client
252    /// library. These include: `user-agent`, `authorization`,
253    /// `x-goog-api-key`, `x-goog-api-client`, `x-goog-user-project`,
254    /// and `x-goog-request-params`.
255    ///
256    /// Callers who want to configure respective system headers should use the dedicated methods instead:
257    /// * `user-agent`: Use [`with_user_agent`](Self::with_user_agent) (or globally via `ClientBuilder`).
258    /// * `x-goog-user-project`: Use [`with_quota_project`](Self::with_quota_project).
259    /// * `authorization` and `x-goog-api-key`: Configure globally via `ClientBuilder::with_credentials`.
260    ///
261    /// Note that `x-goog-api-client` and `x-goog-request-params` are dynamically auto-generated by the SDK
262    /// for telemetry and routing, and cannot be customized by callers.
263    ///
264    /// ### Repeated Headers
265    ///
266    /// Under HTTP RFC 9110 (Section 5.3), standard repeated headers (e.g. `Cache-Control`, `Accept`, or `Allow`)
267    /// are defined as comma-separated lists. Callers can supply repeated values natively as a single
268    /// comma-separated [`HeaderValue`]:
269    ///
270    /// ```
271    /// # use google_cloud_gax::options::RequestOptionsBuilder;
272    /// use http::header::{HeaderName, HeaderValue};
273    /// fn sample<T: RequestOptionsBuilder>(builder: T) -> T {
274    ///     builder.with_custom_header(
275    ///         HeaderName::from_static("cache-control"),
276    ///         HeaderValue::from_static("no-cache, no-store"),
277    ///     )
278    /// }
279    /// ```
280    ///
281    /// [`HeaderValue`]: http::header::HeaderValue
282    fn with_custom_header(
283        mut self,
284        name: http::header::HeaderName,
285        value: http::header::HeaderValue,
286    ) -> Self
287    where
288        Self: Sized,
289    {
290        use internal::RequestOptionsExt;
291        let mut headers = self
292            .request_options()
293            .get_extension::<http::HeaderMap>()
294            .cloned()
295            .unwrap_or_default();
296        headers.insert(name, value);
297        let mut options = std::mem::take(self.request_options());
298        options = options.insert_extension(headers);
299        *self.request_options() = options;
300        self
301    }
302
303    /// Sets the buffer capacity of the internal request channel for streaming RPCs.
304    ///
305    /// Valid values are between `1` and `usize::MAX >> 3`. The default
306    /// capacity is `16`. Values outside this range will be clamped.
307    #[cfg(google_cloud_unstable_gapic_streaming)]
308    fn with_request_stream_channel_capacity(self, _capacity: usize) -> Self
309    where
310        Self: Sized,
311    {
312        unimplemented!();
313    }
314}
315
316#[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
317#[allow(missing_docs)]
318pub mod internal {
319    //! This module contains implementation details. It is not part of the
320    //! public API. Types and functions in this module may be changed or removed
321    //! without warnings. Applications should not use any types contained
322    //! within.
323    use super::RequestOptions;
324
325    /// Simplify implementation of the [super::RequestOptionsBuilder] trait in
326    /// generated code.
327    ///
328    /// This is an implementation detail, most applications have little need to
329    /// worry about or use this trait.
330    pub trait RequestBuilder {
331        fn request_options(&mut self) -> &mut RequestOptions;
332    }
333
334    pub fn set_default_idempotency(mut options: RequestOptions, default: bool) -> RequestOptions {
335        options.set_default_idempotency(default);
336        options
337    }
338
339    mod sealed {
340        pub trait OptionsExt {}
341    }
342
343    /// Access the `RequestOption` extensions.
344    ///
345    /// The client library internals can use this trait to attach extension
346    /// values to the request options. Possibly passing (nearly) arbitrary types
347    /// between layers.
348    ///
349    /// This is useful when (for example) the tracing layer wants to pass
350    /// information to the HTTP or gRPC client, without having to change all the
351    /// intermediate types, which may include public interfaces.
352    pub trait RequestOptionsExt: sealed::OptionsExt {
353        /// Gets an extension value.
354        fn get_extension<T>(&self) -> Option<&T>
355        where
356            T: Send + Sync + 'static;
357
358        /// Gets a mutable reference to an extension value.
359        fn get_extension_mut<T>(&mut self) -> Option<&mut T>
360        where
361            T: Send + Sync + 'static;
362
363        /// Gets a mutable reference to an extension value, inserting the default if it does not exist.
364        fn get_extension_or_default_mut<T>(&mut self) -> &mut T
365        where
366            T: Default + Clone + Send + Sync + 'static;
367
368        /// Sets an extension value.
369        fn insert_extension<T>(self, value: T) -> Self
370        where
371            T: Clone + Send + Sync + 'static;
372    }
373
374    impl sealed::OptionsExt for RequestOptions {}
375    impl RequestOptionsExt for RequestOptions {
376        fn get_extension<T>(&self) -> Option<&T>
377        where
378            T: Send + Sync + 'static,
379        {
380            self.extensions.get::<T>()
381        }
382
383        fn get_extension_mut<T>(&mut self) -> Option<&mut T>
384        where
385            T: Send + Sync + 'static,
386        {
387            self.extensions.get_mut::<T>()
388        }
389
390        fn get_extension_or_default_mut<T>(&mut self) -> &mut T
391        where
392            T: Default + Clone + Send + Sync + 'static,
393        {
394            if self.extensions.get::<T>().is_none() {
395                let _ = self.extensions.insert(T::default());
396            }
397            self.extensions
398                .get_mut::<T>()
399                .expect("value was just inserted if missing")
400        }
401
402        fn insert_extension<T>(mut self, value: T) -> Self
403        where
404            T: Clone + Send + Sync + 'static,
405        {
406            let _ = self.extensions.insert(value);
407            self
408        }
409    }
410
411    #[derive(Debug, Clone, Default, PartialEq)]
412    pub struct PathTemplate(pub &'static str);
413
414    #[derive(Debug, Clone, Default, PartialEq)]
415    pub struct ResourceName(pub String);
416
417    // Cannot remove this function, as that would break any client libraries
418    // that are released and use this function.
419    #[deprecated]
420    pub fn set_path_template(
421        options: RequestOptions,
422        path_template: &'static str,
423    ) -> RequestOptions {
424        options.insert_extension(PathTemplate(path_template))
425    }
426
427    // Cannot remove this function, as that would break any client libraries
428    // that are released and use this function.
429    #[deprecated]
430    pub fn get_path_template(options: &RequestOptions) -> Option<&'static str> {
431        options.get_extension::<PathTemplate>().map(|e| e.0)
432    }
433}
434
435/// Implements the sealed [RequestOptionsBuilder] trait.
436impl<T> RequestOptionsBuilder for T
437where
438    T: internal::RequestBuilder,
439{
440    fn with_idempotency(mut self, v: bool) -> Self {
441        self.request_options().set_idempotency(v);
442        self
443    }
444
445    fn with_user_agent<V: Into<String>>(mut self, v: V) -> Self {
446        self.request_options().set_user_agent(v);
447        self
448    }
449
450    fn with_quota_project<V: Into<String>>(mut self, v: V) -> Self {
451        self.request_options().set_quota_project(v);
452        self
453    }
454
455    fn with_attempt_timeout<V: Into<std::time::Duration>>(mut self, v: V) -> Self {
456        self.request_options().set_attempt_timeout(v);
457        self
458    }
459
460    fn with_retry_policy<V: Into<RetryPolicyArg>>(mut self, v: V) -> Self {
461        self.request_options().set_retry_policy(v);
462        self
463    }
464
465    fn with_backoff_policy<V: Into<BackoffPolicyArg>>(mut self, v: V) -> Self {
466        self.request_options().set_backoff_policy(v);
467        self
468    }
469
470    fn with_retry_throttler<V: Into<RetryThrottlerArg>>(mut self, v: V) -> Self {
471        self.request_options().set_retry_throttler(v);
472        self
473    }
474
475    fn with_polling_error_policy<V: Into<PollingErrorPolicyArg>>(mut self, v: V) -> Self {
476        self.request_options().set_polling_error_policy(v);
477        self
478    }
479
480    fn with_polling_backoff_policy<V: Into<PollingBackoffPolicyArg>>(mut self, v: V) -> Self {
481        self.request_options().set_polling_backoff_policy(v);
482        self
483    }
484
485    #[cfg(google_cloud_unstable_gapic_streaming)]
486    fn with_request_stream_channel_capacity(mut self, capacity: usize) -> Self {
487        self.request_options()
488            .set_request_stream_channel_capacity(capacity);
489        self
490    }
491}
492
493#[cfg(test)]
494mod tests {
495    use super::internal::*;
496    use super::*;
497    use crate::exponential_backoff::ExponentialBackoffBuilder;
498    use crate::polling_error_policy;
499    use crate::retry_policy::LimitedAttemptCount;
500    use crate::retry_throttler::AdaptiveThrottler;
501    use static_assertions::{assert_impl_all, assert_not_impl_all};
502    use std::panic::{RefUnwindSafe, UnwindSafe};
503    use std::time::Duration;
504
505    #[derive(Debug, Default)]
506    struct TestBuilder {
507        request_options: RequestOptions,
508    }
509    impl RequestBuilder for TestBuilder {
510        fn request_options(&mut self) -> &mut RequestOptions {
511            &mut self.request_options
512        }
513    }
514
515    #[test]
516    fn traits() {
517        assert_impl_all!(RequestOptions: Clone, Send, Sync, Unpin, std::fmt::Debug);
518        assert_not_impl_all!(RequestOptions: RefUnwindSafe, UnwindSafe);
519    }
520
521    #[test]
522    fn request_options() {
523        const USER_AGENT: &str = "test-only";
524        const USER_PROJECT: &str = "test-project";
525
526        let mut opts = RequestOptions::default();
527
528        assert_eq!(opts.idempotent, None);
529        opts.set_idempotency(true);
530        assert_eq!(opts.idempotent(), Some(true));
531        opts.set_idempotency(false);
532        assert_eq!(opts.idempotent(), Some(false));
533
534        opts.set_user_agent(USER_AGENT);
535        assert_eq!(opts.user_agent().as_deref(), Some(USER_AGENT));
536        assert_eq!(opts.attempt_timeout(), &None);
537
538        opts.set_quota_project(USER_PROJECT);
539        assert_eq!(opts.quota_project().as_deref(), Some(USER_PROJECT));
540
541        let d = Duration::from_secs(123);
542        opts.set_attempt_timeout(d);
543        assert_eq!(opts.user_agent().as_deref(), Some(USER_AGENT));
544        assert_eq!(opts.attempt_timeout(), &Some(d));
545
546        opts.set_retry_policy(LimitedAttemptCount::new(3));
547        assert!(opts.retry_policy().is_some(), "{opts:?}");
548
549        opts.set_backoff_policy(ExponentialBackoffBuilder::new().clamp());
550        assert!(opts.backoff_policy().is_some(), "{opts:?}");
551
552        opts.set_retry_throttler(AdaptiveThrottler::default());
553        assert!(opts.retry_throttler().is_some(), "{opts:?}");
554
555        opts.set_polling_error_policy(polling_error_policy::Aip194Strict);
556        assert!(opts.polling_error_policy().is_some(), "{opts:?}");
557
558        opts.set_polling_backoff_policy(ExponentialBackoffBuilder::new().clamp());
559        assert!(opts.polling_backoff_policy().is_some(), "{opts:?}");
560    }
561
562    #[test]
563    fn request_options_idempotency() {
564        let opts = set_default_idempotency(RequestOptions::default(), true);
565        assert_eq!(opts.idempotent(), Some(true));
566        let opts = set_default_idempotency(opts, false);
567        assert_eq!(opts.idempotent(), Some(true));
568
569        let opts = set_default_idempotency(RequestOptions::default(), false);
570        assert_eq!(opts.idempotent(), Some(false));
571        let opts = set_default_idempotency(opts, true);
572        assert_eq!(opts.idempotent(), Some(false));
573    }
574
575    #[test]
576    fn request_options_ext() {
577        #[derive(Debug, Clone, PartialEq)]
578        struct TestA(&'static str);
579        #[derive(Debug, Clone, PartialEq)]
580        struct TestB(u32);
581
582        let opts = RequestOptions::default();
583        assert!(opts.get_extension::<TestA>().is_none(), "{opts:?}");
584        assert!(opts.get_extension::<TestB>().is_none(), "{opts:?}");
585        let opts = opts.insert_extension(TestA("1"));
586        assert_eq!(opts.get_extension::<TestA>(), Some(&TestA("1")), "{opts:?}");
587        assert!(opts.get_extension::<TestB>().is_none(), "{opts:?}");
588        let opts = opts
589            .insert_extension(TestA("2"))
590            .insert_extension(TestB(42));
591        assert_eq!(opts.get_extension::<TestA>(), Some(&TestA("2")), "{opts:?}");
592        assert_eq!(opts.get_extension::<TestB>(), Some(&TestB(42)), "{opts:?}");
593    }
594
595    #[test]
596    fn request_options_ext_mut() {
597        #[derive(Debug, Clone, Default, PartialEq)]
598        struct TestCounter(u32);
599
600        let mut opts = RequestOptions::default();
601
602        // 1. get_extension_mut returns None when not present.
603        assert!(opts.get_extension_mut::<TestCounter>().is_none());
604
605        // 2. get_extension_or_default_mut inserts default TestCounter(0) and returns mutable reference.
606        let counter = opts.get_extension_or_default_mut::<TestCounter>();
607        assert_eq!(counter, &mut TestCounter(0));
608        counter.0 += 10;
609
610        // 3. get_extension_mut returns Some(&mut TestCounter(10)) when present.
611        let counter = opts
612            .get_extension_mut::<TestCounter>()
613            .expect("counter extension should be present after insertion");
614        assert_eq!(counter, &mut TestCounter(10));
615        counter.0 += 10;
616
617        // 4. Second call to get_extension_or_default_mut returns existing reference without resetting.
618        let counter2 = opts.get_extension_or_default_mut::<TestCounter>();
619        assert_eq!(counter2, &mut TestCounter(20));
620        counter2.0 += 5;
621
622        assert_eq!(opts.get_extension::<TestCounter>(), Some(&TestCounter(25)));
623    }
624
625    #[test]
626    fn request_options_builder() -> anyhow::Result<()> {
627        const USER_AGENT: &str = "test-only";
628        const USER_PROJECT: &str = "test-project";
629
630        let mut builder = TestBuilder::default();
631        assert_eq!(builder.request_options().user_agent(), &None);
632        assert_eq!(builder.request_options().quota_project(), &None);
633        assert_eq!(builder.request_options().attempt_timeout(), &None);
634
635        let mut builder = TestBuilder::default().with_idempotency(true);
636        assert_eq!(builder.request_options().idempotent(), Some(true));
637        let mut builder = TestBuilder::default().with_idempotency(false);
638        assert_eq!(builder.request_options().idempotent(), Some(false));
639
640        let mut builder = TestBuilder::default().with_user_agent(USER_AGENT);
641        assert_eq!(
642            builder.request_options().user_agent().as_deref(),
643            Some(USER_AGENT)
644        );
645        assert_eq!(builder.request_options().attempt_timeout(), &None);
646
647        let mut builder = TestBuilder::default().with_quota_project(USER_PROJECT);
648        assert_eq!(
649            builder.request_options().quota_project().as_deref(),
650            Some(USER_PROJECT)
651        );
652
653        let d = Duration::from_secs(123);
654        let mut builder = TestBuilder::default().with_attempt_timeout(d);
655        assert_eq!(builder.request_options().user_agent(), &None);
656        assert_eq!(builder.request_options().attempt_timeout(), &Some(d));
657
658        let mut builder = TestBuilder::default().with_retry_policy(LimitedAttemptCount::new(3));
659        assert!(
660            builder.request_options().retry_policy().is_some(),
661            "{builder:?}"
662        );
663
664        let mut builder =
665            TestBuilder::default().with_backoff_policy(ExponentialBackoffBuilder::new().build()?);
666        assert!(
667            builder.request_options().backoff_policy().is_some(),
668            "{builder:?}"
669        );
670
671        let mut builder = TestBuilder::default().with_retry_throttler(AdaptiveThrottler::default());
672        assert!(
673            builder.request_options().retry_throttler().is_some(),
674            "{builder:?}"
675        );
676
677        let mut builder =
678            TestBuilder::default().with_polling_error_policy(polling_error_policy::Aip194Strict);
679        assert!(
680            builder.request_options().polling_error_policy().is_some(),
681            "{builder:?}"
682        );
683
684        let mut builder = TestBuilder::default()
685            .with_polling_backoff_policy(ExponentialBackoffBuilder::new().build()?);
686        assert!(
687            builder.request_options().polling_backoff_policy().is_some(),
688            "{builder:?}"
689        );
690
691        Ok(())
692    }
693
694    #[test]
695    fn request_options_builder_custom_headers() {
696        let mut builder = TestBuilder::default()
697            .with_custom_header(
698                http::header::HeaderName::from_static("x-custom-1"),
699                http::header::HeaderValue::from_static("value1"),
700            )
701            .with_custom_header(
702                http::header::HeaderName::from_static("x-custom-2"),
703                http::header::HeaderValue::from_static("value2"),
704            );
705
706        let headers = builder
707            .request_options()
708            .get_extension::<http::HeaderMap>()
709            .expect("headers extension should be present");
710
711        assert_eq!(
712            headers.get("x-custom-1").and_then(|v| v.to_str().ok()),
713            Some("value1")
714        );
715        assert_eq!(
716            headers.get("x-custom-2").and_then(|v| v.to_str().ok()),
717            Some("value2")
718        );
719    }
720
721    #[cfg(google_cloud_unstable_gapic_streaming)]
722    #[test]
723    fn request_options_builder_request_stream_channel_capacity() {
724        let builder = TestBuilder::default();
725        assert_eq!(
726            builder.request_options.request_stream_channel_capacity(),
727            None
728        );
729
730        let builder = TestBuilder::default().with_request_stream_channel_capacity(32);
731        assert_eq!(
732            builder.request_options.request_stream_channel_capacity(),
733            Some(32)
734        );
735
736        // Clamping tests
737        let builder = TestBuilder::default().with_request_stream_channel_capacity(0);
738        assert_eq!(
739            builder.request_options.request_stream_channel_capacity(),
740            Some(1)
741        );
742
743        let builder = TestBuilder::default().with_request_stream_channel_capacity(usize::MAX);
744        assert_eq!(
745            builder.request_options.request_stream_channel_capacity(),
746            Some(MAX_REQUEST_CHANNEL_CAPACITY)
747        );
748    }
749}