mx_tester/
util.rs

1use async_trait::async_trait;
2use log::debug;
3use rand::Rng;
4
5/// A generic syntax for dict-like structures.
6///
7/// Works for HashMap but also for e.g. serde_json or serde_yaml maps.
8///
9/// ```rust
10/// # #[macro_use] extern crate mx_tester;
11/// # fn main() {
12///
13/// use std::collections::HashMap;
14///
15/// let empty: HashMap<u8, u8> = dict!(HashMap::new(), {});
16/// assert_eq!(empty.len(), 0);
17///
18/// let map: HashMap<u8, u8> = dict!(HashMap::new(), {
19///    0 => 255,
20///    1 => 254,
21///    2 => 253,
22/// });
23/// assert_eq!(map.len(), 3);
24/// assert!(matches!(map.get(&0), Some(255)));
25/// assert!(matches!(map.get(&1), Some(254)));
26/// assert!(matches!(map.get(&2), Some(253)));
27///
28/// # }
29/// ```
30#[macro_export]
31macro_rules! dict {
32    // Empty
33    ( $container: expr, {}) => {
34        $container
35    };
36    // Without trailing `,`.
37    ( $container: expr, { $( $k:expr => $v:expr ),+ } ) => {
38        dict!($container, { $($k => $v,)* })
39    };
40    // With trailing `,`.
41    ( $container: expr, { $( $k:expr => $v:expr ),+, } ) => {
42        {
43            let mut container = $container;
44            $(
45                container.insert($k.into(), $v.into());
46            )*
47            container
48        }
49    };
50}
51
52/// A generic syntax for seq-like structures.
53///
54/// Works for Vec but also for serde_json or serde_yaml arrays.
55///
56/// ```rust
57/// # #[macro_use] extern crate mx_tester;
58/// # fn main() {
59///
60/// use std::collections::HashMap;
61///
62/// let empty: Vec<u8> = seq!(Vec::new(), []);
63/// assert_eq!(empty.len(), 0);
64///
65/// let vec: Vec<u8> = seq!(Vec::new(), [
66///    255,
67///    254,
68///    253,
69/// ]);
70/// assert_eq!(vec.len(), 3);
71/// assert!(matches!(vec.get(0), Some(255)));
72/// assert!(matches!(vec.get(1), Some(254)));
73/// assert!(matches!(vec.get(2), Some(253)));
74///
75/// # }
76/// ```
77#[macro_export]
78macro_rules! seq {
79    // Empty
80    ( $container: expr, []) => {
81        $container
82    };
83    // Without trailing `,`.
84    ( $container: expr, [ $( $v:expr ),+ ] ) => {
85        seq!($container, [$($v,)* ])
86    };
87    // With trailing `,`.
88    ( $container: expr, [ $( $v:expr ),+, ] ) => {
89        {
90            let mut container = $container;
91            $(
92                container.push($v.into());
93            )*
94            container
95        }
96    };
97}
98
99/// A lightweight syntax for YAML.
100///
101/// ```rust
102/// # #[macro_use] extern crate mx_tester;
103/// # fn main() {
104///
105/// use serde_yaml;
106///
107/// let empty_map = yaml!({});
108/// assert!(empty_map.as_mapping().is_some());
109/// assert!(empty_map.as_mapping().unwrap().is_empty());
110///
111/// let empty_seq = yaml!([]);
112/// assert!(empty_seq.as_sequence().is_some());
113/// assert!(empty_seq.as_sequence().unwrap().is_empty());
114///
115/// let five = yaml!(5);
116/// assert!(matches!(five.as_u64(), Some(5)));
117///
118/// let ten = yaml!(10);
119///
120/// let simple_map = yaml!({
121///     5 => 10 // No trailing comma
122/// });
123/// assert!(simple_map.as_mapping().is_some());
124/// assert_eq!(simple_map.as_mapping().unwrap().len(), 1);
125/// assert_eq!(simple_map.as_mapping().unwrap().get(&five).unwrap(), &ten);
126///
127/// let simple_map_2 = yaml!({
128///     5 => 10, // Trailing comma
129/// });
130/// assert_eq!(simple_map_2, simple_map);
131///
132/// let nested_map = yaml!({
133///     5 => 10,
134///     10 => yaml!({ }),
135/// });
136/// let nested_map_2 = yaml!({
137///     10 => yaml!({ }),
138///     5 => 10
139/// });
140/// assert_eq!(nested_map, nested_map_2);
141///
142/// let seq = yaml!([ 5, 5, 10 ]);
143/// assert!(seq.as_sequence().is_some());
144/// assert_eq!(seq[0], five);
145/// assert_eq!(seq[1], five);
146/// assert_eq!(seq[2], ten);
147/// assert!(seq[3].is_null());
148///
149/// # }
150/// ```
151#[macro_export]
152macro_rules! yaml {
153    // Map: empty
154    ({}) => {
155        serde_yaml::Value::Mapping(dict!(serde_yaml::Mapping::new(), {}))
156    };
157    // Map: without trailing `,`.
158    ({ $( $k:expr => $v:expr ),+ } ) => {
159        serde_yaml::Value::Mapping(dict!(serde_yaml::Mapping::new(), { $($k => $v,)* }))
160    };
161    // Map: with trailing `,`.
162    ({ $( $k:expr => $v:expr ),+, } ) => {
163        serde_yaml::Value::Mapping(dict!(serde_yaml::Mapping::new(), { $($k => $v,)* }))
164    };
165    // Sequence: empty
166    ([]) => {
167        serde_yaml::Value::Sequence(seq!(serde_yaml::Sequence::new(), []))
168    };
169    // Sequence: without trailing `,`.
170    ( [ $( $v:expr ),+ ] ) => {
171        serde_yaml::Value::Sequence(seq!(serde_yaml::Sequence::new(), [$($v,)* ]))
172    };
173    // Sequence: with trailing `,`.
174    ( [ $( $v:expr ),+, ] ) => {
175        serde_yaml::Value::Sequence(seq!(serde_yaml::Sequence::new(), [$($v,)* ]))
176    };
177    // Anything else: convert to YAML.
178    ( $v:expr ) => {
179        serde_yaml::Value::from($v)
180    }
181}
182
183/// Utility extensions to manipulate yaml.
184pub trait YamlExt {
185    /// Convert a yaml subtree into a sequence.
186    ///
187    /// This works only if the yaml subtree is either null or already a sequence.
188    fn to_seq_mut(&mut self) -> Option<&mut serde_yaml::Sequence>;
189}
190impl YamlExt for serde_yaml::Value {
191    /// Convert a yaml subtree into a sequence.
192    ///
193    /// This works only if the yaml subtree is either null or already a sequence.
194    fn to_seq_mut(&mut self) -> Option<&mut serde_yaml::Sequence> {
195        if self.is_null() {
196            *self = yaml!([]);
197        }
198        self.as_sequence_mut()
199    }
200}
201
202/// Utility function: return `true`.
203pub fn true_() -> bool {
204    true
205}
206
207pub trait AsRumaError {
208    fn as_ruma_error(&self) -> Option<&matrix_sdk::ruma::api::client::Error>;
209}
210impl AsRumaError for matrix_sdk::HttpError {
211    fn as_ruma_error(&self) -> Option<&matrix_sdk::ruma::api::client::Error> {
212        match *self {
213            matrix_sdk::HttpError::Api(
214                matrix_sdk::ruma::api::error::FromHttpResponseError::Server(
215                    matrix_sdk::ruma::api::error::ServerError::Known(
216                        matrix_sdk::RumaApiError::ClientApi(ref err),
217                    ),
218                ),
219            ) => Some(err),
220            _ => None,
221        }
222    }
223}
224impl AsRumaError for matrix_sdk::Error {
225    fn as_ruma_error(&self) -> Option<&matrix_sdk::ruma::api::client::Error> {
226        match *self {
227            matrix_sdk::Error::Http(ref err) => err.as_ruma_error(),
228            _ => None,
229        }
230    }
231}
232
233#[async_trait]
234pub trait Retry {
235    async fn auto_retry(&self, attempts: u64) -> Result<reqwest::Response, anyhow::Error>;
236}
237
238#[async_trait]
239impl Retry for reqwest::RequestBuilder {
240    async fn auto_retry(&self, max_attempts: u64) -> Result<reqwest::Response, anyhow::Error> {
241        /// The duration of the retry will be picked randomly within this interval,
242        /// plus an exponential backoff.
243        const BASE_INTERVAL_MS: std::ops::Range<u64> = 300..1000;
244
245        let mut attempt = 1;
246        loop {
247            match self
248                .try_clone()
249                .expect("Cannot auto-retry non-clonable requests")
250                .send()
251                .await
252            {
253                Ok(response) => {
254                    debug!("auto_retry success");
255                    break Ok(response);
256                }
257                Err(err) => {
258                    debug!("auto_retry error {:?} => {:?}", err, err.status());
259                    // FIXME: Is this the right way to decide when to retry?
260                    let should_retry = attempt < max_attempts
261                        && (err.is_connect() || err.is_timeout() || err.is_request());
262
263                    if should_retry {
264                        let duration =
265                            (attempt * attempt) * rand::thread_rng().gen_range(BASE_INTERVAL_MS);
266                        attempt += 1;
267                        debug!("auto_retry: sleeping {}ms", duration);
268                        tokio::time::sleep(std::time::Duration::from_millis(duration)).await;
269                    } else {
270                        debug!("auto_retry: giving up!");
271                        return Err(err.into());
272                    }
273                }
274            }
275        }
276    }
277}