Skip to main content

rama_http_headers/common/
transfer_encoding.rs

1rama_utils::macros::enums::enum_builder! {
2    /// Directive for the [`TransferEncoding`] header.
3    @String
4    pub enum TransferEncodingDirective {
5        /// A format using the [Lempel-Ziv-Welch](https://en.wikipedia.org/wiki/LZW) (LZW) algorithm.
6        ///
7        /// The value name was taken from the UNIX compress program,
8        /// which implemented this algorithm. Like the compress program,
9        /// which has disappeared from most UNIX distributions,
10        /// this content-encoding is used by almost no browsers today,
11        /// partly because of a patent issue (which expired in 2003).
12        Compress => "compress",
13        /// Using the zlib structure (defined in [RFC 1950](https://datatracker.ietf.org/doc/html/rfc1950))
14        /// with the [deflate](https://en.wikipedia.org/wiki/Deflate) compression algorithm
15        /// (defined in [RFC 1951](https://datatracker.ietf.org/doc/html/rfc1951)).
16        Deflate => "deflate",
17        /// A format using the [Lempel-Ziv coding](https://en.wikipedia.org/wiki/LZ77_and_LZ78#LZ77) (LZ77),
18        /// with a 32-bit CRC. This is the original format of the UNIX gzip program.
19        ///
20        /// The HTTP/1.1 standard also recommends that the servers supporting this
21        /// content-encoding should recognize x-gzip as an alias, for compatibility purposes.
22        Gzip => "gzip" | "x-gzip",
23        /// Data is sent in a series of chunks.
24        ///
25        /// Content can be sent in streams of unknown size to be transferred as a sequence of
26        /// length-delimited buffers, so the sender can keep a connection open,
27        /// and let the recipient know when it has received the entire message.
28        /// The Content-Length header must be omitted, and at the beginning of each chunk,
29        /// a string of hex digits indicate the size of the chunk-data in octets,
30        /// followed by `\r\n` and then the chunk itself, followed by another `\r\n`.
31        /// The terminating chunk is a zero-length chunk.
32        Chunked => "chunked",
33    }
34}
35
36derive_non_empty_flat_csv_header! {
37    #[header(name = TRANSFER_ENCODING, sep = Comma)]
38    #[derive(Clone, Debug)]
39    /// `Transfer-Encoding` header, defined in
40    /// [RFC7230](https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.1)
41    ///
42    /// The `Transfer-Encoding` header field lists the transfer coding names
43    /// corresponding to the sequence of transfer codings that have been (or
44    /// will be) applied to the payload body in order to form the message
45    /// body.
46    ///
47    /// Note that setting this header will *remove* any previously set
48    /// `Content-Length` header, in accordance with
49    /// [RFC7230](https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2):
50    ///
51    /// > A sender MUST NOT send a Content-Length header field in any message
52    /// > that contains a Transfer-Encoding header field.
53    ///
54    /// # ABNF
55    ///
56    /// ```text
57    /// Transfer-Encoding = 1#transfer-coding
58    /// ```
59    ///
60    /// # Example values
61    ///
62    /// * `chunked`
63    /// * `gzip, chunked`
64    ///
65    /// # Example
66    ///
67    /// ```
68    /// use rama_http_headers::TransferEncoding;
69    ///
70    /// let transfer = TransferEncoding::chunked();
71    /// ```
72    pub struct TransferEncoding(pub NonEmptySmallVec<2, TransferEncodingDirective>);
73}
74
75impl TransferEncoding {
76    /// Constructor for the most common Transfer-Encoding, `chunked`.
77    #[must_use]
78    #[inline(always)]
79    pub fn chunked() -> Self {
80        Self::new(TransferEncodingDirective::Chunked)
81    }
82
83    /// Returns whether this ends with the `chunked` encoding.
84    #[must_use]
85    pub fn is_chunked(&self) -> bool {
86        self.0.last().eq(&TransferEncodingDirective::Chunked)
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use rama_utils::collections::non_empty_smallvec;
93
94    use super::super::{test_decode, test_encode};
95    use super::{TransferEncoding, TransferEncodingDirective};
96
97    #[test]
98    fn chunked_is_chunked() {
99        assert!(TransferEncoding::chunked().is_chunked());
100    }
101
102    #[test]
103    fn decode_gzip_chunked_is_chunked() {
104        let te = test_decode::<TransferEncoding>(&["gzip, chunked"]).unwrap();
105        assert!(te.is_chunked());
106    }
107
108    #[test]
109    fn decode_chunked_gzip_is_not_chunked() {
110        let te = test_decode::<TransferEncoding>(&["chunked, gzip"]).unwrap();
111        assert!(!te.is_chunked());
112    }
113
114    #[test]
115    fn decode_notchunked_is_not_chunked() {
116        let te = test_decode::<TransferEncoding>(&["notchunked"]).unwrap();
117        assert!(!te.is_chunked());
118    }
119
120    #[test]
121    fn decode_multiple_is_chunked() {
122        let te = test_decode::<TransferEncoding>(&["gzip", "chunked"]).unwrap();
123        assert!(te.is_chunked());
124    }
125
126    #[test]
127    fn encode_single() {
128        let allow = TransferEncoding::new(TransferEncodingDirective::Gzip);
129
130        let headers = test_encode(allow);
131        assert_eq!(headers["transfer-encoding"], "gzip");
132    }
133
134    #[test]
135    fn encode_multi() {
136        let allow = TransferEncoding(non_empty_smallvec![
137            TransferEncodingDirective::Deflate,
138            TransferEncodingDirective::Chunked,
139        ]);
140
141        let headers = test_encode(allow);
142        assert_eq!(headers["transfer-encoding"], "deflate, chunked");
143    }
144}