use clap::{ArgAction, Parser};
use futures_util::StreamExt;
use minio::s3::builders::ObjectContent;
use minio::s3::client::hooks::{Extensions, RequestHooks};
use minio::s3::client::{Method, Response};
use minio::s3::creds::StaticProvider;
use minio::s3::error::Error;
use minio::s3::http::Url;
use minio::s3::multimap_ext::Multimap;
use minio::s3::response::BucketExistsResponse;
use minio::s3::segmented_bytes::SegmentedBytes;
use minio::s3::types::{BucketName, ObjectKey, S3Api, ToStream};
use minio::s3::{MinioClient, MinioClientBuilder};
use std::sync::Arc;
#[derive(Debug)]
struct DebugLoggingHook {
verbose: bool,
}
impl DebugLoggingHook {
fn new(verbose: bool) -> Self {
Self { verbose }
}
}
#[async_trait::async_trait]
impl RequestHooks for DebugLoggingHook {
fn name(&self) -> &'static str {
"debug-logger"
}
async fn before_signing_mut(
&self,
method: &Method,
url: &mut Url,
_region: &str,
_headers: &mut Multimap,
_query_params: &Multimap,
bucket: Option<&BucketName>,
object: Option<&ObjectKey>,
_body: Option<&SegmentedBytes>,
_extensions: &mut Extensions,
) -> Result<(), Error> {
if self.verbose {
let bucket_obj = match (bucket, object) {
(Some(b), Some(o)) => format!("{}/{}", b.as_str(), o.as_str()),
(Some(b), None) => b.as_str().to_string(),
_ => url.to_string(),
};
println!("→ Preparing {method} request for {bucket_obj}");
}
Ok(())
}
async fn after_execute(
&self,
method: &Method,
url: &Url,
_region: &str,
headers: &Multimap,
_query_params: &Multimap,
bucket: Option<&BucketName>,
object: Option<&ObjectKey>,
resp: &Result<Response, reqwest::Error>,
_extensions: &mut Extensions,
) {
let bucket_obj = match (bucket, object) {
(Some(b), Some(o)) => format!("{}/{}", b.as_str(), o.as_str()),
(Some(b), None) => b.as_str().to_string(),
_ => url.to_string(),
};
let status = match resp {
Ok(response) => format!("✓ {}", response.status()),
Err(err) => format!("✗ Error: {err}"),
};
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!("S3 Request: {method} {bucket_obj}");
println!("URL: {url}");
println!("Status: {status}");
if self.verbose {
let mut header_strings: Vec<String> = headers
.iter_all()
.map(|(k, v)| format!("{}: {}", k, v.join(",")))
.collect();
header_strings.sort();
println!("\nRequest Headers:");
for header in header_strings {
println!(" {header}");
}
}
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
}
}
#[derive(Parser)]
struct Cli {
#[arg(default_value = "test-bucket")]
bucket: String,
#[arg(default_value = "test-object.txt")]
object: String,
#[arg(long = "no-verbose", action = ArgAction::SetFalse, default_value_t = true)]
verbose: bool,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
env_logger::init();
let args = Cli::parse();
println!("\n🔧 MinIO Debug Logging Hook Example\n");
println!("This example demonstrates how hooks can be used for debugging S3 requests.");
println!(
"We'll perform a few operations on bucket '{}' with debug logging enabled.\n",
args.bucket
);
let debug_hook = Arc::new(DebugLoggingHook::new(args.verbose));
let static_provider = StaticProvider::new("minioadmin", "minioadmin", None);
let client: MinioClient = MinioClientBuilder::new("http://localhost:9000".parse()?)
.provider(Some(static_provider))
.hook(debug_hook) .build()?;
println!("✓ Created MinIO client with debug logging hook\n");
println!("📋 Checking if bucket exists...");
let resp: BucketExistsResponse = client
.bucket_exists(BucketName::new(&args.bucket)?)?
.build()
.send()
.await?;
if !resp.exists() {
println!("\n📋 Creating bucket...");
client
.create_bucket(BucketName::new(&args.bucket)?)?
.build()
.send()
.await?;
} else {
println!("\n✓ Bucket already exists");
}
println!("\n📋 Uploading object...");
let content = b"Hello from MinIO Rust SDK with debug logging!";
let object_content: ObjectContent = content.to_vec().into();
client
.put_object_content(
BucketName::new(&args.bucket)?,
ObjectKey::new(&args.object)?,
object_content,
)?
.build()
.send()
.await?;
println!("\n📋 Listing objects in bucket...");
let mut list_stream = client
.list_objects(BucketName::new(&args.bucket)?)?
.recursive(false)
.build()
.to_stream()
.await;
let mut total_objects = 0;
while let Some(result) = list_stream.next().await {
match result {
Ok(resp) => {
total_objects += resp.contents.len();
}
Err(e) => {
eprintln!("Error listing objects: {e}");
}
}
}
println!("\n✓ Found {total_objects} objects in bucket");
println!("\n🎉 All operations completed successfully with debug logging enabled!\n");
println!("💡 Tip: Run with --no-verbose to disable detailed output\n");
Ok(())
}