Skip to content

Commit 0b3bd28

Browse files
committed
Add structured Content-Encoding headers
1 parent 267dcc1 commit 0b3bd28

File tree

4 files changed

+280
-0
lines changed

4 files changed

+280
-0
lines changed

src/content/content_encoding.rs

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
//! Specify the compression algorithm.
2+
3+
use crate::content::EncodingDirective;
4+
use crate::headers::{HeaderName, HeaderValue, Headers, ToHeaderValues, CONTENT_ENCODING};
5+
6+
use std::fmt::{self, Debug, Write};
7+
use std::iter::Iterator;
8+
use std::option;
9+
use std::slice;
10+
11+
/// Specify the compression algorithm.
12+
///
13+
/// # Specifications
14+
///
15+
/// - [RFC 7231, section 3.1.2.2: Content-Encoding](https://tools.ietf.org/html/rfc7231#section-3.1.2.2)
16+
///
17+
/// # Examples
18+
///
19+
/// ```
20+
/// # fn main() -> http_types::Result<()> {
21+
/// #
22+
/// use http_types::Response;
23+
/// use http_types::content::{ContentEncoding, EncodingDirective};
24+
/// let mut entries = ContentEncoding::new();
25+
/// entries.push(EncodingDirective::Gzip);
26+
/// entries.push(EncodingDirective::Identity);
27+
///
28+
/// let mut res = Response::new(200);
29+
/// entries.apply(&mut res);
30+
///
31+
/// let entries = ContentEncoding::from_headers(res)?.unwrap();
32+
/// let mut entries = entries.iter();
33+
/// assert_eq!(entries.next().unwrap(), &EncodingDirective::Gzip);
34+
/// assert_eq!(entries.next().unwrap(), &EncodingDirective::Identity);
35+
/// #
36+
/// # Ok(()) }
37+
/// ```
38+
pub struct ContentEncoding {
39+
entries: Vec<EncodingDirective>,
40+
}
41+
42+
impl ContentEncoding {
43+
/// Create a new instance of `CacheControl`.
44+
pub fn new() -> Self {
45+
Self { entries: vec![] }
46+
}
47+
48+
/// Create a new instance from headers.
49+
pub fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> {
50+
let mut entries = vec![];
51+
let headers = match headers.as_ref().get(CONTENT_ENCODING) {
52+
Some(headers) => headers,
53+
None => return Ok(None),
54+
};
55+
56+
for value in headers {
57+
for part in value.as_str().trim().split(',') {
58+
// Try and parse a directive from a str. If the directive is
59+
// unkown we skip it.
60+
if let Some(entry) = EncodingDirective::from_str(part) {
61+
entries.push(entry);
62+
}
63+
}
64+
}
65+
66+
Ok(Some(Self { entries }))
67+
}
68+
69+
/// Sets the `Server-Timing` header.
70+
pub fn apply(&self, mut headers: impl AsMut<Headers>) {
71+
headers.as_mut().insert(CONTENT_ENCODING, self.value());
72+
}
73+
74+
/// Get the `HeaderName`.
75+
pub fn name(&self) -> HeaderName {
76+
CONTENT_ENCODING
77+
}
78+
79+
/// Get the `HeaderValue`.
80+
pub fn value(&self) -> HeaderValue {
81+
let mut output = String::new();
82+
for (n, directive) in self.entries.iter().enumerate() {
83+
let directive: HeaderValue = directive.clone().into();
84+
match n {
85+
0 => write!(output, "{}", directive).unwrap(),
86+
_ => write!(output, ", {}", directive).unwrap(),
87+
};
88+
}
89+
90+
// SAFETY: the internal string is validated to be ASCII.
91+
unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
92+
}
93+
/// Push a directive into the list of entries.
94+
pub fn push(&mut self, directive: EncodingDirective) {
95+
self.entries.push(directive);
96+
}
97+
98+
/// An iterator visiting all server entries.
99+
pub fn iter(&self) -> Iter<'_> {
100+
Iter {
101+
inner: self.entries.iter(),
102+
}
103+
}
104+
105+
/// An iterator visiting all server entries.
106+
pub fn iter_mut(&mut self) -> IterMut<'_> {
107+
IterMut {
108+
inner: self.entries.iter_mut(),
109+
}
110+
}
111+
}
112+
113+
impl IntoIterator for ContentEncoding {
114+
type Item = EncodingDirective;
115+
type IntoIter = IntoIter;
116+
117+
#[inline]
118+
fn into_iter(self) -> Self::IntoIter {
119+
IntoIter {
120+
inner: self.entries.into_iter(),
121+
}
122+
}
123+
}
124+
125+
impl<'a> IntoIterator for &'a ContentEncoding {
126+
type Item = &'a EncodingDirective;
127+
type IntoIter = Iter<'a>;
128+
129+
#[inline]
130+
fn into_iter(self) -> Self::IntoIter {
131+
self.iter()
132+
}
133+
}
134+
135+
impl<'a> IntoIterator for &'a mut ContentEncoding {
136+
type Item = &'a mut EncodingDirective;
137+
type IntoIter = IterMut<'a>;
138+
139+
#[inline]
140+
fn into_iter(self) -> Self::IntoIter {
141+
self.iter_mut()
142+
}
143+
}
144+
145+
/// A borrowing iterator over entries in `CacheControl`.
146+
#[derive(Debug)]
147+
pub struct IntoIter {
148+
inner: std::vec::IntoIter<EncodingDirective>,
149+
}
150+
151+
impl Iterator for IntoIter {
152+
type Item = EncodingDirective;
153+
154+
fn next(&mut self) -> Option<Self::Item> {
155+
self.inner.next()
156+
}
157+
158+
#[inline]
159+
fn size_hint(&self) -> (usize, Option<usize>) {
160+
self.inner.size_hint()
161+
}
162+
}
163+
164+
/// A lending iterator over entries in `CacheControl`.
165+
#[derive(Debug)]
166+
pub struct Iter<'a> {
167+
inner: slice::Iter<'a, EncodingDirective>,
168+
}
169+
170+
impl<'a> Iterator for Iter<'a> {
171+
type Item = &'a EncodingDirective;
172+
173+
fn next(&mut self) -> Option<Self::Item> {
174+
self.inner.next()
175+
}
176+
177+
#[inline]
178+
fn size_hint(&self) -> (usize, Option<usize>) {
179+
self.inner.size_hint()
180+
}
181+
}
182+
183+
/// A mutable iterator over entries in `CacheControl`.
184+
#[derive(Debug)]
185+
pub struct IterMut<'a> {
186+
inner: slice::IterMut<'a, EncodingDirective>,
187+
}
188+
189+
impl<'a> Iterator for IterMut<'a> {
190+
type Item = &'a mut EncodingDirective;
191+
192+
fn next(&mut self) -> Option<Self::Item> {
193+
self.inner.next()
194+
}
195+
196+
#[inline]
197+
fn size_hint(&self) -> (usize, Option<usize>) {
198+
self.inner.size_hint()
199+
}
200+
}
201+
202+
impl ToHeaderValues for ContentEncoding {
203+
type Iter = option::IntoIter<HeaderValue>;
204+
fn to_header_values(&self) -> crate::Result<Self::Iter> {
205+
// A HeaderValue will always convert into itself.
206+
Ok(self.value().to_header_values().unwrap())
207+
}
208+
}
209+
210+
impl Debug for ContentEncoding {
211+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212+
let mut list = f.debug_list();
213+
for directive in &self.entries {
214+
list.entry(directive);
215+
}
216+
list.finish()
217+
}
218+
}

src/content/encoding_directive.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use crate::headers::HeaderValue;
2+
3+
/// Available compression algorithms.
4+
#[non_exhaustive]
5+
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
6+
pub enum EncodingDirective {
7+
/// The Gzip encoding.
8+
Gzip,
9+
/// The Deflate encoding.
10+
Deflate,
11+
/// The Brotli encoding.
12+
Brotli,
13+
/// The Zstd encoding.
14+
Zstd,
15+
/// No encoding.
16+
Identity,
17+
}
18+
19+
impl EncodingDirective {
20+
/// Parses a given string into its corresponding encoding.
21+
pub(crate) fn from_str(s: &str) -> Option<EncodingDirective> {
22+
let s = s.trim();
23+
24+
// We're dealing with an empty string.
25+
if s.is_empty() {
26+
return None;
27+
}
28+
29+
match s {
30+
"gzip" => Some(EncodingDirective::Gzip),
31+
"deflate" => Some(EncodingDirective::Deflate),
32+
"br" => Some(EncodingDirective::Brotli),
33+
"zstd" => Some(EncodingDirective::Zstd),
34+
"identity" => Some(EncodingDirective::Identity),
35+
_ => None,
36+
}
37+
}
38+
}
39+
40+
impl From<EncodingDirective> for HeaderValue {
41+
fn from(directive: EncodingDirective) -> Self {
42+
let h = |s: &str| unsafe { HeaderValue::from_bytes_unchecked(s.to_string().into_bytes()) };
43+
44+
match directive {
45+
EncodingDirective::Gzip => h("gzip"),
46+
EncodingDirective::Deflate => h("deflate"),
47+
EncodingDirective::Brotli => h("br"),
48+
EncodingDirective::Zstd => h("zstd"),
49+
EncodingDirective::Identity => h("identity"),
50+
}
51+
}
52+
}

src/content/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//! HTTP Content headers.
2+
3+
pub mod content_encoding;
4+
5+
mod encoding_directive;
6+
7+
#[doc(inline)]
8+
pub use content_encoding::ContentEncoding;
9+
pub use encoding_directive::EncodingDirective;

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ mod utils;
120120

121121
pub mod cache;
122122
pub mod conditional;
123+
pub mod content;
123124
pub mod headers;
124125
pub mod mime;
125126
pub mod proxies;

0 commit comments

Comments
 (0)