use crate::backoff_policy::{BackoffPolicy, BackoffPolicyArg};
use crate::polling_backoff_policy::{PollingBackoffPolicy, PollingBackoffPolicyArg};
use crate::polling_error_policy::{PollingErrorPolicy, PollingErrorPolicyArg};
use crate::retry_policy::{RetryPolicy, RetryPolicyArg};
use crate::retry_throttler::{RetryThrottlerArg, SharedRetryThrottler};
use std::sync::Arc;
#[derive(Clone, Debug, Default)]
pub struct RequestOptions {
idempotent: Option<bool>,
user_agent: Option<String>,
attempt_timeout: Option<std::time::Duration>,
retry_policy: Option<Arc<dyn RetryPolicy>>,
backoff_policy: Option<Arc<dyn BackoffPolicy>>,
retry_throttler: Option<SharedRetryThrottler>,
polling_error_policy: Option<Arc<dyn PollingErrorPolicy>>,
polling_backoff_policy: Option<Arc<dyn PollingBackoffPolicy>>,
extensions: http::Extensions,
}
impl RequestOptions {
pub fn idempotent(&self) -> Option<bool> {
self.idempotent
}
pub fn set_idempotency(&mut self, value: bool) {
self.idempotent = Some(value);
}
pub(crate) fn set_default_idempotency(&mut self, default: bool) {
self.idempotent.get_or_insert(default);
}
pub fn set_user_agent<T: Into<String>>(&mut self, v: T) {
self.user_agent = Some(v.into());
}
pub fn user_agent(&self) -> &Option<String> {
&self.user_agent
}
pub fn set_attempt_timeout<T: Into<std::time::Duration>>(&mut self, v: T) {
self.attempt_timeout = Some(v.into());
}
pub fn attempt_timeout(&self) -> &Option<std::time::Duration> {
&self.attempt_timeout
}
pub fn retry_policy(&self) -> &Option<Arc<dyn RetryPolicy>> {
&self.retry_policy
}
pub fn set_retry_policy<V: Into<RetryPolicyArg>>(&mut self, v: V) {
self.retry_policy = Some(v.into().into());
}
pub fn backoff_policy(&self) -> &Option<Arc<dyn BackoffPolicy>> {
&self.backoff_policy
}
pub fn set_backoff_policy<V: Into<BackoffPolicyArg>>(&mut self, v: V) {
self.backoff_policy = Some(v.into().into());
}
pub fn retry_throttler(&self) -> &Option<SharedRetryThrottler> {
&self.retry_throttler
}
pub fn set_retry_throttler<V: Into<RetryThrottlerArg>>(&mut self, v: V) {
self.retry_throttler = Some(v.into().into());
}
pub fn polling_error_policy(&self) -> &Option<Arc<dyn PollingErrorPolicy>> {
&self.polling_error_policy
}
pub fn set_polling_error_policy<V: Into<PollingErrorPolicyArg>>(&mut self, v: V) {
self.polling_error_policy = Some(v.into().0);
}
pub fn polling_backoff_policy(&self) -> &Option<Arc<dyn PollingBackoffPolicy>> {
&self.polling_backoff_policy
}
pub fn set_polling_backoff_policy<V: Into<PollingBackoffPolicyArg>>(&mut self, v: V) {
self.polling_backoff_policy = Some(v.into().0);
}
}
pub trait RequestOptionsBuilder: internal::RequestBuilder {
fn with_idempotency(self, v: bool) -> Self;
fn with_user_agent<V: Into<String>>(self, v: V) -> Self;
fn with_attempt_timeout<V: Into<std::time::Duration>>(self, v: V) -> Self;
fn with_retry_policy<V: Into<RetryPolicyArg>>(self, v: V) -> Self;
fn with_backoff_policy<V: Into<BackoffPolicyArg>>(self, v: V) -> Self;
fn with_retry_throttler<V: Into<RetryThrottlerArg>>(self, v: V) -> Self;
fn with_polling_error_policy<V: Into<PollingErrorPolicyArg>>(self, v: V) -> Self;
fn with_polling_backoff_policy<V: Into<PollingBackoffPolicyArg>>(self, v: V) -> Self;
}
#[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
pub mod internal {
use super::RequestOptions;
pub trait RequestBuilder {
fn request_options(&mut self) -> &mut RequestOptions;
}
pub fn set_default_idempotency(mut options: RequestOptions, default: bool) -> RequestOptions {
options.set_default_idempotency(default);
options
}
mod sealed {
pub trait OptionsExt {}
}
pub trait RequestOptionsExt: sealed::OptionsExt {
fn get_extension<T>(&self) -> Option<&T>
where
T: Send + Sync + 'static;
fn insert_extension<T>(self, value: T) -> Self
where
T: Clone + Send + Sync + 'static;
}
impl sealed::OptionsExt for RequestOptions {}
impl RequestOptionsExt for RequestOptions {
fn get_extension<T>(&self) -> Option<&T>
where
T: Send + Sync + 'static,
{
self.extensions.get::<T>()
}
fn insert_extension<T>(mut self, value: T) -> Self
where
T: Clone + Send + Sync + 'static,
{
let _ = self.extensions.insert(value);
self
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct PathTemplate(pub &'static str);
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ResourceName(pub String);
#[deprecated]
pub fn set_path_template(
options: RequestOptions,
path_template: &'static str,
) -> RequestOptions {
options.insert_extension(PathTemplate(path_template))
}
#[deprecated]
pub fn get_path_template(options: &RequestOptions) -> Option<&'static str> {
options.get_extension::<PathTemplate>().map(|e| e.0)
}
}
impl<T> RequestOptionsBuilder for T
where
T: internal::RequestBuilder,
{
fn with_idempotency(mut self, v: bool) -> Self {
self.request_options().set_idempotency(v);
self
}
fn with_user_agent<V: Into<String>>(mut self, v: V) -> Self {
self.request_options().set_user_agent(v);
self
}
fn with_attempt_timeout<V: Into<std::time::Duration>>(mut self, v: V) -> Self {
self.request_options().set_attempt_timeout(v);
self
}
fn with_retry_policy<V: Into<RetryPolicyArg>>(mut self, v: V) -> Self {
self.request_options().set_retry_policy(v);
self
}
fn with_backoff_policy<V: Into<BackoffPolicyArg>>(mut self, v: V) -> Self {
self.request_options().set_backoff_policy(v);
self
}
fn with_retry_throttler<V: Into<RetryThrottlerArg>>(mut self, v: V) -> Self {
self.request_options().set_retry_throttler(v);
self
}
fn with_polling_error_policy<V: Into<PollingErrorPolicyArg>>(mut self, v: V) -> Self {
self.request_options().set_polling_error_policy(v);
self
}
fn with_polling_backoff_policy<V: Into<PollingBackoffPolicyArg>>(mut self, v: V) -> Self {
self.request_options().set_polling_backoff_policy(v);
self
}
}
#[cfg(test)]
mod tests {
use super::internal::*;
use super::*;
use crate::exponential_backoff::ExponentialBackoffBuilder;
use crate::polling_error_policy;
use crate::retry_policy::LimitedAttemptCount;
use crate::retry_throttler::AdaptiveThrottler;
use static_assertions::{assert_impl_all, assert_not_impl_all};
use std::panic::{RefUnwindSafe, UnwindSafe};
use std::time::Duration;
#[derive(Debug, Default)]
struct TestBuilder {
request_options: RequestOptions,
}
impl RequestBuilder for TestBuilder {
fn request_options(&mut self) -> &mut RequestOptions {
&mut self.request_options
}
}
#[test]
fn traits() {
assert_impl_all!(RequestOptions: Clone, Send, Sync, Unpin, std::fmt::Debug);
assert_not_impl_all!(RequestOptions: RefUnwindSafe, UnwindSafe);
}
#[test]
fn request_options() {
let mut opts = RequestOptions::default();
assert_eq!(opts.idempotent, None);
opts.set_idempotency(true);
assert_eq!(opts.idempotent(), Some(true));
opts.set_idempotency(false);
assert_eq!(opts.idempotent(), Some(false));
opts.set_user_agent("test-only");
assert_eq!(opts.user_agent().as_deref(), Some("test-only"));
assert_eq!(opts.attempt_timeout(), &None);
let d = Duration::from_secs(123);
opts.set_attempt_timeout(d);
assert_eq!(opts.user_agent().as_deref(), Some("test-only"));
assert_eq!(opts.attempt_timeout(), &Some(d));
opts.set_retry_policy(LimitedAttemptCount::new(3));
assert!(opts.retry_policy().is_some(), "{opts:?}");
opts.set_backoff_policy(ExponentialBackoffBuilder::new().clamp());
assert!(opts.backoff_policy().is_some(), "{opts:?}");
opts.set_retry_throttler(AdaptiveThrottler::default());
assert!(opts.retry_throttler().is_some(), "{opts:?}");
opts.set_polling_error_policy(polling_error_policy::Aip194Strict);
assert!(opts.polling_error_policy().is_some(), "{opts:?}");
opts.set_polling_backoff_policy(ExponentialBackoffBuilder::new().clamp());
assert!(opts.polling_backoff_policy().is_some(), "{opts:?}");
}
#[test]
fn request_options_idempotency() {
let opts = set_default_idempotency(RequestOptions::default(), true);
assert_eq!(opts.idempotent(), Some(true));
let opts = set_default_idempotency(opts, false);
assert_eq!(opts.idempotent(), Some(true));
let opts = set_default_idempotency(RequestOptions::default(), false);
assert_eq!(opts.idempotent(), Some(false));
let opts = set_default_idempotency(opts, true);
assert_eq!(opts.idempotent(), Some(false));
}
#[test]
fn request_options_ext() {
#[derive(Debug, Clone, PartialEq)]
struct TestA(&'static str);
#[derive(Debug, Clone, PartialEq)]
struct TestB(u32);
let opts = RequestOptions::default();
assert!(opts.get_extension::<TestA>().is_none(), "{opts:?}");
assert!(opts.get_extension::<TestB>().is_none(), "{opts:?}");
let opts = opts.insert_extension(TestA("1"));
assert_eq!(opts.get_extension::<TestA>(), Some(&TestA("1")), "{opts:?}");
assert!(opts.get_extension::<TestB>().is_none(), "{opts:?}");
let opts = opts
.insert_extension(TestA("2"))
.insert_extension(TestB(42));
assert_eq!(opts.get_extension::<TestA>(), Some(&TestA("2")), "{opts:?}");
assert_eq!(opts.get_extension::<TestB>(), Some(&TestB(42)), "{opts:?}");
}
#[test]
fn request_options_builder() -> anyhow::Result<()> {
let mut builder = TestBuilder::default();
assert_eq!(builder.request_options().user_agent(), &None);
assert_eq!(builder.request_options().attempt_timeout(), &None);
let mut builder = TestBuilder::default().with_idempotency(true);
assert_eq!(builder.request_options().idempotent(), Some(true));
let mut builder = TestBuilder::default().with_idempotency(false);
assert_eq!(builder.request_options().idempotent(), Some(false));
let mut builder = TestBuilder::default().with_user_agent("test-only");
assert_eq!(
builder.request_options().user_agent().as_deref(),
Some("test-only")
);
assert_eq!(builder.request_options().attempt_timeout(), &None);
let d = Duration::from_secs(123);
let mut builder = TestBuilder::default().with_attempt_timeout(d);
assert_eq!(builder.request_options().user_agent(), &None);
assert_eq!(builder.request_options().attempt_timeout(), &Some(d));
let mut builder = TestBuilder::default().with_retry_policy(LimitedAttemptCount::new(3));
assert!(
builder.request_options().retry_policy().is_some(),
"{builder:?}"
);
let mut builder =
TestBuilder::default().with_backoff_policy(ExponentialBackoffBuilder::new().build()?);
assert!(
builder.request_options().backoff_policy().is_some(),
"{builder:?}"
);
let mut builder = TestBuilder::default().with_retry_throttler(AdaptiveThrottler::default());
assert!(
builder.request_options().retry_throttler().is_some(),
"{builder:?}"
);
let mut builder =
TestBuilder::default().with_polling_error_policy(polling_error_policy::Aip194Strict);
assert!(
builder.request_options().polling_error_policy().is_some(),
"{builder:?}"
);
let mut builder = TestBuilder::default()
.with_polling_backoff_policy(ExponentialBackoffBuilder::new().build()?);
assert!(
builder.request_options().polling_backoff_policy().is_some(),
"{builder:?}"
);
Ok(())
}
}