s3 0.1.30

A lean, modern, unofficial S3-compatible client for Rust.
Documentation
use http::{HeaderMap, header::AsHeaderName};

use crate::{Error, Result};

pub(crate) fn header_string<N>(headers: &HeaderMap, name: N) -> Option<String>
where
    N: AsHeaderName,
{
    headers
        .get(name)
        .and_then(|v| v.to_str().ok())
        .map(|v| v.to_string())
}

pub(crate) fn header_u64<N>(headers: &HeaderMap, name: N) -> Option<u64>
where
    N: AsHeaderName,
{
    headers
        .get(name)
        .and_then(|v| v.to_str().ok())
        .and_then(|v| v.parse::<u64>().ok())
}

pub(crate) fn copy_source_header_value(
    bucket: &str,
    key: &str,
    version_id: Option<&str>,
) -> Result<String> {
    let bucket_enc = crate::util::encode::aws_percent_encode(bucket);
    let key_enc = crate::util::encode::aws_percent_encode_path(key);

    let value = match version_id {
        Some(v) => {
            validate_version_id(v)?;
            let version_enc = crate::util::encode::aws_percent_encode(v);
            format!("/{bucket_enc}/{key_enc}?versionId={version_enc}")
        }
        None => format!("/{bucket_enc}/{key_enc}"),
    };

    Ok(value)
}

pub(crate) fn validate_version_id(version_id: &str) -> Result<()> {
    if version_id.is_empty() {
        return Err(Error::invalid_config("version_id must not be empty"));
    }
    if version_id.trim() != version_id {
        return Err(Error::invalid_config(
            "version_id must not include leading or trailing whitespace",
        ));
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn extracts_string_and_u64_headers() {
        let mut headers = HeaderMap::new();
        headers.insert("etag", "\"abc\"".parse().unwrap());
        headers.insert("content-length", "42".parse().unwrap());

        assert_eq!(header_string(&headers, "etag").as_deref(), Some("\"abc\""));
        assert_eq!(header_u64(&headers, "content-length"), Some(42));
    }

    #[test]
    fn copy_source_header_value_encodes_bucket_key_and_version() {
        assert_eq!(
            copy_source_header_value("bucket name", "dir/file name.txt", Some("v 1")).unwrap(),
            "/bucket%20name/dir/file%20name.txt?versionId=v%201"
        );
    }

    #[test]
    fn copy_source_header_value_rejects_invalid_version_id() {
        assert!(copy_source_header_value("bucket", "key", Some("")).is_err());
        assert!(copy_source_header_value("bucket", "key", Some(" version")).is_err());
        assert!(copy_source_header_value("bucket", "key", Some("version ")).is_err());
    }
}