use async_trait::async_trait;
use log::debug;
use rand::Rng;
#[macro_export]
macro_rules! dict {
( $container: expr, {}) => {
$container
};
( $container: expr, { $( $k:expr => $v:expr ),+ } ) => {
dict!($container, { $($k => $v,)* })
};
( $container: expr, { $( $k:expr => $v:expr ),+, } ) => {
{
let mut container = $container;
$(
container.insert($k.into(), $v.into());
)*
container
}
};
}
#[macro_export]
macro_rules! seq {
( $container: expr, []) => {
$container
};
( $container: expr, [ $( $v:expr ),+ ] ) => {
seq!($container, [$($v,)* ])
};
( $container: expr, [ $( $v:expr ),+, ] ) => {
{
let mut container = $container;
$(
container.push($v.into());
)*
container
}
};
}
#[macro_export]
macro_rules! yaml {
({}) => {
serde_yaml::Value::Mapping(dict!(serde_yaml::Mapping::new(), {}))
};
({ $( $k:expr => $v:expr ),+ } ) => {
serde_yaml::Value::Mapping(dict!(serde_yaml::Mapping::new(), { $($k => $v,)* }))
};
({ $( $k:expr => $v:expr ),+, } ) => {
serde_yaml::Value::Mapping(dict!(serde_yaml::Mapping::new(), { $($k => $v,)* }))
};
([]) => {
serde_yaml::Value::Sequence(seq!(serde_yaml::Sequence::new(), []))
};
( [ $( $v:expr ),+ ] ) => {
serde_yaml::Value::Sequence(seq!(serde_yaml::Sequence::new(), [$($v,)* ]))
};
( [ $( $v:expr ),+, ] ) => {
serde_yaml::Value::Sequence(seq!(serde_yaml::Sequence::new(), [$($v,)* ]))
};
( $v:expr ) => {
serde_yaml::Value::from($v)
}
}
pub trait YamlExt {
fn to_seq_mut(&mut self) -> Option<&mut serde_yaml::Sequence>;
}
impl YamlExt for serde_yaml::Value {
fn to_seq_mut(&mut self) -> Option<&mut serde_yaml::Sequence> {
if self.is_null() {
*self = yaml!([]);
}
self.as_sequence_mut()
}
}
pub fn true_() -> bool {
true
}
pub trait AsRumaError {
fn as_ruma_error(&self) -> Option<&matrix_sdk::ruma::api::client::Error>;
}
impl AsRumaError for matrix_sdk::HttpError {
fn as_ruma_error(&self) -> Option<&matrix_sdk::ruma::api::client::Error> {
match *self {
matrix_sdk::HttpError::Api(
matrix_sdk::ruma::api::error::FromHttpResponseError::Server(
matrix_sdk::ruma::api::error::ServerError::Known(
matrix_sdk::RumaApiError::ClientApi(ref err),
),
),
) => Some(err),
_ => None,
}
}
}
impl AsRumaError for matrix_sdk::Error {
fn as_ruma_error(&self) -> Option<&matrix_sdk::ruma::api::client::Error> {
match *self {
matrix_sdk::Error::Http(ref err) => err.as_ruma_error(),
_ => None,
}
}
}
#[async_trait]
pub trait Retry {
async fn auto_retry(&self, attempts: u64) -> Result<reqwest::Response, anyhow::Error>;
}
#[async_trait]
impl Retry for reqwest::RequestBuilder {
async fn auto_retry(&self, max_attempts: u64) -> Result<reqwest::Response, anyhow::Error> {
const BASE_INTERVAL_MS: std::ops::Range<u64> = 300..1000;
let mut attempt = 1;
loop {
match self
.try_clone()
.expect("Cannot auto-retry non-clonable requests")
.send()
.await
{
Ok(response) => {
debug!("auto_retry success");
break Ok(response);
}
Err(err) => {
debug!("auto_retry error {:?} => {:?}", err, err.status());
let should_retry = attempt < max_attempts
&& (err.is_connect() || err.is_timeout() || err.is_request());
if should_retry {
let duration =
(attempt * attempt) * rand::thread_rng().gen_range(BASE_INTERVAL_MS);
attempt += 1;
debug!("auto_retry: sleeping {}ms", duration);
tokio::time::sleep(std::time::Duration::from_millis(duration)).await;
} else {
debug!("auto_retry: giving up!");
return Err(err.into());
}
}
}
}
}
}