Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 35 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ All source files that haven't been generated MUST include the following copyrigh

### Comments
- **NO redundant comments** - Code should be self-documenting
- Avoid obvious comments like `// Set x to 5` for `x := 5`
- Avoid obvious comments like `// Set x to 5` for `let x = 5;`
- Only add comments when they explain WHY, not WHAT
- Document complex algorithms or non-obvious business logic

## Critical Code Patterns

### Builder Pattern
All S3 API requests MUST use the builder pattern, with the following documentation but then for the appropriate API
All S3 API requests MUST use the builder pattern, with documentation similar to the following example (adapted for each specific API)

```rust
/// Argument builder for the [`AppendObject`](https://docs.aws.amazon.com/AmazonS3/latest/userguide/directory-buckets-objects-append.html) S3 API operation.
Expand Down Expand Up @@ -73,6 +73,33 @@ impl Client {
}
```

### Rust-Specific Best Practices

1. **Ownership and Borrowing**
- Prefer `&str` over `&String` in function parameters
- Use `AsRef<str>` or `Into<String>` for flexible string parameters
- Return owned types from functions unless lifetime annotations are clear

2. **Type Safety**
- Use `#[must_use]` attribute for functions returning important values
- Prefer strong typing over primitive obsession
- Use newtypes for domain-specific values

3. **Unsafe Code**
- Avoid `unsafe` code unless absolutely necessary
- Document all safety invariants when `unsafe` is required
- Isolate `unsafe` blocks and keep them minimal

4. **Performance**
- Use `Cow<'_, str>` to avoid unnecessary allocations
- Prefer iterators over collecting into intermediate vectors
- Use `Box<dyn Trait>` sparingly; prefer generics when possible

5. **Async Patterns**
- Use `tokio::select!` for concurrent operations
- Avoid blocking operations in async contexts
- Use `async-trait` for async trait methods

## Code Quality Principles

### Why Code Quality Standards Are Mandatory
Expand Down Expand Up @@ -126,8 +153,9 @@ Complex distributed systems code must remain **human-readable**:
### Variables
- Use meaningful variable names that reflect business concepts
- Variable names should reflect usage frequency: frequent variables can be shorter
- Constants should follow Rust patterns
- Global variables should be clearly identified and documented for their system-wide purpose
- Constants should use SCREAMING_SNAKE_CASE (e.g., `MAX_RETRIES`, `DEFAULT_TIMEOUT`)
- Static variables should be clearly identified with proper safety documentation
- Prefer `const` over `static` when possible for compile-time constants

### Developer Documentation

Expand Down Expand Up @@ -179,7 +207,7 @@ Claude will periodically analyze the codebase and suggest:
Before any code changes:
1. ✅ Run `cargo fmt --all` to check and fix code formatting
2. ✅ Run `cargo test` to ensure all tests pass
3. ✅ Run `cargo clippy` to check for common mistakes
3. ✅ Run `cargo clippy --all-targets --all-features --workspace -- -D warnings` to check for common mistakes and ensure no warnings
4. ✅ Ensure new code has appropriate test coverage
5. ✅ Verify no redundant comments are added

Expand Down Expand Up @@ -222,6 +250,6 @@ fn operation() -> Result<Response, Error> {
- **Fix formatting**: `cargo fmt --all`
- **Run tests**: `cargo test`
- **Run specific test**: `cargo test test_name`
- **Check code**: `cargo clippy`
- **Check code**: `cargo clippy --all-targets --all-features --workspace -- -D warnings`
- **Build project**: `cargo build --release`
- **Generate docs**: `cargo doc --open`
- **Generate docs**: `cargo doc --open`
7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sha2 = { version = "0.10", optional = true }
urlencoding = "2.1"
xmltree = "0.11"
xmltree = "0.12"
http = "1.3"
thiserror = "2.0"
typed-builder = "0.22"
typed-builder = "0.23"

[dev-dependencies]
minio-common = { path = "./common" }
Expand Down Expand Up @@ -87,6 +87,9 @@ name = "object_prompt"
[[example]]
name = "append_object"

[[example]]
name = "load_balancing_with_hooks"

[[bench]]
name = "s3-api"
path = "benches/s3/api_benchmarks.rs"
Expand Down
2 changes: 1 addition & 1 deletion common/src/test_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ impl TestContext {
/// - `CleanupGuard` - A guard that automatically deletes the bucket when dropped.
///
/// # Example
/// ```ignore
/// ```no_run
/// let (bucket_name, guard) = client.create_bucket_helper().await;
/// println!("Created temporary bucket: {}", bucket_name);
/// // The bucket will be removed when `guard` is dropped.
Expand Down
235 changes: 235 additions & 0 deletions examples/debug_logging_hook.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
// MinIO Rust Library for Amazon S3 Compatible Cloud Storage
// Copyright 2024 MinIO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Example demonstrating how to use RequestHooks for debug logging.
//!
//! This example shows:
//! - Creating a custom debug logging hook
//! - Attaching the hook to the MinIO client
//! - Automatic logging of all S3 API requests with headers and response status
//! - Using both `before_signing_mut` and `after_execute` hooks
//!
//! Run with default values (test-bucket / test-object.txt, verbose mode enabled):
//! ```
//! cargo run --example debug_logging_hook
//! ```
//!
//! Run with custom bucket and object:
//! ```
//! cargo run --example debug_logging_hook -- mybucket myobject
//! ```
//!
//! Disable verbose output:
//! ```
//! cargo run --example debug_logging_hook -- --no-verbose
//! ```

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::{S3Api, ToStream};
use minio::s3::{MinioClient, MinioClientBuilder};
use std::sync::Arc;

/// Debug logging hook that prints detailed information about each S3 request.
#[derive(Debug)]
struct DebugLoggingHook {
/// Enable verbose output including all headers
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_name: Option<&str>,
object_name: Option<&str>,
_body: Option<&SegmentedBytes>,
_extensions: &mut Extensions,
) -> Result<(), Error> {
if self.verbose {
let bucket_obj = match (bucket_name, object_name) {
(Some(b), Some(o)) => format!("{b}/{o}"),
(Some(b), None) => b.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_name: Option<&str>,
object_name: Option<&str>,
resp: &Result<Response, reqwest::Error>,
_extensions: &mut Extensions,
) {
// Format the basic request info
let bucket_obj = match (bucket_name, object_name) {
(Some(b), Some(o)) => format!("{b}/{o}"),
(Some(b), None) => b.to_string(),
_ => url.to_string(),
};

// Format response status
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 {
// Print headers alphabetically
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");
}
}

/// Example demonstrating debug logging with hooks
#[derive(Parser)]
struct Cli {
/// Bucket to use for the example
#[arg(default_value = "test-bucket")]
bucket: String,
/// Object to upload
#[arg(default_value = "test-object.txt")]
object: String,
/// Disable verbose output (verbose is enabled by default, use --no-verbose to disable)
#[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
);

// Create the debug logging hook
let debug_hook = Arc::new(DebugLoggingHook::new(args.verbose));

// Create MinIO client with the debug logging hook attached
let static_provider = StaticProvider::new(
"Q3AM3UQ867SPQQA43P2F",
"zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG",
None,
);

let client: MinioClient = MinioClientBuilder::new("https://play.min.io".parse()?)
.provider(Some(static_provider))
.hook(debug_hook) // Attach the debug logging hook
.build()?;

println!("✓ Created MinIO client with debug logging hook\n");

// Operation 1: Check if bucket exists
println!("📋 Checking if bucket exists...");
let resp: BucketExistsResponse = client.bucket_exists(&args.bucket).build().send().await?;

// Operation 2: Create bucket if it doesn't exist
if !resp.exists() {
println!("\n📋 Creating bucket...");
client.create_bucket(&args.bucket).build().send().await?;
} else {
println!("\n✓ Bucket already exists");
}

// Operation 3: Upload a small object
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(&args.bucket, &args.object, object_content)
.build()
.send()
.await?;

// Operation 4: List objects in the bucket
println!("\n📋 Listing objects in bucket...");
let mut list_stream = client
.list_objects(&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(())
}
Loading