bucketwarden-server 0.1.0

BucketWarden storage server runtime.
Documentation
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
}