use super::*;
use bucketwarden_errors::{
error_response_is_retryable, s3_service_specific_error_by_family_code,
s3_service_specific_error_catalog,
};
#[path = "error_encoding_codecs.rs"]
mod codecs;
pub(crate) use codecs::*;
pub(crate) fn general_error_response(code: &str, status: u16, message: &str) -> S3HttpResponse {
if let Some(service_error) = s3_service_specific_error_by_family_code("general", code) {
error_response_code(service_error.status, service_error.code, message)
.with_header("x-bucketwarden-error-family", service_error.family)
} else {
error_response_code(status, code, message)
}
}
pub(crate) fn error_response(error: &RuntimeError) -> S3HttpResponse {
match error {
RuntimeError::Auth(_) => {
error_response_code(403, "SignatureDoesNotMatch", &error.to_string())
}
RuntimeError::AuthorizationQueryParametersError(_) => {
general_error_response("AuthorizationQueryParametersError", 400, &error.to_string())
}
RuntimeError::AuthorizationHeaderMalformed(_) => {
general_error_response("AuthorizationHeaderMalformed", 400, &error.to_string())
}
RuntimeError::InvalidAccessKeyId(_) => {
general_error_response("InvalidAccessKeyId", 403, &error.to_string())
}
RuntimeError::ExpiredToken(_) => {
general_error_response("ExpiredToken", 400, &error.to_string())
}
RuntimeError::InvalidToken(_) => {
general_error_response("InvalidToken", 400, &error.to_string())
}
RuntimeError::RequestTimeTooSkewed(_) => {
general_error_response("RequestTimeTooSkewed", 403, &error.to_string())
}
RuntimeError::AccessDenied { .. } => {
general_error_response("AccessDenied", 403, &error.to_string())
}
RuntimeError::OperatorActionDenied { .. } => {
general_error_response("AccessDenied", 403, &error.to_string())
}
RuntimeError::SigV4Authentication(_) => {
error_response_code(403, "SignatureDoesNotMatch", &error.to_string())
}
RuntimeError::QuotaExceeded { .. } => {
error_response_code(429, "SlowDown", &error.to_string())
}
RuntimeError::ServiceSpecificErrorFeature(feature_id) => {
if let Some(service_error) = s3_service_specific_error_catalog()
.iter()
.find(|service_error| service_error.feature_id == *feature_id)
{
error_response_code(
service_error.status,
service_error.code,
service_error.message,
)
.with_header("x-bucketwarden-error-family", service_error.family)
} else {
error_response_code(400, "InvalidRequest", &error.to_string())
}
}
RuntimeError::InvalidBucketName(_) => {
general_error_response("InvalidBucketName", 400, &error.to_string())
}
RuntimeError::InvalidObjectKey(_) => {
general_error_response("InvalidObjectKey", 400, &error.to_string())
}
RuntimeError::InvalidMultiObjectDelete(_) => {
general_error_response("MalformedXML", 400, &error.to_string())
}
RuntimeError::BucketAlreadyExists(_) => {
general_error_response("BucketAlreadyExists", 409, &error.to_string())
}
RuntimeError::BucketNotEmpty(_) => {
general_error_response("BucketNotEmpty", 409, &error.to_string())
}
RuntimeError::NoSuchBucket(_) => {
general_error_response("NoSuchBucket", 404, &error.to_string())
}
RuntimeError::NoSuchKey(_) => general_error_response("NoSuchKey", 404, &error.to_string()),
RuntimeError::NoSuchVersion { .. } => {
general_error_response("NoSuchVersion", 404, &error.to_string())
}
RuntimeError::ObjectLocked(_) => {
general_error_response("AccessDenied", 403, &error.to_string())
}
RuntimeError::Kms(_) => {
general_error_response("InternalError", 500, "KMS operation failed.")
}
RuntimeError::SnapshotSerialize(_)
| RuntimeError::SnapshotDeserialize(_)
| RuntimeError::SnapshotIo(_)
| RuntimeError::UnsupportedSnapshotSchema(_)
| RuntimeError::InvalidSnapshotVersionCounter { .. }
| RuntimeError::InvalidSnapshotUploadCounter { .. }
| RuntimeError::InvalidSnapshotEventCounter { .. }
| RuntimeError::InvalidStorageCommitLog(_) => {
general_error_response("InternalError", 500, "Snapshot operation failed.")
}
RuntimeError::InvalidPartNumber(_) => {
general_error_response("InvalidPart", 400, &error.to_string())
}
RuntimeError::NoSuchUpload(_) => {
general_error_response("NoSuchUpload", 404, &error.to_string())
}
RuntimeError::NoMultipartParts(_)
| RuntimeError::MissingMultipartPart { .. }
| RuntimeError::MultipartEtagMismatch { .. } => {
general_error_response("InvalidPart", 400, &error.to_string())
}
RuntimeError::InvalidRange(_) => {
general_error_response("InvalidRange", 416, &error.to_string())
}
RuntimeError::InvalidConditionalHeader { .. } => {
general_error_response("InvalidArgument", 400, &error.to_string())
}
RuntimeError::InvalidListParameter { .. } => {
general_error_response("InvalidArgument", 400, &error.to_string())
}
RuntimeError::InvalidBucketVersioningStatus(_) => {
general_error_response("MalformedXML", 400, &error.to_string())
}
RuntimeError::InvalidDigest { .. } => {
general_error_response("InvalidDigest", 400, &error.to_string())
}
RuntimeError::InvalidMetadataDirective(_) => {
general_error_response("InvalidRequest", 400, &error.to_string())
}
RuntimeError::InvalidObjectAttributesRequest(_) => {
general_error_response("InvalidRequest", 400, &error.to_string())
}
RuntimeError::BadDigest { .. } => {
general_error_response("BadDigest", 400, &error.to_string())
}
RuntimeError::CorsNotAllowed(_) => {
general_error_response("CORSForbidden", 403, &error.to_string())
}
RuntimeError::InvalidCorsRule(_) => {
general_error_response("MalformedXML", 400, &error.to_string())
}
RuntimeError::NoSuchWebsiteConfiguration(_) => {
general_error_response("NoSuchWebsiteConfiguration", 404, &error.to_string())
}
RuntimeError::InvalidWebsiteConfiguration(_) => {
general_error_response("MalformedXML", 400, &error.to_string())
}
RuntimeError::InvalidBucketLogging(_) => {
general_error_response("MalformedXML", 400, &error.to_string())
}
RuntimeError::NoSuchBucketPolicy(_) => {
general_error_response("NoSuchBucketPolicy", 404, &error.to_string())
}
RuntimeError::InvalidBucketPolicy(_) => {
general_error_response("MalformedPolicy", 400, &error.to_string())
}
RuntimeError::InvalidBucketAbac(_) => {
general_error_response("MalformedXML", 400, &error.to_string())
}
RuntimeError::InvalidRequestPaymentConfiguration(_) => {
general_error_response("MalformedXML", 400, &error.to_string())
}
RuntimeError::InvalidAccelerateConfiguration(_) => {
general_error_response("MalformedXML", 400, &error.to_string())
}
RuntimeError::InvalidCreateSession(_) => {
general_error_response("InvalidArgument", 400, &error.to_string())
}
RuntimeError::InvalidPublicAccessBlockConfiguration(_) => {
general_error_response("MalformedXML", 400, &error.to_string())
}
RuntimeError::NoSuchPublicAccessBlockConfiguration(_) => general_error_response(
"NoSuchPublicAccessBlockConfiguration",
404,
&error.to_string(),
),
RuntimeError::InvalidBucketMetadataConfiguration(_) => {
general_error_response("MalformedXML", 400, &error.to_string())
}
RuntimeError::NoSuchBucketMetadataConfiguration(_) => {
general_error_response("NoSuchBucketMetadataConfiguration", 404, &error.to_string())
}
RuntimeError::InvalidBucketMetadataTableConfiguration(_) => {
general_error_response("MalformedXML", 400, &error.to_string())
}
RuntimeError::NoSuchBucketMetadataTableConfiguration(_) => general_error_response(
"NoSuchBucketMetadataTableConfiguration",
404,
&error.to_string(),
),
RuntimeError::BucketMetadataTableApiNotAvailable(_) => {
general_error_response("MethodNotAllowed", 405, &error.to_string())
}
RuntimeError::InvalidMetricsConfiguration(_) => {
general_error_response("InvalidArgument", 400, &error.to_string())
}
RuntimeError::NoSuchMetricsConfiguration { .. } => {
general_error_response("NoSuchConfiguration", 404, &error.to_string())
}
RuntimeError::TooManyConfigurations(_) => {
general_error_response("TooManyConfigurations", 400, &error.to_string())
}
RuntimeError::InvalidAnalyticsConfiguration(_) => {
general_error_response("InvalidArgument", 400, &error.to_string())
}
RuntimeError::NoSuchAnalyticsConfiguration { .. } => {
general_error_response("NoSuchConfiguration", 404, &error.to_string())
}
RuntimeError::InvalidInventoryConfiguration(_) => {
general_error_response("InvalidArgument", 400, &error.to_string())
}
RuntimeError::NoSuchInventoryConfiguration { .. } => {
general_error_response("NoSuchConfiguration", 404, &error.to_string())
}
RuntimeError::InvalidIntelligentTieringConfiguration(_) => {
general_error_response("InvalidArgument", 400, &error.to_string())
}
RuntimeError::NoSuchIntelligentTieringConfiguration { .. } => {
general_error_response("NoSuchConfiguration", 404, &error.to_string())
}
RuntimeError::NoSuchTagSet(_) => {
general_error_response("NoSuchTagSet", 404, &error.to_string())
}
RuntimeError::InvalidTagging(_) => {
general_error_response("InvalidTag", 400, &error.to_string())
}
RuntimeError::ObjectLockConfigurationNotFound(_) => general_error_response(
"ObjectLockConfigurationNotFoundError",
404,
&error.to_string(),
),
RuntimeError::InvalidObjectLockConfiguration(_) => {
general_error_response("InvalidObjectLockConfiguration", 400, &error.to_string())
}
RuntimeError::InvalidLegalHold(_) => {
general_error_response("MalformedXML", 400, &error.to_string())
}
RuntimeError::InvalidRetention(_) => {
general_error_response("InvalidRetention", 400, &error.to_string())
}
RuntimeError::NoSuchBucketEncryption(_) => general_error_response(
"ServerSideEncryptionConfigurationNotFoundError",
404,
&error.to_string(),
),
RuntimeError::InvalidEncryption(_) => {
general_error_response("InvalidEncryption", 400, &error.to_string())
}
RuntimeError::UnsupportedEncryption(_) => {
general_error_response("InvalidEncryptionAlgorithmError", 400, &error.to_string())
}
RuntimeError::UnsupportedStorageBackend(_) => {
general_error_response("InvalidStorageBackend", 400, &error.to_string())
}
RuntimeError::UnsupportedReplicationStrategy(_) => {
general_error_response("InvalidReplicationStrategy", 400, &error.to_string())
}
RuntimeError::UnsupportedErasureCodingProfile(_) => {
general_error_response("InvalidErasureCodingProfile", 400, &error.to_string())
}
RuntimeError::InvalidErasureCodingLayout(_) => {
general_error_response("InvalidErasureCodingLayout", 400, &error.to_string())
}
RuntimeError::UnsupportedPlacementDomain(_) => {
general_error_response("InvalidPlacementDomain", 400, &error.to_string())
}
RuntimeError::InvalidPlacementPolicy(_) => {
general_error_response("InvalidPlacementPolicy", 400, &error.to_string())
}
RuntimeError::UnsupportedConsistencyModel(_) => {
general_error_response("InvalidConsistencyModel", 400, &error.to_string())
}
RuntimeError::InvalidConsistencyPolicy(_) => {
general_error_response("InvalidConsistencyPolicy", 400, &error.to_string())
}
RuntimeError::UnsupportedMetadataArchitecture(_) => {
general_error_response("InvalidMetadataArchitecture", 400, &error.to_string())
}
RuntimeError::InvalidMetadataArchitecturePolicy(_) => {
general_error_response("InvalidMetadataArchitecturePolicy", 400, &error.to_string())
}
RuntimeError::UnsupportedObjectLayout(_) => {
general_error_response("InvalidObjectLayout", 400, &error.to_string())
}
RuntimeError::InvalidObjectLayoutPolicy(_) => {
general_error_response("InvalidObjectLayoutPolicy", 400, &error.to_string())
}
RuntimeError::UnsupportedSmallObjectOptimization(_) => {
general_error_response("InvalidSmallObjectOptimization", 400, &error.to_string())
}
RuntimeError::InvalidSmallObjectOptimizationPolicy(_) => error_response_code(
400,
"InvalidSmallObjectOptimizationPolicy",
&error.to_string(),
),
RuntimeError::UnsupportedLargeObjectOptimization(_) => {
general_error_response("InvalidLargeObjectOptimization", 400, &error.to_string())
}
RuntimeError::InvalidLargeObjectOptimizationPolicy(_) => error_response_code(
400,
"InvalidLargeObjectOptimizationPolicy",
&error.to_string(),
),
RuntimeError::NoSuchLifecycleConfiguration(_) => {
general_error_response("NoSuchLifecycleConfiguration", 404, &error.to_string())
}
RuntimeError::InvalidLifecycleConfiguration(_) => {
general_error_response("MalformedXML", 400, &error.to_string())
}
RuntimeError::InvalidNotificationConfiguration(_) => {
general_error_response("MalformedXML", 400, &error.to_string())
}
RuntimeError::NoSuchBucketReplication(_) => error_response_code(
404,
"ReplicationConfigurationNotFoundError",
&error.to_string(),
),
RuntimeError::InvalidReplicationConfiguration(_) => {
general_error_response("MalformedXML", 400, &error.to_string())
}
RuntimeError::AccessControlListNotSupported(_) => {
general_error_response("AccessControlListNotSupported", 400, &error.to_string())
}
RuntimeError::InvalidOwnershipControls(_) => {
general_error_response("MalformedXML", 400, &error.to_string())
}
RuntimeError::InvalidBucketLocation(_) => {
general_error_response("InvalidLocationConstraint", 400, &error.to_string())
}
}
}
pub(crate) fn error_response_code(status: u16, code: &str, message: &str) -> S3HttpResponse {
xml_response(
status,
format!(
"<Error><Code>{}</Code><Message>{}</Message></Error>",
xml_escape(code),
xml_escape(message)
),
)
}
pub(crate) fn finalize_s3_response(
mut response: S3HttpResponse,
request_id: &str,
extended_request_id: &str,
) -> S3HttpResponse {
if response.status >= 400 {
let retryable = error_response_is_retryable(response.status).to_string();
response = enrich_error_response_body(response, request_id, extended_request_id)
.with_header("x-bucketwarden-error-retryable", retryable);
response.headers.insert(
"content-length".to_string(),
response.body.len().to_string(),
);
}
response
.with_header("x-amz-request-id", request_id)
.with_header("x-amz-id-2", extended_request_id)
}
pub(crate) fn enrich_error_response_body(
mut response: S3HttpResponse,
request_id: &str,
extended_request_id: &str,
) -> S3HttpResponse {
let Ok(body) = String::from_utf8(response.body.clone()) else {
return response;
};
if !(body.starts_with("<Error>") && body.ends_with("</Error>")) || body.contains("<RequestId>")
{
return response;
}
let suffix = format!(
"<RequestId>{}</RequestId><HostId>{}</HostId></Error>",
xml_escape(request_id),
xml_escape(extended_request_id)
);
response.body = body
.trim_end_matches("</Error>")
.to_string()
.replace('\n', "")
.into_bytes();
response.body.extend_from_slice(suffix.as_bytes());
response
}