use std::io::Write;
use anyhow::Result;
use serde::Serialize;
use crate::cli::gmail::format::{write_scalar_jsonl, JsonlSerialize};
#[derive(Debug, Default, Serialize)]
pub(crate) struct InsertReport {
pub(crate) actions: Vec<InsertAction>,
pub(crate) errors: Vec<InsertError>,
#[serde(skip)]
pub(crate) stop_error: Option<anyhow::Error>,
}
impl JsonlSerialize for InsertReport {
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 InsertAction {
Inserted {
id: String,
inserted_id: String,
label_ids: Vec<String>,
},
WouldInsert { id: String, label_ids: Vec<String> },
Skipped { id: String, reason: SkipReason },
Note { message: String },
}
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub(crate) enum SkipReason {
AlreadyInserted,
FoundRemote,
}
#[derive(Debug, Clone, Serialize)]
pub(crate) struct InsertError {
pub(crate) id: String,
pub(crate) reason: String,
}
#[derive(Debug, Default, PartialEq, Eq, Serialize)]
pub(crate) struct InsertSummary {
pub(crate) inserted: usize,
pub(crate) would_insert: usize,
pub(crate) skipped: usize,
pub(crate) errors: usize,
}
impl InsertReport {
pub(crate) fn summary(&self) -> InsertSummary {
let mut summary = InsertSummary {
errors: self.errors.len(),
..InsertSummary::default()
};
for action in &self.actions {
match action {
InsertAction::Inserted { .. } => summary.inserted += 1,
InsertAction::WouldInsert { .. } => summary.would_insert += 1,
InsertAction::Skipped { .. } => summary.skipped += 1,
InsertAction::Note { .. } => {}
}
}
summary
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn summary_counts_each_action_variant() {
let report = InsertReport {
actions: vec![
InsertAction::Inserted {
id: "m1".to_string(),
inserted_id: "new1".to_string(),
label_ids: vec!["INBOX".to_string()],
},
InsertAction::WouldInsert {
id: "m2".to_string(),
label_ids: vec![],
},
InsertAction::Skipped {
id: "m3".to_string(),
reason: SkipReason::AlreadyInserted,
},
InsertAction::Note {
message: "note".to_string(),
},
],
errors: vec![InsertError {
id: "m4".to_string(),
reason: "boom".to_string(),
}],
stop_error: None,
};
assert_eq!(
report.summary(),
InsertSummary {
inserted: 1,
would_insert: 1,
skipped: 1,
errors: 1,
}
);
}
#[test]
fn summary_of_empty_report_is_all_zero() {
assert_eq!(InsertReport::default().summary(), InsertSummary::default());
}
#[test]
fn write_jsonl_emits_one_compact_line_for_the_whole_report() {
let report = InsertReport {
actions: vec![InsertAction::Note {
message: "note".to_string(),
}],
errors: vec![],
stop_error: None,
};
let mut buf = Vec::new();
report.write_jsonl(&mut buf).unwrap();
let text = String::from_utf8(buf).unwrap();
assert_eq!(text.lines().count(), 1);
assert!(text.contains("\"note\""));
}
}