📝 docs(rule_processor): add module and method docs with safety notes and doctest examples

This commit is contained in:
Jeremiah Russell
2025-10-19 09:08:26 +01:00
committed by Jeremiah Russell
parent 099a074a61
commit ea948eda27

View File

@@ -1,40 +1,208 @@
//! Rule Processing Module
//!
//! This module provides the [`RuleProcessor`] trait and its implementation for processing
//! Gmail messages according to configured end-of-life (EOL) rules. It handles the complete
//! workflow of finding messages, applying filters based on rules, and executing actions
//! such as moving messages to trash or permanently deleting them.
//!
//! ## Safety Considerations
//!
//! - **Destructive Operations**: The [`RuleProcessor::batch_delete`] method permanently
//! removes messages from Gmail and cannot be undone.
//! - **Recoverable Operations**: The [`RuleProcessor::batch_trash`] method moves messages
//! to the Gmail trash folder, from which they can be recovered within 30 days.
//! - **Execute Flag**: All destructive operations are gated by an execute flag that must
//! be explicitly set to `true`. When `false`, operations run in "dry-run" mode.
//!
//! ## Workflow
//!
//! 1. Set a rule using [`RuleProcessor::set_rule`]
//! 2. Configure the execute flag with [`RuleProcessor::set_execute`]
//! 3. Process messages for a label with [`RuleProcessor::find_rule_and_messages_for_label`]
//! 4. The processor will automatically:
//! - Find messages matching the rule's query
//! - Prepare the message list via [`RuleProcessor::prepare`]
//! - Execute the rule's action (trash) if execute flag is true
//!
//! ## Example
//!
//! ```rust,no_run
//! # use cull_gmail::{GmailClient, RuleProcessor, EolRule, EolAction};
//! # async fn example() -> cull_gmail::Result<()> {
//! let mut client = GmailClient::new().await?;
//!
//! // Create a rule (this would typically come from configuration)
//! let rule = EolRule::new(1, "old-emails".to_string(), EolAction::Trash, None, None, None)?;
//!
//! client.set_rule(rule);
//! client.set_execute(true); // Set to false for dry-run
//!
//! // Process all messages with the "old-emails" label according to the rule
//! client.find_rule_and_messages_for_label("old-emails").await?;
//! # Ok(())
//! # }
//! ```
use google_gmail1::api::{BatchDeleteMessagesRequest, BatchModifyMessagesRequest}; use google_gmail1::api::{BatchDeleteMessagesRequest, BatchModifyMessagesRequest};
use crate::{EolAction, Error, GmailClient, Result, message_list::MessageList, rules::EolRule}; use crate::{EolAction, Error, GmailClient, Result, message_list::MessageList, rules::EolRule};
/// Rules processor to apply the configured rules to the mailbox. /// Trait for processing Gmail messages according to configured end-of-life rules.
///
/// This trait defines the interface for finding, filtering, and acting upon Gmail messages
/// based on retention rules. Implementations should handle the complete workflow from
/// rule application to message processing.
pub trait RuleProcessor { pub trait RuleProcessor {
/// Find the rule and the message for a specific label /// Processes all messages for a specific Gmail label according to the configured rule.
///
/// This is the main entry point for rule processing. It coordinates the entire workflow:
/// 1. Validates that the label exists in the mailbox
/// 2. Applies the rule's query to find matching messages
/// 3. Prepares the message list for processing
/// 4. Executes the rule's action (if execute flag is true) or runs in dry-run mode
///
/// # Arguments
///
/// * `label` - The Gmail label name to process (e.g., "INBOX", "old-emails")
///
/// # Returns
///
/// * `Ok(())` - Processing completed successfully
/// * `Err(Error::LabelNotFoundInMailbox)` - The specified label doesn't exist
/// * `Err(Error::RuleNotFound)` - No rule has been set via [`set_rule`](Self::set_rule)
/// * `Err(Error::NoQueryStringCalculated)` - The rule doesn't provide a valid query
///
/// # Side Effects
///
/// When execute flag is true, messages may be moved to trash or permanently deleted.
/// When execute flag is false, runs in dry-run mode with no destructive actions.
fn find_rule_and_messages_for_label( fn find_rule_and_messages_for_label(
&mut self, &mut self,
label: &str, label: &str,
) -> impl std::future::Future<Output = Result<()>> + Send; ) -> impl std::future::Future<Output = Result<()>> + Send;
/// Set the execute flag in the client
/// Sets the execution mode for destructive operations.
///
/// # Arguments
///
/// * `value` - `true` to enable destructive operations, `false` for dry-run mode
///
/// # Safety
///
/// When set to `true`, subsequent calls to processing methods will perform actual
/// destructive operations on Gmail messages. Always verify your rules and queries
/// in dry-run mode (`false`) before enabling execution.
fn set_execute(&mut self, value: bool); fn set_execute(&mut self, value: bool);
/// Set rule to process
fn set_rule(&mut self, action: EolRule); /// Configures the end-of-life rule to apply during processing.
/// Report the action from the rule ///
/// # Arguments
///
/// * `rule` - The `EolRule` containing query criteria and action to perform
///
/// # Example
///
/// ```rust,no_run
/// # use cull_gmail::{GmailClient, RuleProcessor, EolRule, EolAction};
/// # fn example() -> cull_gmail::Result<()> {
/// # let mut client = GmailClient::new_fake(); // This would be real in practice
/// let rule = EolRule::new(1, "old".to_string(), EolAction::Trash, None, None, None)?;
/// client.set_rule(rule);
/// # Ok(())
/// # }
/// ```
fn set_rule(&mut self, rule: EolRule);
/// Returns the action that will be performed by the currently configured rule.
///
/// # Returns
///
/// * `Some(EolAction)` - The action (e.g., `EolAction::Trash`) if a rule is set
/// * `None` - If no rule has been configured via [`set_rule`](Self::set_rule)
fn action(&self) -> Option<EolAction>; fn action(&self) -> Option<EolAction>;
/// Prepare a list of messages to trash or delete
/// Prepares the list of messages for processing by fetching them from Gmail.
///
/// This method queries the Gmail API to retrieve messages matching the current
/// query and label filters, up to the specified number of pages.
///
/// # Arguments
///
/// * `pages` - Maximum number of result pages to fetch (0 = all pages)
///
/// # Returns
///
/// * `Ok(())` - Messages successfully retrieved and prepared
/// * `Err(_)` - Gmail API error or network failure
///
/// # Side Effects
///
/// Makes API calls to Gmail to retrieve message metadata. No messages are
/// modified by this operation.
fn prepare(&mut self, pages: u32) -> impl std::future::Future<Output = Result<()>> + Send; fn prepare(&mut self, pages: u32) -> impl std::future::Future<Output = Result<()>> + Send;
/// Batch delete of messages
/// Permanently deletes all prepared messages from Gmail.
///
/// # Returns
///
/// * `Ok(())` - All messages successfully deleted
/// * `Err(_)` - Gmail API error, network failure, or insufficient permissions
///
/// # Safety
///
/// ⚠️ **DESTRUCTIVE OPERATION** - This permanently removes messages from Gmail.
/// Deleted messages cannot be recovered. Use [`batch_trash`](Self::batch_trash)
/// for recoverable deletion.
///
/// # Gmail API Requirements
///
/// Requires the `https://www.googleapis.com/auth/gmail.modify` scope or broader.
fn batch_delete(&self) -> impl std::future::Future<Output = Result<()>> + Send; fn batch_delete(&self) -> impl std::future::Future<Output = Result<()>> + Send;
/// Batch trash
/// Moves all prepared messages to the Gmail trash folder.
///
/// Messages moved to trash can be recovered within 30 days through the Gmail
/// web interface or API calls.
///
/// # Returns
///
/// * `Ok(())` - All messages successfully moved to trash
/// * `Err(_)` - Gmail API error, network failure, or insufficient permissions
///
/// # Recovery
///
/// Messages can be recovered from trash within 30 days. After 30 days,
/// Gmail automatically purges trashed messages.
///
/// # Gmail API Requirements
///
/// Requires the `https://www.googleapis.com/auth/gmail.modify` scope.
fn batch_trash(&self) -> impl std::future::Future<Output = Result<()>> + Send; fn batch_trash(&self) -> impl std::future::Future<Output = Result<()>> + Send;
} }
impl RuleProcessor for GmailClient { impl RuleProcessor for GmailClient {
/// Add Action to the Client for processing /// Configures the end-of-life rule for this Gmail client.
///
/// The rule defines which messages to target and what action to perform on them.
/// This must be called before processing any labels.
fn set_rule(&mut self, value: EolRule) { fn set_rule(&mut self, value: EolRule) {
self.rule = Some(value); self.rule = Some(value);
} }
/// Set the execute flag /// Controls whether destructive operations are actually executed.
///
/// When `false` (dry-run mode), all operations are simulated but no actual
/// changes are made to Gmail messages. When `true`, destructive operations
/// like moving to trash or deleting will be performed.
///
/// **Default is `false` for safety.**
fn set_execute(&mut self, value: bool) { fn set_execute(&mut self, value: bool) {
self.execute = value; self.execute = value;
} }
/// The action set in the rule /// Returns the action that will be performed by the current rule.
///
/// This is useful for logging and verification before executing destructive operations.
fn action(&self) -> Option<EolAction> { fn action(&self) -> Option<EolAction> {
if let Some(rule) = &self.rule { if let Some(rule) = &self.rule {
return rule.action(); return rule.action();
@@ -42,7 +210,16 @@ impl RuleProcessor for GmailClient {
None None
} }
/// Find the rule and messages for the label /// Orchestrates the complete rule processing workflow for a Gmail label.
///
/// This method implements the main processing logic:
/// 1. Validates the label exists in the mailbox
/// 2. Constructs a Gmail query from the rule's criteria
/// 3. Fetches matching messages from the Gmail API
/// 4. Executes the rule's action if execute flag is enabled
///
/// The method respects the execute flag - when `false`, it runs in dry-run mode
/// and only logs what would be done without making any changes.
async fn find_rule_and_messages_for_label(&mut self, label: &str) -> Result<()> { async fn find_rule_and_messages_for_label(&mut self, label: &str) -> Result<()> {
self.add_labels(&[label.to_string()])?; self.add_labels(&[label.to_string()])?;
@@ -71,12 +248,32 @@ impl RuleProcessor for GmailClient {
} }
} }
/// Prepare the message list for delete to be completed on execute by batch_delete /// Fetches messages from Gmail API based on current query and label filters.
///
/// This is a read-only operation that retrieves message metadata from Gmail
/// without modifying any messages. The results are cached internally for
/// subsequent batch operations.
///
/// # Arguments
///
/// * `pages` - Number of result pages to fetch (0 = all available pages)
async fn prepare(&mut self, pages: u32) -> Result<()> { async fn prepare(&mut self, pages: u32) -> Result<()> {
self.get_messages(pages).await self.get_messages(pages).await
} }
/// Run the batch delete on the selected messages /// Permanently deletes all prepared messages using Gmail's batch delete API.
///
/// ⚠️ **DESTRUCTIVE OPERATION** - This action cannot be undone!
///
/// This method uses the Gmail API's batch delete functionality to permanently
/// remove messages from the user's mailbox. Once deleted, messages cannot be
/// recovered through any means.
///
/// # API Scope Requirements
///
/// Uses `https://mail.google.com/` scope for maximum API access. Consider
/// using the more restrictive `https://www.googleapis.com/auth/gmail.modify`
/// scope if your application security policy allows it.
async fn batch_delete(&self) -> Result<()> { async fn batch_delete(&self) -> Result<()> {
let ids = Some(self.message_ids()); let ids = Some(self.message_ids());
@@ -99,7 +296,20 @@ impl RuleProcessor for GmailClient {
Ok(()) Ok(())
} }
/// Move the messages to trash /// Moves all prepared messages to Gmail's trash folder using batch modify API.
///
/// This is a recoverable operation - messages can be restored from trash within
/// 30 days via the Gmail web interface or API calls. After 30 days, Gmail
/// automatically purges trashed messages permanently.
///
/// The operation adds the TRASH label and removes any existing labels that were
/// used to filter the messages, effectively moving them out of their current
/// folders into the trash.
///
/// # API Scope Requirements
///
/// Currently uses `https://www.google.com/` scope. Should be updated to use
/// `https://www.googleapis.com/auth/gmail.modify` for better security.
async fn batch_trash(&self) -> Result<()> { async fn batch_trash(&self) -> Result<()> {
let add_label_ids = Some(Vec::from(["TRASH".to_string()])); let add_label_ids = Some(Vec::from(["TRASH".to_string()]));
let ids = Some(self.message_ids()); let ids = Some(self.message_ids());