use anyhow::Result;
use crate::cli::gmail::helpers::label_ids_contain_any;
use crate::gmail::types::Label;
const SYSTEM_LABEL_ALLOWLIST: &[&str] = &[
"INBOX",
"SENT",
"UNREAD",
"STARRED",
"IMPORTANT",
"SPAM",
"TRASH",
];
fn is_replayable_system_label(label_id: &str) -> bool {
SYSTEM_LABEL_ALLOWLIST.contains(&label_id) || label_id.starts_with("CATEGORY_")
}
pub(crate) fn resolve_label_ids(
source_label_ids: &[String],
drop_label_ids: &[String],
destination_label_id: Option<&str>,
) -> Vec<String> {
let mut ids: Vec<String> = source_label_ids
.iter()
.filter(|id| is_replayable_system_label(id))
.filter(|id| !drop_label_ids.iter().any(|dropped| dropped == *id))
.cloned()
.collect();
if let Some(label) = destination_label_id {
if !ids.iter().any(|id| id == label) {
ids.push(label.to_string());
}
}
ids
}
pub(crate) fn resolve_label_id_by_name(labels: &[Label], name: &str) -> Result<String> {
labels
.iter()
.find(|label| label.name == name)
.map(|label| label.id.clone())
.ok_or_else(|| {
anyhow::anyhow!(
"label {name:?} does not exist in the destination mailbox; `gmail insert` \
never auto-creates labels — create it first (e.g. `gmail label list`, or via \
Gmail's own label settings)"
)
})
}
pub(crate) fn lands_in_inbox_or_unread(label_ids: &[String]) -> bool {
label_ids_contain_any(label_ids, &["INBOX", "UNREAD"])
}
pub(crate) fn lands_in_trash_or_spam(label_ids: &[String]) -> bool {
label_ids_contain_any(label_ids, &["TRASH", "SPAM"])
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
fn label(id: &str, name: &str) -> Label {
Label {
id: id.to_string(),
name: name.to_string(),
..Label::default()
}
}
#[test]
fn resolve_label_ids_keeps_only_allow_listed_system_labels() {
let ids = resolve_label_ids(
&[
"INBOX".to_string(),
"Label_1".to_string(),
"IMPORTANT".to_string(),
],
&[],
None,
);
assert_eq!(ids, vec!["INBOX".to_string(), "IMPORTANT".to_string()]);
}
#[test]
fn resolve_label_ids_excludes_draft_despite_being_a_system_label() {
let ids = resolve_label_ids(&["DRAFT".to_string(), "INBOX".to_string()], &[], None);
assert_eq!(ids, vec!["INBOX".to_string()]);
}
#[test]
fn resolve_label_ids_replays_category_labels() {
let ids = resolve_label_ids(&["CATEGORY_PROMOTIONS".to_string()], &[], None);
assert_eq!(ids, vec!["CATEGORY_PROMOTIONS".to_string()]);
}
#[test]
fn resolve_label_ids_applies_drop_label_after_the_system_filter() {
let ids = resolve_label_ids(
&["INBOX".to_string(), "UNREAD".to_string()],
&["INBOX".to_string(), "UNREAD".to_string()],
None,
);
assert!(ids.is_empty());
}
#[test]
fn resolve_label_ids_dropping_a_user_label_is_a_silent_no_op() {
let ids = resolve_label_ids(&["INBOX".to_string()], &["Label_1".to_string()], None);
assert_eq!(ids, vec!["INBOX".to_string()]);
}
#[test]
fn resolve_label_ids_unions_the_destination_label() {
let ids = resolve_label_ids(&["INBOX".to_string()], &[], Some("Label_restore"));
assert_eq!(ids, vec!["INBOX".to_string(), "Label_restore".to_string()]);
}
#[test]
fn resolve_label_ids_does_not_duplicate_the_destination_label_if_already_present() {
let ids = resolve_label_ids(&["Label_restore".to_string()], &[], Some("Label_restore"));
assert_eq!(ids, vec!["Label_restore".to_string()]);
}
#[test]
fn resolve_label_id_by_name_finds_a_match() {
let labels = vec![label("Label_1", "Restored"), label("INBOX", "INBOX")];
assert_eq!(
resolve_label_id_by_name(&labels, "Restored").unwrap(),
"Label_1"
);
}
#[test]
fn resolve_label_id_by_name_errors_without_auto_creating() {
let labels = vec![label("INBOX", "INBOX")];
let err = resolve_label_id_by_name(&labels, "Missing").unwrap_err();
assert!(err.to_string().contains("does not exist"));
assert!(err.to_string().contains("never auto-creates"));
}
#[test]
fn lands_in_inbox_or_unread_detects_either() {
assert!(lands_in_inbox_or_unread(&["INBOX".to_string()]));
assert!(lands_in_inbox_or_unread(&["UNREAD".to_string()]));
assert!(!lands_in_inbox_or_unread(&["SENT".to_string()]));
}
#[test]
fn lands_in_trash_or_spam_detects_either() {
assert!(lands_in_trash_or_spam(&["TRASH".to_string()]));
assert!(lands_in_trash_or_spam(&["SPAM".to_string()]));
assert!(!lands_in_trash_or_spam(&["SENT".to_string()]));
}
}