From fc85f46e80c14010a27586d372411ef67f3f979f Mon Sep 17 00:00:00 2001 From: Jeremiah Russell Date: Thu, 30 Oct 2025 08:02:18 +0000 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(rule=5Fprocessor)?= =?UTF-8?q?:=20enhance=20Gmail=20message=20handling=20with=20chunk=20proce?= =?UTF-8?q?ssing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - introduce `process_in_chunks` for batch operations to respect API limits - refactor `batch_trash` to use `process_in_chunks` for efficient trashing - add documentation for `process_in_chunks` explaining chunking and API limits --- src/rule_processor.rs | 79 ++++++++++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 23 deletions(-) diff --git a/src/rule_processor.rs b/src/rule_processor.rs index 835f531..7e6efa9 100644 --- a/src/rule_processor.rs +++ b/src/rule_processor.rs @@ -307,27 +307,6 @@ pub trait RuleProcessor { /// Requires the `https://mail.google.com/` scope or broader. fn batch_delete(&mut self) -> impl std::future::Future> + Send; - /// Calls the Gmail API to permanently deletes a slice from the list of messages. - /// - /// # 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://mail.google.com/` scope or broader. - fn call_batch_delete( - &self, - ids: &[String], - ) -> impl std::future::Future> + Send; - /// Calls the Gmail API to move a slice of the prepared messages to the Gmail /// trash folder. /// @@ -349,6 +328,40 @@ pub trait RuleProcessor { /// Requires the `https://www.googleapis.com/auth/gmail.modify` scope. fn batch_trash(&mut self) -> impl std::future::Future> + Send; + /// Chunk the message lists to respect API limits and call required action. + /// + /// # Returns + /// + /// * `Ok(())` - All messages successfully deleted + /// * `Err(_)` - Gmail API error, network failure, or insufficient permissions + /// + fn process_in_chunks( + &self, + message_ids: Vec, + action: EolAction, + ) -> impl std::future::Future> + Send; + + /// Calls the Gmail API to permanently deletes a slice from the list of messages. + /// + /// # 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://mail.google.com/` scope or broader. + fn call_batch_delete( + &self, + ids: &[String], + ) -> impl std::future::Future> + Send; + /// Moves all prepared messages to the Gmail trash folder. /// /// Messages moved to trash can be recovered within 30 days through the Gmail @@ -540,6 +553,13 @@ impl RuleProcessor for GmailClient { self.log_messages("Message with subject `", "` moved to trash") .await?; + self.process_in_chunks(message_ids, EolAction::Trash) + .await?; + + Ok(()) + } + + async fn process_in_chunks(&self, message_ids: Vec, action: EolAction) -> Result<()> { let (chunks, remainder) = message_ids.as_chunks::<1000>(); log::trace!( "Message list chopped into {} chunks with {} ids in the remainder", @@ -547,16 +567,21 @@ impl RuleProcessor for GmailClient { remainder.len() ); + let act = async |action, list| match action { + EolAction::Trash => self.call_batch_trash(list).await, + EolAction::Delete => self.call_batch_delete(list).await, + }; + if !chunks.is_empty() { for (i, chunk) in chunks.iter().enumerate() { log::trace!("Processing chunk {i}"); - self.call_batch_delete(chunk).await?; + act(action, chunk).await?; } } if !remainder.is_empty() { log::trace!("Processing remainder."); - self.call_batch_delete(remainder).await?; + act(action, remainder).await?; } Ok(()) @@ -886,6 +911,14 @@ mod tests { async fn call_batch_trash(&self, _ids: &[String]) -> Result<()> { Ok(()) } + + async fn process_in_chunks( + &self, + _message_ids: Vec, + _action: EolAction, + ) -> Result<()> { + Ok(()) + } } let mut processor = MockProcessor {