use std::fmt;
use anyhow::Result;
use clap::{Parser, ValueEnum};
use io_gmail::v1::rest::history::{
GmailHistoryLabel, GmailHistoryMessage, GmailHistoryType,
list::{GmailHistoryList, GmailHistoryListParams},
};
use pimalaya_cli::printer::Printer;
use schemars::JsonSchema;
use serde::Serialize;
use crate::{gmail::client::GmailClient, shared::output::Paginated};
#[derive(Debug, Parser)]
pub struct GmailHistoryListCommand {
#[arg(long = "start-history-id", value_name = "ID")]
pub start_history_id: String,
#[arg(long = "label-id", value_name = "ID")]
pub label_id: Option<String>,
#[arg(long = "history-type", value_name = "TYPE")]
pub history_types: Vec<HistoryTypeArg>,
#[arg(short = 's', long, value_name = "N")]
pub max_results: Option<u32>,
#[arg(long, value_name = "TOKEN")]
pub page_token: Option<String>,
}
impl GmailHistoryListCommand {
pub fn execute(self, printer: &mut impl Printer, client: &mut GmailClient) -> Result<()> {
let types: Vec<GmailHistoryType> =
self.history_types.iter().copied().map(Into::into).collect();
let out = {
let params = GmailHistoryListParams {
start_history_id: &self.start_history_id,
label_id: self.label_id.as_deref(),
history_types: &types,
max_results: self.max_results,
page_token: self.page_token.as_deref(),
};
let c = GmailHistoryList::new(&client.auth, &client.user_id, ¶ms)?;
client.run(c)?
};
let resp = out.response;
let history = resp
.history
.into_iter()
.map(|record| GmailHistoryRecordOutput {
id: record.id,
messages_added: message_ids(record.messages_added),
messages_deleted: message_ids(record.messages_deleted),
labels_added: label_changes(record.labels_added),
labels_removed: label_changes(record.labels_removed),
})
.collect();
let output = GmailHistoryListOutput {
history_id: resp.history_id,
history,
};
printer.out(Paginated::new(output, resp.next_page_token))
}
}
#[derive(Clone, Copy, Debug, ValueEnum)]
#[clap(rename_all = "camelCase")]
pub enum HistoryTypeArg {
MessageAdded,
MessageDeleted,
LabelAdded,
LabelRemoved,
}
impl From<HistoryTypeArg> for GmailHistoryType {
fn from(arg: HistoryTypeArg) -> Self {
match arg {
HistoryTypeArg::MessageAdded => GmailHistoryType::MessageAdded,
HistoryTypeArg::MessageDeleted => GmailHistoryType::MessageDeleted,
HistoryTypeArg::LabelAdded => GmailHistoryType::LabelAdded,
HistoryTypeArg::LabelRemoved => GmailHistoryType::LabelRemoved,
}
}
}
#[derive(Serialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub(crate) struct GmailHistoryListOutput {
#[serde(skip_serializing_if = "Option::is_none")]
history_id: Option<String>,
history: Vec<GmailHistoryRecordOutput>,
}
impl fmt::Display for GmailHistoryListOutput {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let history_id = self.history_id.as_deref().unwrap_or("(none)");
writeln!(f, "New history id: {history_id}")?;
if self.history.is_empty() {
return writeln!(f, "No history changes since the given history id.");
}
for record in &self.history {
writeln!(
f,
"{}: +{}msg -{}msg +{}lbl -{}lbl",
record.id,
record.messages_added.len(),
record.messages_deleted.len(),
record.labels_added.len(),
record.labels_removed.len(),
)?;
}
Ok(())
}
}
#[derive(Serialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub(crate) struct GmailHistoryRecordOutput {
id: String,
messages_added: Vec<String>,
messages_deleted: Vec<String>,
labels_added: Vec<GmailHistoryLabelOutput>,
labels_removed: Vec<GmailHistoryLabelOutput>,
}
#[derive(Serialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub(crate) struct GmailHistoryLabelOutput {
message_id: String,
label_ids: Vec<String>,
}
fn message_ids(messages: Vec<GmailHistoryMessage>) -> Vec<String> {
messages
.into_iter()
.map(|message| message.message.id)
.collect()
}
fn label_changes(labels: Vec<GmailHistoryLabel>) -> Vec<GmailHistoryLabelOutput> {
labels
.into_iter()
.map(|label| GmailHistoryLabelOutput {
message_id: label.message.id,
label_ids: label.label_ids,
})
.collect()
}