1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
use std::time::Duration;

use crate::{Record, TypedValue};

pub const RETRY_INTERVAL: Duration = Duration::from_secs(1);
pub const MAX_RETRIES: usize = 120;

pub fn debug_print_records<T>(records: &[Record<T>])
where
    T: TypedValue,
{
    for record in records {
        debug_print_record(record)
    }
}
pub fn debug_print_record<T>(record: &Record<T>)
where
    T: TypedValue,
{
    eprintln!(
        r#"<Record {}_{} [{}]>"#,
        record.id(),
        record.typ(),
        record.value.label().unwrap_or_default()
    );
}

pub struct RetryOpts {
    pub max_retries: usize,
    pub name: Option<String>,
    pub interval: Duration,
}
impl Default for RetryOpts {
    fn default() -> Self {
        Self {
            max_retries: MAX_RETRIES,
            interval: RETRY_INTERVAL,
            name: None,
        }
    }
}
impl RetryOpts {
    pub fn with_name(name: String) -> Self {
        Self {
            name: Some(name),
            ..Default::default()
        }
    }
}

/// Repeat a HTTP request until it returns a successfull status.
pub async fn wait_for_ready(
    client: &reqwest::Client,
    opts: RetryOpts,
    req_builder: impl Fn() -> Result<reqwest::Request, reqwest::Error>,
) -> Result<(), std::io::Error> {
    let mut interval = tokio::time::interval(opts.interval);
    let name = opts.name.unwrap_or_default();
    for _i in 0..opts.max_retries {
        let req = req_builder()
            .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, format!("{}", err)))?;
        let url = req.url().to_string();
        match client.execute(req).await {
            Ok(res) => {
                if res.status().is_success() {
                    return Ok(());
                } else {
                    log::warn!(
                        "Failed to connect to {} at {}: {}",
                        name,
                        url,
                        res.status().canonical_reason().unwrap()
                    );
                }
            }
            Err(err) => {
                log::warn!("Failed to connect to {} at {}: {}", name, url, err);
            }
        }
        interval.tick().await;
    }
    Err(std::io::Error::new(
        std::io::ErrorKind::TimedOut,
        "Cannot reach service {}",
    ))
}