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}