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