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}