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
//! [`Reconciler`] and associated types.

use netloc_core::reporter::{Data, Reporter};
use std::{convert::Infallible, net::IpAddr, time::Duration};
use thiserror::Error;

use crate::{
    ip_resolver,
    state::{self, State},
};

/// Reconciler encapulates the logic to maintain the reconciliation loop.
///
/// In a loop, it obtains the current IP address, and, if it's a new one,
/// reports it to all of the reporters.
pub struct Reconciler<E: std::fmt::Debug> {
    /// The delay between the reconciliation attempts.
    pub delay: Duration,
    /// An IP resolver.
    pub ip_resolver: ip_resolver::http::Resolver,
    /// A list of reporters to use.
    pub reporters: Vec<Box<dyn Reporter<Error = E>>>,
    /// The state to maintain.
    pub current_ip: State<IpAddr>,
}

impl<E: std::fmt::Debug> Reconciler<E> {
    /// Run the reconciliation loop.
    pub async fn run(&mut self) -> Result<Infallible, Error<E>> {
        loop {
            tokio::time::sleep(self.delay).await;
            self.reconcile_once().await?;
        }
    }

    async fn reconcile_once(&mut self) -> Result<(), Error<E>> {
        let obtained_ip = self
            .ip_resolver
            .resolve()
            .await
            .map_err(Error::IpResolution)?;

        let update_effect = self.current_ip.update(obtained_ip);
        if let state::UpdateEffect::Unchanged = update_effect {
            // No changes.
            return Ok(());
        }

        let data = Data {
            ip: obtained_ip.to_string(),
        };
        self.report_all(&data).await.map_err(Error::Reporting)?;
        Ok(())
    }

    async fn report_all(&self, data: &Data) -> Result<(), E> {
        for reporter in &self.reporters {
            reporter.report(data).await?;
        }
        Ok(())
    }
}

/// A reconciler error.
#[derive(Debug, Error)]
pub enum Error<E: std::fmt::Debug> {
    /// An error occured during an IP resolution.
    #[error("IP resolution failed: {0}")]
    IpResolution(anyhow::Error),

    /// An error occured during reporting.
    #[error("reporting state update failed: {0:?}")]
    Reporting(E),
}