Affected AWS service
s3
Summary
winterbaume-s3's HeadBucket handler returns an XML <Error> body on a 4xx response. The AWS S3 documentation for HeadBucket states that 4xx responses do not include a message body, and the presence of that body is what makes aws-sdk-rust leave the error as Unhandled instead of resolving it into the typed HeadBucketError::NotFound variant.
Reproduction
Cargo.toml:
[dependencies]
aws-config = "1"
aws-sdk-s3 = "1"
tokio = { version = "1", features = ["full"] }
winterbaume-core = "0.2"
winterbaume-s3 = "0.2"
src/main.rs:
use aws_sdk_s3::config::BehaviorVersion;
use winterbaume_core::MockAws;
use winterbaume_s3::S3Service;
#[tokio::main]
async fn main() {
let mock = MockAws::builder().with_service(S3Service::new()).build();
let config = aws_config::defaults(BehaviorVersion::latest())
.http_client(mock.http_client())
.credentials_provider(mock.credentials_provider())
.region(aws_sdk_s3::config::Region::new("us-east-1"))
.load().await;
let client = aws_sdk_s3::Client::new(&config);
let err = client.head_bucket().bucket("does-not-exist").send().await
.expect_err("head_bucket on missing bucket must fail");
if let aws_sdk_s3::error::SdkError::ServiceError(svc) = &err {
let raw = svc.raw();
println!("HTTP status: {}", raw.status().as_u16());
for (k, v) in raw.headers().iter() { println!("header: {k}: {v}"); }
println!("inner: {:?}", svc.err());
}
let typed_not_found = matches!(
err.as_service_error(),
Some(aws_sdk_s3::operation::head_bucket::HeadBucketError::NotFound(_))
);
println!("typed HeadBucketError::NotFound match: {typed_not_found}");
}
Expected behaviour
Per the AWS S3 HeadBucket API documentation:
If the bucket doesn't exist or you don't have permission to access it, the HEAD request returns a generic 400 Bad Request, 403 Forbidden, or 404 Not Found HTTP status code. A message body isn't included, so you can't determine the exception beyond these HTTP response codes.
The 4xx response should carry no body; the bucket-absence signal is the HTTP status alone. With a body-less response, aws-sdk-rust resolves the error into the typed HeadBucketError::NotFound variant.
Actual behaviour
The repro above prints:
HTTP status: 404
header: content-type: text/xml
inner: Unhandled(Unhandled { source: ErrorMetadata { code: Some("NoSuchBucket"), message: Some("The specified bucket does not exist."), extras: None }, ... })
typed HeadBucketError::NotFound match: false
The response carries a text/xml body (an <Error><Code>NoSuchBucket</Code>...</Error> document), and aws-sdk-rust leaves the error as Unhandled rather than the typed HeadBucketError::NotFound variant.
The body is what causes the Unhandled classification. I verified this locally by patching handle_head_bucket to overwrite the error response's body with Bytes::new() (leaving the 404 status and content-type: text/xml header unchanged) and re-running the same repro. The output then becomes:
HTTP status: 404
header: content-type: text/xml
inner: NotFound(NotFound { message: None, meta: ErrorMetadata { code: Some("NotFound"), message: None, extras: None } })
typed HeadBucketError::NotFound match: true
So dropping the body alone is sufficient to restore the typed variant resolution — no other response field needs to change.
winterbaume version / commit
4b4cc9a
Affected AWS service
s3
Summary
winterbaume-s3'sHeadBuckethandler returns an XML<Error>body on a 4xx response. The AWS S3 documentation forHeadBucketstates that 4xx responses do not include a message body, and the presence of that body is what makesaws-sdk-rustleave the error asUnhandledinstead of resolving it into the typedHeadBucketError::NotFoundvariant.Reproduction
Cargo.toml:src/main.rs:Expected behaviour
Per the AWS S3
HeadBucketAPI documentation:The 4xx response should carry no body; the bucket-absence signal is the HTTP status alone. With a body-less response,
aws-sdk-rustresolves the error into the typedHeadBucketError::NotFoundvariant.Actual behaviour
The repro above prints:
The response carries a
text/xmlbody (an<Error><Code>NoSuchBucket</Code>...</Error>document), andaws-sdk-rustleaves the error asUnhandledrather than the typedHeadBucketError::NotFoundvariant.The body is what causes the
Unhandledclassification. I verified this locally by patchinghandle_head_bucketto overwrite the error response's body withBytes::new()(leaving the 404 status andcontent-type: text/xmlheader unchanged) and re-running the same repro. The output then becomes:So dropping the body alone is sufficient to restore the typed variant resolution — no other response field needs to change.
winterbaume version / commit
4b4cc9a