s3-wire 0.1.1

An async, streaming S3-compatible client for Rust
Documentation
use http::{HeaderMap, StatusCode};

use super::headers::request_ids;
use crate::error::{ErrorCategory, RetryClassification, S3Error};
use crate::protocol::ParsedS3Error;

pub(super) fn embedded_complete_error(headers: &HeaderMap, parsed: ParsedS3Error) -> S3Error {
    let category = match parsed.code.as_deref() {
        Some("AccessDenied" | "AllAccessDisabled") => ErrorCategory::Authorization,
        Some("NoSuchBucket" | "NoSuchUpload" | "NotFound") => ErrorCategory::NotFound,
        Some("SlowDown" | "Throttling" | "ThrottlingException") => ErrorCategory::Throttling,
        Some("InternalError" | "ServiceUnavailable") => ErrorCategory::Server,
        _ => ErrorCategory::InvalidResponse,
    };
    let retry = match category {
        ErrorCategory::Throttling => RetryClassification::Throttled,
        ErrorCategory::Server => RetryClassification::Retryable,
        _ => RetryClassification::Never,
    };
    let message = parsed
        .message
        .as_deref()
        .and_then(safe_service_message)
        .unwrap_or_else(|| "S3 returned an embedded completion error".to_owned());
    let ids = request_ids(headers);
    S3Error::new(category, message, retry).with_service_details(
        parsed.code,
        StatusCode::OK,
        ids.request_id.or(parsed.request_id),
        ids.host_id.or(parsed.host_id),
    )
}

fn safe_service_message(message: &str) -> Option<String> {
    if message.is_empty()
        || message.len() > 512
        || message.contains("X-Amz-")
        || message.to_ascii_lowercase().contains("authorization")
        || message.to_ascii_lowercase().contains("token")
        || !message.chars().all(|character| {
            character.is_ascii_alphanumeric()
                || character.is_ascii_whitespace()
                || matches!(
                    character,
                    '.' | ',' | ':' | ';' | '_' | '-' | '\'' | '(' | ')'
                )
        })
    {
        return None;
    }
    Some(
        message
            .split_ascii_whitespace()
            .collect::<Vec<_>>()
            .join(" "),
    )
}