s3-wire 0.1.1

An async, streaming S3-compatible client for Rust
Documentation
use std::time::{Duration, SystemTime};

use http::header::RETRY_AFTER;
use http::{HeaderMap, StatusCode};

use super::super::S3Client;

impl S3Client {
    pub(super) fn uses_standard_aws_endpoint(&self) -> bool {
        let endpoint = self.inner.config.endpoint().url();
        if endpoint.scheme() != "https" {
            return false;
        }
        let authority = endpoint.authority();
        let host = authority
            .split_once(':')
            .map_or(authority, |(host, _)| host);
        host == "s3.amazonaws.com" || (host.starts_with("s3.") && host.ends_with(".amazonaws.com"))
    }
}

pub(super) fn parse_retry_after(headers: &HeaderMap) -> Option<Duration> {
    let value = headers.get(RETRY_AFTER)?.to_str().ok()?;
    if let Ok(seconds) = value.parse::<u64>() {
        return Some(Duration::from_secs(seconds));
    }
    let retry_at = httpdate::parse_http_date(value).ok()?;
    Some(
        retry_at
            .duration_since(SystemTime::now())
            .unwrap_or(Duration::ZERO),
    )
}

pub(super) fn controlled_region_redirect(status: StatusCode, headers: &HeaderMap) -> Option<&str> {
    if !matches!(
        status,
        StatusCode::MOVED_PERMANENTLY
            | StatusCode::TEMPORARY_REDIRECT
            | StatusCode::PERMANENT_REDIRECT
            | StatusCode::BAD_REQUEST
    ) {
        return None;
    }
    headers
        .get("x-amz-bucket-region")?
        .to_str()
        .ok()
        .filter(|region| {
            !region.is_empty()
                && region.len() <= 64
                && region
                    .bytes()
                    .all(|byte| byte.is_ascii_alphanumeric() || byte == b'-')
        })
}

#[cfg(test)]
mod tests {
    use http::{HeaderName, HeaderValue};

    use super::*;

    #[test]
    fn retry_after_in_the_past_means_no_delay() {
        let mut headers = HeaderMap::new();
        headers.insert(
            RETRY_AFTER,
            HeaderValue::from_static("Thu, 01 Jan 1970 00:00:00 GMT"),
        );
        assert_eq!(parse_retry_after(&headers), Some(Duration::ZERO));
    }

    #[test]
    fn region_redirects_require_a_valid_aws_region_header() {
        let mut headers = HeaderMap::new();
        headers.insert(
            HeaderName::from_static("x-amz-bucket-region"),
            HeaderValue::from_static("eu-west-1"),
        );
        assert_eq!(
            controlled_region_redirect(StatusCode::MOVED_PERMANENTLY, &headers),
            Some("eu-west-1")
        );
        headers.insert(
            HeaderName::from_static("x-amz-bucket-region"),
            HeaderValue::from_static("amazonaws.com"),
        );
        assert_eq!(
            controlled_region_redirect(StatusCode::MOVED_PERMANENTLY, &headers),
            None
        );
    }
}