s3rm_rs/lib.rs
1/*!
2# s3rm-rs
3
4s3rm-rs is a fast Amazon S3 object deletion tool.
5It can be used to delete objects from S3 buckets with powerful filtering,
6safety features, and versioning support.
7
8## Features
9
10- **High Performance**: Parallel deletion using S3 batch API (up to 1000 objects per request)
11 with configurable worker pools (1–65,535 concurrent workers).
12- **Flexible Filtering**: Regex patterns on keys/content-type/metadata/tags, size ranges,
13 time ranges, and Lua script-based custom filtering.
14- **Safety First**: Dry-run mode, confirmation prompts, force flag, max-delete threshold.
15- **Versioning Support**: Delete markers, all-versions deletion for versioned buckets.
16- **Library-First**: All CLI features available as a Rust library for programmatic use.
17- **s3sync Compatible**: Reuses s3sync's proven infrastructure (~90% code reuse).
18
19## Architecture
20
21s3rm-rs uses a streaming pipeline architecture:
22
23```text
24ObjectLister → [Filter Stages] → ObjectDeleter Workers (MPMC) → Terminator
25```
26
27All core functionality resides in this library crate. The CLI binary is a thin
28wrapper that parses arguments, builds a [`Config`], and runs a [`DeletionPipeline`].
29
30## Quick Start (Library Usage)
31
32```toml
33[dependencies]
34s3rm-rs = "1"
35tokio = { version = "1", features = ["full"] }
36```
37
38The easiest way is [`build_config_from_args`] — pass CLI-style arguments
39and get a ready-to-run [`Config`]:
40
41```no_run
42use s3rm_rs::{build_config_from_args, DeletionPipeline, create_pipeline_cancellation_token};
43
44#[tokio::main]
45async fn main() {
46 // Same arguments you would pass to the s3rm CLI.
47 let config = build_config_from_args([
48 "s3rm",
49 "s3://my-bucket/logs/2024/",
50 "--dry-run",
51 "--force",
52 ]).expect("invalid arguments");
53
54 let token = create_pipeline_cancellation_token();
55 let mut pipeline = DeletionPipeline::new(config, token).await;
56 // The pipeline sends real-time stats to a channel for progress reporting.
57 // Close the sender if you aren't reading from get_stats_receiver(),
58 // otherwise the channel fills up and the pipeline stalls.
59 pipeline.close_stats_sender();
60
61 pipeline.run().await;
62
63 // --- Error checking ---
64 if pipeline.has_error() {
65 if let Some(messages) = pipeline.get_error_messages() {
66 for msg in &messages {
67 eprintln!("Error: {msg}");
68 }
69 }
70 std::process::exit(1);
71 }
72
73 let stats = pipeline.get_deletion_stats();
74
75 println!("Deleted {} objects ({} bytes)",
76 stats.stats_deleted_objects, stats.stats_deleted_bytes);
77}
78```
79
80You can also build a [`Config`] with [`Config::for_target`], but note that
81**command-line validation checks are not performed** (e.g. conflicting flags,
82rate-limit vs batch-size). Prefer [`build_config_from_args`] when possible:
83
84```no_run
85# use s3rm_rs::Config;
86let mut config = Config::for_target("my-bucket", "logs/2024/");
87config.dry_run = true; // preview without deleting
88config.worker_size = 100; // more concurrent workers
89config.max_delete = Some(5000); // stop after 5 000 deletions
90```
91
92## Lua Scripting
93
94Lua filter and event callbacks can be registered via script paths in the [`Config`]:
95
96```no_run
97# let mut config: s3rm_rs::Config = todo!();
98config.filter_callback_lua_script = Some("path/to/filter.lua".to_string());
99config.event_callback_lua_script = Some("path/to/event.lua".to_string());
100```
101
102Lua scripts run in a sandboxed VM by default (no OS or I/O library access).
103Use `allow_lua_os_library` and `allow_lua_unsafe_vm` on [`Config`] to relax restrictions.
104
105For more information, see the [s3sync documentation](https://github.com/nidor1998/s3sync)
106as s3rm-rs shares the same Lua integration.
107*/
108
109#![allow(clippy::collapsible_if)]
110#![allow(clippy::assertions_on_constants)]
111
112// ---------------------------------------------------------------------------
113// Module declarations
114// ---------------------------------------------------------------------------
115
116pub mod callback;
117pub mod config;
118pub(crate) mod deleter;
119pub(crate) mod filters;
120pub(crate) mod lister;
121#[cfg(feature = "lua_support")]
122pub(crate) mod lua;
123pub(crate) mod pipeline;
124pub(crate) mod safety;
125pub(crate) mod stage;
126pub(crate) mod storage;
127pub(crate) mod terminator;
128pub mod types;
129
130#[cfg(test)]
131pub(crate) mod test_utils;
132
133// ---------------------------------------------------------------------------
134// Root-level re-exports for convenient access
135// ---------------------------------------------------------------------------
136
137// Core pipeline
138pub use pipeline::DeletionPipeline;
139
140// Configuration
141pub use config::Config;
142pub use config::args::{CLIArgs, build_config_from_args, parse_from_args};
143
144// Statistics
145pub use types::{DeletionStatistics, DeletionStats, DeletionStatsReport};
146
147// Object types
148pub use types::{S3Object, S3Target};
149
150// Error types
151pub use types::error::{S3rmError, exit_code_from_error, is_cancelled_error};
152
153/// Per-object deletion error returned when an individual object fails to delete.
154pub use types::DeletionError;
155
156/// Per-object deletion event emitted for each processed object (success or failure).
157pub use types::DeletionEvent;
158
159/// Outcome of a single object deletion attempt (success with metadata, or error).
160pub use types::DeletionOutcome;
161
162// Cancellation token
163pub use types::token::{PipelineCancellationToken, create_pipeline_cancellation_token};
164
165// Callback traits
166pub use types::event_callback::{EventCallback, EventData, EventType};
167pub use types::filter_callback::FilterCallback;
168
169// Callback managers
170pub use callback::event_manager::EventManager;
171pub use callback::filter_manager::FilterManager;
172
173#[cfg(test)]
174mod property_tests;
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn library_crate_loads() {
182 // Basic sanity check that the library crate compiles and loads
183 assert!(true);
184 }
185
186 #[test]
187 fn root_re_exports_accessible() {
188 // Verify that all root-level re-exports are accessible.
189 // This test ensures that the public API surface is correct.
190 let _ = std::any::type_name::<DeletionPipeline>();
191 let _ = std::any::type_name::<Config>();
192 let _ = std::any::type_name::<DeletionStats>();
193 let _ = std::any::type_name::<DeletionStatsReport>();
194 let _ = std::any::type_name::<DeletionStatistics>();
195 let _ = std::any::type_name::<S3Object>();
196 let _ = std::any::type_name::<S3Target>();
197 let _ = std::any::type_name::<S3rmError>();
198 let _ = std::any::type_name::<DeletionError>();
199 let _ = std::any::type_name::<DeletionEvent>();
200 let _ = std::any::type_name::<DeletionOutcome>();
201 let _ = std::any::type_name::<PipelineCancellationToken>();
202 let _ = std::any::type_name::<EventData>();
203 let _ = std::any::type_name::<EventType>();
204 let _ = std::any::type_name::<FilterManager>();
205 let _ = std::any::type_name::<EventManager>();
206 }
207
208 #[test]
209 fn create_cancellation_token_from_root() {
210 let token = create_pipeline_cancellation_token();
211 assert!(!token.is_cancelled());
212 }
213
214 #[test]
215 fn error_helpers_accessible() {
216 let err = anyhow::anyhow!(S3rmError::Cancelled);
217 assert!(is_cancelled_error(&err));
218 assert_eq!(exit_code_from_error(&err), 0);
219 }
220}