#![cfg_attr(docsrs, feature(doc_cfg))]
#![allow(clippy::derive_partial_eq_without_eq)]
#![warn(
missing_docs,
rustdoc::missing_crate_level_docs,
unreachable_pub,
rust_2018_idioms
)]
use aws_smithy_runtime_api::box_error::BoxError;
use aws_smithy_types::config_bag::{Storable, StoreReplace};
use std::io::Write;
use std::str::FromStr;
pub mod body;
mod gzip;
pub mod http;
pub const GZIP_NAME: &str = "gzip";
const MAX_MIN_COMPRESSION_SIZE_BYTES: u32 = 10_485_760;
pub trait Compress: Send + Sync {
fn compress_bytes(&mut self, bytes: &[u8], writer: &mut dyn Write) -> Result<(), BoxError>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct CompressionOptions {
level: u32,
min_compression_size_bytes: u32,
enabled: bool,
}
impl Default for CompressionOptions {
fn default() -> Self {
Self {
level: 6,
min_compression_size_bytes: 10240,
enabled: true,
}
}
}
impl CompressionOptions {
pub fn level(&self) -> u32 {
self.level
}
pub fn min_compression_size_bytes(&self) -> u32 {
self.min_compression_size_bytes
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn with_enabled(self, enabled: bool) -> Self {
Self { enabled, ..self }
}
pub fn with_level(self, level: u32) -> Result<Self, BoxError> {
Self::validate_level(level)?;
Ok(Self { level, ..self })
}
pub fn with_min_compression_size_bytes(
self,
min_compression_size_bytes: u32,
) -> Result<Self, BoxError> {
Self::validate_min_compression_size_bytes(min_compression_size_bytes)?;
Ok(Self {
min_compression_size_bytes,
..self
})
}
fn validate_level(level: u32) -> Result<(), BoxError> {
if level > 9 {
return Err(
format!("compression level `{level}` is invalid, valid values are 0..=9").into(),
);
};
Ok(())
}
fn validate_min_compression_size_bytes(
min_compression_size_bytes: u32,
) -> Result<(), BoxError> {
if min_compression_size_bytes > MAX_MIN_COMPRESSION_SIZE_BYTES {
return Err(format!(
"min compression size `{min_compression_size_bytes}` is invalid, valid values are 0..=10_485_760"
)
.into());
};
Ok(())
}
}
impl Storable for CompressionOptions {
type Storer = StoreReplace<Self>;
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum CompressionAlgorithm {
Gzip,
}
impl FromStr for CompressionAlgorithm {
type Err = BoxError;
fn from_str(compression_algorithm: &str) -> Result<Self, Self::Err> {
if compression_algorithm.eq_ignore_ascii_case(GZIP_NAME) {
Ok(Self::Gzip)
} else {
Err(format!("unknown compression algorithm `{compression_algorithm}`").into())
}
}
}
impl CompressionAlgorithm {
pub fn into_impl_http_body_1_x(
self,
options: &CompressionOptions,
) -> Box<dyn http::CompressRequest> {
match self {
Self::Gzip => Box::new(gzip::Gzip::from(options)),
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::Gzip { .. } => GZIP_NAME,
}
}
}
#[cfg(test)]
mod tests {
use crate::CompressionAlgorithm;
use pretty_assertions::assert_eq;
#[test]
fn test_compression_algorithm_from_str_unknown() {
let error = "some unknown compression algorithm"
.parse::<CompressionAlgorithm>()
.expect_err("it should error");
assert_eq!(
"unknown compression algorithm `some unknown compression algorithm`",
error.to_string()
);
}
#[test]
fn test_compression_algorithm_from_str_gzip() {
let algo = "gzip".parse::<CompressionAlgorithm>().unwrap();
assert_eq!("gzip", algo.as_str());
}
}