✨ feat(rules): support multiple actions per label
- support trash and delete actions for the same label - add `run_rules_for_action` function to filter rules by action - update `get_rules_by_label` to `get_rules_by_label_for_action` - update `find_label` to return a vector of rule ids - update `remove_rule_by_label` to remove all rules with the label
This commit is contained in:
committed by
Jeremiah Russell
parent
afe798dae0
commit
cb53faf3b3
@@ -488,7 +488,54 @@ fn get_config() -> Result<(Config, ClientConfig)> {
|
|||||||
/// The function continues processing even if individual rules fail, logging
|
/// The function continues processing even if individual rules fail, logging
|
||||||
/// warnings for missing rules, processing errors, or action failures.
|
/// warnings for missing rules, processing errors, or action failures.
|
||||||
async fn run_rules(client: &mut GmailClient, rules: Rules, execute: bool) -> Result<()> {
|
async fn run_rules(client: &mut GmailClient, rules: Rules, execute: bool) -> Result<()> {
|
||||||
let rules_by_labels = rules.get_rules_by_label();
|
run_rules_for_action(client, &rules, execute, EolAction::Trash).await?;
|
||||||
|
run_rules_for_action(client, &rules, execute, EolAction::Delete).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executes automated message retention rules across Gmail labels for an action.
|
||||||
|
///
|
||||||
|
/// This function orchestrates the rule-based message processing workflow by:
|
||||||
|
/// 1. Organizing rules by their target labels
|
||||||
|
/// 2. Processing each label according to its configured rule
|
||||||
|
/// 3. Executing or simulating actions based on execution mode
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `client` - Mutable Gmail client for API operations
|
||||||
|
/// * `rules` - Loaded rules configuration containing all retention policies
|
||||||
|
/// * `execute` - Whether to actually perform actions (true) or dry-run (false)
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Returns `Result<()>` indicating success or failure of the rule processing.
|
||||||
|
///
|
||||||
|
/// # Rule Processing Flow
|
||||||
|
///
|
||||||
|
/// For each configured label:
|
||||||
|
/// 1. **Rule Lookup**: Find the retention rule for the label
|
||||||
|
/// 2. **Rule Application**: Apply rule criteria to find matching messages
|
||||||
|
/// 3. **Action Determination**: Determine appropriate action (trash/delete)
|
||||||
|
/// 4. **Execution**: Execute action or simulate for dry-run
|
||||||
|
///
|
||||||
|
/// # Safety Features
|
||||||
|
///
|
||||||
|
/// - **Dry-run mode**: When `execute` is false, actions are logged but not performed
|
||||||
|
/// - **Error isolation**: Errors for individual labels don't stop processing of other labels
|
||||||
|
/// - **Detailed logging**: Comprehensive logging of rule execution and results
|
||||||
|
///
|
||||||
|
/// # Error Handling
|
||||||
|
///
|
||||||
|
/// The function continues processing even if individual rules fail, logging
|
||||||
|
/// warnings for missing rules, processing errors, or action failures.
|
||||||
|
async fn run_rules_for_action(
|
||||||
|
client: &mut GmailClient,
|
||||||
|
rules: &Rules,
|
||||||
|
execute: bool,
|
||||||
|
action: EolAction,
|
||||||
|
) -> Result<()> {
|
||||||
|
let rules_by_labels = rules.get_rules_by_label_for_action(action);
|
||||||
|
|
||||||
for label in rules.labels() {
|
for label in rules.labels() {
|
||||||
let Some(rule) = rules_by_labels.get(&label) else {
|
let Some(rule) = rules_by_labels.get(&label) else {
|
||||||
|
|||||||
46
src/rules.rs
46
src/rules.rs
@@ -248,13 +248,25 @@ impl Rules {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Find the id of the rule that contains a label
|
/// Find the id of the rule that contains a label
|
||||||
fn find_label(&self, label: &str) -> usize {
|
fn find_label(&self, label: &str) -> Vec<usize> {
|
||||||
let rules_by_label = self.get_rules_by_label();
|
let mut rwl = Vec::new();
|
||||||
if let Some(rule) = rules_by_label.get(label) {
|
|
||||||
rule.id()
|
if let Some(t) = self.find_label_for_action(label, EolAction::Trash) {
|
||||||
} else {
|
rwl.push(t);
|
||||||
0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(d) = self.find_label_for_action(label, EolAction::Delete) {
|
||||||
|
rwl.push(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
rwl
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the id of the rule that contains a label
|
||||||
|
fn find_label_for_action(&self, label: &str, action: EolAction) -> Option<usize> {
|
||||||
|
let rules_by_label = self.get_rules_by_label_for_action(action);
|
||||||
|
|
||||||
|
rules_by_label.get(label).map(|r| r.id())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes a rule from the set by its unique ID.
|
/// Removes a rule from the set by its unique ID.
|
||||||
@@ -321,12 +333,14 @@ impl Rules {
|
|||||||
return Err(Error::LabelNotFoundInRules(label.to_string()));
|
return Err(Error::LabelNotFoundInRules(label.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let rule_id = self.find_label(label);
|
let rule_ids = self.find_label(label);
|
||||||
if rule_id == 0 {
|
if rule_ids.is_empty() {
|
||||||
return Err(Error::NoRuleFoundForLabel(label.to_string()));
|
return Err(Error::NoRuleFoundForLabel(label.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.rules.remove(&rule_id.to_string());
|
for id in rule_ids {
|
||||||
|
self.rules.remove(&id.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
log::info!("Rule containing the label `{label}` has been removed.");
|
log::info!("Rule containing the label `{label}` has been removed.");
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -347,17 +361,19 @@ impl Rules {
|
|||||||
/// let retention = Retention::new(MessageAge::Days(30), false);
|
/// let retention = Retention::new(MessageAge::Days(30), false);
|
||||||
/// rules.add_rule(retention, Some("test"), false);
|
/// rules.add_rule(retention, Some("test"), false);
|
||||||
///
|
///
|
||||||
/// let label_map = rules.get_rules_by_label();
|
/// let label_map = rules.get_rules_by_label(EolAction::Trash);
|
||||||
/// if let Some(rule) = label_map.get("test") {
|
/// if let Some(rule) = label_map.get("test") {
|
||||||
/// println!("Rule for 'test' label: {}", rule.describe());
|
/// println!("Rule for 'test' label: {}", rule.describe());
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn get_rules_by_label(&self) -> BTreeMap<String, EolRule> {
|
pub fn get_rules_by_label_for_action(&self, action: EolAction) -> BTreeMap<String, EolRule> {
|
||||||
let mut rbl = BTreeMap::new();
|
let mut rbl = BTreeMap::new();
|
||||||
|
|
||||||
for rule in self.rules.values() {
|
for rule in self.rules.values() {
|
||||||
for label in rule.labels() {
|
if rule.action() == Some(action) {
|
||||||
rbl.insert(label, rule.clone());
|
for label in rule.labels() {
|
||||||
|
rbl.insert(label, rule.clone());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -733,7 +749,7 @@ mod tests {
|
|||||||
let retention = Retention::new(MessageAge::Days(7), false);
|
let retention = Retention::new(MessageAge::Days(7), false);
|
||||||
rules.add_rule(retention, Some("delete-test"), true);
|
rules.add_rule(retention, Some("delete-test"), true);
|
||||||
|
|
||||||
let rules_by_label = rules.get_rules_by_label();
|
let rules_by_label = rules.get_rules_by_label_for_action(EolAction::Delete);
|
||||||
let rule = rules_by_label.get("delete-test").unwrap();
|
let rule = rules_by_label.get("delete-test").unwrap();
|
||||||
assert_eq!(rule.action(), Some(EolAction::Delete));
|
assert_eq!(rule.action(), Some(EolAction::Delete));
|
||||||
}
|
}
|
||||||
@@ -799,7 +815,7 @@ mod tests {
|
|||||||
let retention = Retention::new(MessageAge::Days(30), false);
|
let retention = Retention::new(MessageAge::Days(30), false);
|
||||||
rules.add_rule(retention, Some("mapped-label"), false);
|
rules.add_rule(retention, Some("mapped-label"), false);
|
||||||
|
|
||||||
let label_map = rules.get_rules_by_label();
|
let label_map = rules.get_rules_by_label_for_action(EolAction::Trash);
|
||||||
let rule = label_map.get("mapped-label");
|
let rule = label_map.get("mapped-label");
|
||||||
assert!(rule.is_some());
|
assert!(rule.is_some());
|
||||||
assert!(rule.unwrap().labels().contains(&"mapped-label".to_string()));
|
assert!(rule.unwrap().labels().contains(&"mapped-label".to_string()));
|
||||||
|
|||||||
Reference in New Issue
Block a user