aws_smithy_compression/
lib.rs

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