1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
/* Automatically managed default lints */
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
/* End of automatically managed default lints */
#![allow(clippy::derive_partial_eq_without_eq)]
#![warn(
missing_docs,
rustdoc::missing_crate_level_docs,
unreachable_pub,
rust_2018_idioms
)]
//! Compression-related code.
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;
// Valid compression algorithm names
/// The name of the `gzip` algorithm.
pub const GZIP_NAME: &str = "gzip";
/// The maximum-allowable value per internal standards is 10 Megabytes.
const MAX_MIN_COMPRESSION_SIZE_BYTES: u32 = 10_485_760;
/// Types implementing this trait can compress data.
///
/// Compression algorithms are used reduce the size of data. This trait
/// requires Send + Sync because trait implementors are often used in an
/// async context.
pub trait Compress: Send + Sync {
/// Given a slice of bytes, and a [Write] implementor, compress and write
/// bytes to the writer until done.
// I wanted to use `impl Write` but that's not object-safe
fn compress_bytes(&mut self, bytes: &[u8], writer: &mut dyn Write) -> Result<(), BoxError>;
}
/// Options for configuring request compression.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct CompressionOptions {
/// Valid values are 0-9 with lower values configuring less (but faster) compression
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 {
/// The compression level to use.
pub fn level(&self) -> u32 {
self.level
}
/// The minimum size of data to compress.
///
/// Data smaller than this will not be compressed.
pub fn min_compression_size_bytes(&self) -> u32 {
self.min_compression_size_bytes
}
/// Whether compression is enabled.
pub fn is_enabled(&self) -> bool {
self.enabled
}
/// Set whether compression is enabled.
pub fn with_enabled(self, enabled: bool) -> Self {
Self { enabled, ..self }
}
/// Set the compression level.
///
/// Valid values are `0..=9` with lower values configuring less _(but faster)_ compression
pub fn with_level(self, level: u32) -> Result<Self, BoxError> {
Self::validate_level(level)?;
Ok(Self { level, ..self })
}
/// Set the minimum size of data to compress.
///
/// Data smaller than this will not be compressed.
/// Valid values are `0..=10_485_760`. The default is `10_240`.
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 `{}` is invalid, valid values are 0..=9",
level
)
.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 `{}` is invalid, valid values are 0..=10_485_760",
min_compression_size_bytes
)
.into());
};
Ok(())
}
}
impl Storable for CompressionOptions {
type Storer = StoreReplace<Self>;
}
/// An enum encompassing all supported compression algorithms.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum CompressionAlgorithm {
/// The [gzip](https://en.wikipedia.org/wiki/Gzip) compression algorithm
Gzip,
}
impl FromStr for CompressionAlgorithm {
type Err = BoxError;
/// Create a new `CompressionAlgorithm` from an algorithm name.
///
/// Valid algorithm names are:
/// - "gzip"
///
/// Passing an invalid name will return an error.
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 {
#[cfg(feature = "http-body-0-4-x")]
/// Return the `HttpChecksum` implementor for this algorithm.
pub fn into_impl_http_body_0_4_x(
self,
options: &CompressionOptions,
) -> Box<dyn http::http_body_0_4_x::CompressRequest> {
match self {
Self::Gzip => Box::new(gzip::Gzip::from(options)),
}
}
#[cfg(feature = "http-body-1-x")]
/// Return the `HttpChecksum` implementor for this algorithm.
pub fn into_impl_http_body_1_x(
self,
options: &CompressionOptions,
) -> Box<dyn http::http_body_1_x::CompressRequest> {
match self {
Self::Gzip => Box::new(gzip::Gzip::from(options)),
}
}
/// Return the name of this algorithm in string form
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());
}
}