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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
use rama_utils::collections::NonEmptyVec;
use self::sealed::CmpCoding;
rama_utils::macros::enums::enum_builder! {
/// Directive for the [`ContentEncoding`] header.
@String
pub enum ContentEncodingDirective {
/// A format using the [Lempel-Ziv coding](https://en.wikipedia.org/wiki/LZ77_and_LZ78#LZ77) (LZ77),
/// with a 32-bit CRC. This is the original format of the UNIX gzip program.
///
/// The HTTP/1.1 standard also recommends that the servers supporting this
/// content-encoding should recognize x-gzip as an alias, for compatibility purposes.
Gzip => "gzip" | "x-gzip",
/// A format using the [Lempel-Ziv-Welch](https://en.wikipedia.org/wiki/LZW) (LZW) algorithm.
/// The value name was taken from the UNIX compress program,
/// which implemented this algorithm. Like the compress program,
/// which has disappeared from most UNIX distributions,
/// this content-encoding is not used by many browsers today,
/// partly because of a patent issue (it expired in 2003).
Compress => "compress",
/// Using the zlib structure (defined in [RFC 1950](https://datatracker.ietf.org/doc/html/rfc1950))
/// with the [deflate](https://en.wikipedia.org/wiki/Deflate) compression algorithm
/// (defined in [RFC 1951](https://datatracker.ietf.org/doc/html/rfc1951)).
Deflate => "deflate",
/// A format using the [Brotli](https://developer.mozilla.org/en-US/docs/Glossary/Brotli_compression)
/// algorithm structure (defined in [RFC 7932](https://datatracker.ietf.org/doc/html/rfc7932)).
Brotli => "br",
/// A format using the [Zstandard](https://developer.mozilla.org/en-US/docs/Glossary/Zstandard_compression)
/// algorithm structure (defined in [RFC 8878](https://datatracker.ietf.org/doc/html/rfc8878)).
ZStandard => "zstd",
/// A format that uses the [Dictionary-Compressed Brotli algorithm](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-compression-dictionary#name-dictionary-compressed-brotl).
/// See [Compression Dictionary Transport](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Compression_dictionary_transport).
///
/// Experimental directive still in Draft!
DCBrotli => "dcb",
/// A format that uses the [Dictionary-Compressed Zstandard algorithm](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-compression-dictionary#name-dictionary-compressed-zstan).
/// See [Compression Dictionary Transport](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Compression_dictionary_transport).
///
/// Experimental directive still in Draft!
DCZStandard => "dcz",
}
}
derive_non_empty_flat_csv_header! {
#[header(name = CONTENT_ENCODING, sep = Comma)]
#[derive(Clone, Debug)]
/// `Content-Encoding` header, defined in
/// [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-3.1.2.2)
///
/// The `Content-Encoding` header field indicates what content codings
/// have been applied to the representation, beyond those inherent in the
/// media type, and thus what decoding mechanisms have to be applied in
/// order to obtain data in the media type referenced by the Content-Type
/// header field. Content-Encoding is primarily used to allow a
/// representation's data to be compressed without losing the identity of
/// its underlying media type.
///
/// # ABNF
///
/// ```text
/// Content-Encoding = 1#content-coding
/// ```
///
/// # Example values
///
/// * `gzip`
/// * `br`
/// * `zstd`
/// * `deflate, gzip`
///
/// # Examples
///
/// ```
/// use rama_http_headers::ContentEncoding;
///
/// let content_enc = ContentEncoding::gzip();
/// ```
pub struct ContentEncoding(pub NonEmptyVec<ContentEncodingDirective>);
}
impl ContentEncoding {
/// Create a new [`ContentEncoding`] header with multiple directives.
///
/// Meaning the content is encoded with all given directives and in order.
#[inline]
#[must_use]
pub fn new_multi(directives: NonEmptyVec<ContentEncodingDirective>) -> Self {
Self(directives)
}
/// A constructor to easily create a `Content-Encoding: gzip` header.
#[inline]
#[must_use]
pub fn gzip() -> Self {
Self(NonEmptyVec::new(ContentEncodingDirective::Gzip))
}
/// A constructor to easily create a `Content-Encoding: br` header.
#[inline]
#[must_use]
pub fn brotli() -> Self {
Self(NonEmptyVec::new(ContentEncodingDirective::Brotli))
}
/// A constructor to easily create a `Content-Encoding: zstd` header.
#[inline]
#[must_use]
pub fn zstd() -> Self {
Self(NonEmptyVec::new(ContentEncodingDirective::ZStandard))
}
/// Check if this header contains a given "coding".
///
/// This can be used with these argument types:
///
/// - `&str`
///
/// # Example
///
/// ```
/// use rama_http_headers::ContentEncoding;
///
/// let content_enc = ContentEncoding::gzip();
///
/// assert!(content_enc.contains_directive("gzip"));
/// assert!(!content_enc.contains_directive("br"));
/// ```
#[expect(clippy::needless_pass_by_value)]
pub fn contains_directive(&self, coding: impl CmpCoding) -> bool {
self.0.iter().any(|other| coding.cmp_coding(other))
}
}
mod sealed {
use super::ContentEncodingDirective;
pub trait CmpCoding: Sealed {}
pub trait Sealed {
fn cmp_coding(&self, other: &ContentEncodingDirective) -> bool;
}
impl CmpCoding for &str {}
impl Sealed for &str {
#[inline(always)]
fn cmp_coding(&self, other: &ContentEncodingDirective) -> bool {
self.trim().eq_ignore_ascii_case(other.as_str())
}
}
impl CmpCoding for ContentEncodingDirective {}
impl Sealed for ContentEncodingDirective {
fn cmp_coding(&self, other: &ContentEncodingDirective) -> bool {
self.eq(other)
}
}
}
#[cfg(test)]
mod tests {
use super::super::{test_decode, test_encode};
use super::*;
use rama_utils::collections::non_empty_vec;
#[test]
fn decode_header_single() {
let ContentEncoding(directives) = test_decode(&["deflate, gzip"]).unwrap();
assert_eq!(directives.len(), 2);
assert_eq!(directives[0], ContentEncodingDirective::Deflate);
assert_eq!(directives[1], ContentEncodingDirective::Gzip);
let header = ContentEncoding(directives);
assert!(header.contains_directive(ContentEncodingDirective::Deflate));
assert!(header.contains_directive(ContentEncodingDirective::Gzip));
assert!(!header.contains_directive(ContentEncodingDirective::Brotli));
}
#[test]
fn decode_header_multi() {
let ContentEncoding(directives) = test_decode(&["deflate, gzip", "compress"]).unwrap();
assert_eq!(directives.len(), 3);
assert_eq!(directives[0], ContentEncodingDirective::Deflate);
assert_eq!(directives[1], ContentEncodingDirective::Gzip);
assert_eq!(directives[2], ContentEncodingDirective::Compress);
let header = ContentEncoding(directives);
assert!(header.contains_directive(ContentEncodingDirective::Deflate));
assert!(header.contains_directive(ContentEncodingDirective::Gzip));
assert!(header.contains_directive(ContentEncodingDirective::Compress));
assert!(!header.contains_directive(ContentEncodingDirective::Brotli));
}
#[test]
fn encode_single() {
let allow = ContentEncoding::new(ContentEncodingDirective::Brotli);
let headers = test_encode(allow);
assert_eq!(headers["content-encoding"], "br");
}
#[test]
fn encode_multi() {
let allow = ContentEncoding::new_multi(non_empty_vec![
ContentEncodingDirective::Deflate,
ContentEncodingDirective::Gzip
]);
let headers = test_encode(allow);
assert_eq!(headers["content-encoding"], "deflate, gzip");
}
#[test]
fn decode_with_empty_header_value() {
let ContentEncoding(directives) = test_decode(&[""]).unwrap();
assert_eq!(directives.len(), 1);
assert_eq!(directives[0], ContentEncodingDirective::Unknown("".into()));
}
#[test]
fn decode_with_no_headers() {
assert!(test_decode::<ContentEncoding>(&[]).is_none());
}
#[test]
fn decode_header_unknown_directive() {
let ContentEncoding(directives) = test_decode(&["foobar"]).unwrap();
assert_eq!(directives.len(), 1);
assert_eq!(
directives[0],
ContentEncodingDirective::Unknown("foobar".into())
);
}
}