auberge 0.12.2

CLI tool for managing self-hosted infrastructure with Ansible
use serde::Deserialize;

#[derive(Debug, Deserialize)]
pub struct ResticStatus {
    pub percent_done: f64,
    pub total_bytes: Option<u64>,
    pub bytes_done: Option<u64>,
}

#[derive(Debug, Deserialize)]
pub struct ResticSummary {
    pub snapshot_id: String,
    #[allow(dead_code)]
    pub files_new: u64,
    #[allow(dead_code)]
    pub files_changed: u64,
    #[allow(dead_code)]
    pub data_added: u64,
}

#[derive(Debug, Deserialize)]
#[serde(tag = "message_type", rename_all = "lowercase")]
pub enum ResticMessage {
    Status(ResticStatus),
    Summary(ResticSummary),
}

pub fn parse_restic_message(line: &str) -> Option<ResticMessage> {
    serde_json::from_str(line).ok()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parse_restic_status_line() {
        let line = r#"{"message_type":"status","percent_done":0.5,"total_bytes":1048576,"bytes_done":524288}"#;
        let msg = parse_restic_message(line).unwrap();
        match msg {
            ResticMessage::Status(s) => {
                assert!((s.percent_done - 0.5).abs() < f64::EPSILON);
                assert_eq!(s.total_bytes, Some(1048576));
                assert_eq!(s.bytes_done, Some(524288));
            }
            _ => panic!("expected Status"),
        }
    }

    #[test]
    fn parse_restic_summary_line() {
        let line = r#"{"message_type":"summary","snapshot_id":"abc123","files_new":10,"files_changed":2,"data_added":1048576}"#;
        let msg = parse_restic_message(line).unwrap();
        match msg {
            ResticMessage::Summary(s) => {
                assert_eq!(s.snapshot_id, "abc123");
                assert_eq!(s.files_new, 10);
                assert_eq!(s.files_changed, 2);
                assert_eq!(s.data_added, 1048576);
            }
            _ => panic!("expected Summary"),
        }
    }

    #[test]
    fn parse_restic_plain_text_returns_none() {
        assert!(parse_restic_message("using parent snapshot abc123").is_none());
    }

    #[test]
    fn parse_restic_malformed_json_returns_none() {
        assert!(parse_restic_message("{bad json}").is_none());
    }

    #[test]
    fn parse_restic_zero_percent() {
        let line = r#"{"message_type":"status","percent_done":0.0}"#;
        let msg = parse_restic_message(line).unwrap();
        match msg {
            ResticMessage::Status(s) => {
                assert!((s.percent_done).abs() < f64::EPSILON);
                assert_eq!(s.total_bytes, None);
                assert_eq!(s.bytes_done, None);
            }
            _ => panic!("expected Status"),
        }
    }

    #[test]
    fn parse_restic_full_percent() {
        let line =
            r#"{"message_type":"status","percent_done":1.0,"total_bytes":100,"bytes_done":100}"#;
        let msg = parse_restic_message(line).unwrap();
        match msg {
            ResticMessage::Status(s) => assert!((s.percent_done - 1.0).abs() < f64::EPSILON),
            _ => panic!("expected Status"),
        }
    }
}