use std::io::Write;
use std::path::PathBuf;
use anyhow::Result;
use serde::Serialize;
use crate::cli::gmail::format::{write_scalar_jsonl, JsonlSerialize};
#[derive(Debug, Default, Serialize)]
pub(crate) struct SyncReport {
pub(crate) actions: Vec<SyncAction>,
pub(crate) errors: Vec<SyncError>,
}
impl JsonlSerialize for SyncReport {
fn write_jsonl(&self, out: &mut dyn Write) -> Result<()> {
write_scalar_jsonl(self, out)
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub(crate) enum SyncAction {
Fetched {
id: String,
path: PathBuf,
bytes: u64,
},
WouldFetch { id: String },
LabelsUpdated {
id: String,
added: Vec<String>,
removed: Vec<String>,
},
Deleted { id: String },
Undeleted { id: String },
WouldDelete { id: String },
WouldUndelete { id: String },
Vanished { id: String },
Note { message: String },
}
#[derive(Debug, Clone, Serialize)]
pub(crate) struct SyncError {
pub(crate) id: String,
pub(crate) reason: String,
}
#[derive(Debug, Default, PartialEq, Eq, Serialize)]
pub(crate) struct SyncSummary {
pub(crate) fetched: usize,
pub(crate) would_fetch: usize,
pub(crate) vanished: usize,
pub(crate) labels_updated: usize,
pub(crate) deleted: usize,
pub(crate) undeleted: usize,
pub(crate) would_delete: usize,
pub(crate) would_undelete: usize,
pub(crate) errors: usize,
}
impl SyncReport {
pub(crate) fn summary(&self) -> SyncSummary {
let mut summary = SyncSummary {
errors: self.errors.len(),
..SyncSummary::default()
};
for action in &self.actions {
match action {
SyncAction::Fetched { .. } => summary.fetched += 1,
SyncAction::WouldFetch { .. } => summary.would_fetch += 1,
SyncAction::LabelsUpdated { .. } => summary.labels_updated += 1,
SyncAction::Deleted { .. } => summary.deleted += 1,
SyncAction::Undeleted { .. } => summary.undeleted += 1,
SyncAction::WouldDelete { .. } => summary.would_delete += 1,
SyncAction::WouldUndelete { .. } => summary.would_undelete += 1,
SyncAction::Vanished { .. } => summary.vanished += 1,
SyncAction::Note { .. } => {}
}
}
summary
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn summary_counts_each_action_variant() {
let report = SyncReport {
actions: vec![
SyncAction::Fetched {
id: "m1".to_string(),
path: PathBuf::from("m1.eml"),
bytes: 1,
},
SyncAction::WouldFetch {
id: "m2".to_string(),
},
SyncAction::LabelsUpdated {
id: "m3".to_string(),
added: vec!["IMPORTANT".to_string()],
removed: vec![],
},
SyncAction::Deleted {
id: "m4".to_string(),
},
SyncAction::Undeleted {
id: "m5".to_string(),
},
SyncAction::WouldDelete {
id: "m6".to_string(),
},
SyncAction::WouldUndelete {
id: "m7".to_string(),
},
SyncAction::Vanished {
id: "m8".to_string(),
},
SyncAction::Note {
message: "one note".to_string(),
},
SyncAction::Note {
message: "another note".to_string(),
},
],
errors: vec![SyncError {
id: "m9".to_string(),
reason: "boom".to_string(),
}],
};
assert_eq!(
report.summary(),
SyncSummary {
fetched: 1,
would_fetch: 1,
vanished: 1,
labels_updated: 1,
deleted: 1,
undeleted: 1,
would_delete: 1,
would_undelete: 1,
errors: 1,
}
);
}
#[test]
fn summary_of_empty_report_is_all_zero() {
assert_eq!(SyncReport::default().summary(), SyncSummary::default());
}
}