Skip to content
This repository was archived by the owner on Jun 21, 2020. It is now read-only.

Commit dadaa2b

Browse files
committed
add encodings fn, zstd support + tests
1 parent 6303023 commit dadaa2b

File tree

2 files changed

+111
-39
lines changed

2 files changed

+111
-39
lines changed

src/lib.rs

Lines changed: 77 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,18 @@ use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING};
4141
/// Encoding levels.
4242
#[derive(Debug, Clone, Copy, Eq, PartialEq, is_enum_variant)]
4343
pub enum Encoding {
44-
/// Gzip is the most preferred encoding present.
44+
/// Gzip is the most preferred encoding.
4545
Gzip,
46-
/// Deflate is the most preferred encoding present.
46+
/// Deflate is the most preferred encoding.
4747
Deflate,
48-
/// Brotli is the most preferred encoding present.
48+
/// Brotli is the most preferred encoding.
4949
Brotli,
50+
/// Zstd is the most preferred encoding.
51+
Zstd,
5052
/// No encoding is preferred.
5153
Identity,
5254
/// No preference is expressed on which encoding to use. Either the `Accept-Encoding` header is not present, or `*` is set as the most preferred encoding.
53-
None,
55+
Any,
5456
}
5557

5658
impl Encoding {
@@ -60,63 +62,100 @@ impl Encoding {
6062
"gzip" => Ok(Encoding::Gzip),
6163
"deflate" => Ok(Encoding::Deflate),
6264
"br" => Ok(Encoding::Brotli),
65+
"zstd" => Ok(Encoding::Zstd),
6366
"identity" => Ok(Encoding::Identity),
64-
"*" => Ok(Encoding::None),
67+
"*" => Ok(Encoding::Any),
6568
_ => Err(ErrorKind::UnknownEncoding)?,
6669
}
6770
}
6871

6972
/// Converts the encoding into its' corresponding header value.
7073
///
71-
/// Note that [`Encoding::None`] will return a HeaderValue with the content `*`.
74+
/// Note that [`Encoding::Any`] will return a HeaderValue with the content `*`.
7275
/// This is likely not what you want if you are using this to generate the `Content-Encoding` header to be included in an encoded response.
7376
pub fn to_header_value(self) -> HeaderValue {
7477
match self {
7578
Encoding::Gzip => HeaderValue::from_str("gzip").unwrap(),
7679
Encoding::Deflate => HeaderValue::from_str("deflate").unwrap(),
7780
Encoding::Brotli => HeaderValue::from_str("br").unwrap(),
81+
Encoding::Zstd => HeaderValue::from_str("zstd").unwrap(),
7882
Encoding::Identity => HeaderValue::from_str("identity").unwrap(),
79-
Encoding::None => HeaderValue::from_str("*").unwrap(),
83+
Encoding::Any => HeaderValue::from_str("*").unwrap(),
8084
}
8185
}
8286
}
8387

84-
/// Parse a set of HTTP headers into an `Encoding`.
88+
/// Parse a set of HTTP headers into a single `Encoding` that the client prefers.
89+
///
90+
/// If you're looking for an easy way to determine the best encoding for the client and support every [`Encoding`] listed, this is likely what you want.
8591
pub fn parse(headers: &HeaderMap) -> Result<Encoding> {
86-
let mut preferred_encoding = Encoding::None;
92+
let mut preferred_encoding = Encoding::Any;
8793
let mut max_qval = 0.0;
8894

89-
for header_value in headers.get_all(ACCEPT_ENCODING).iter() {
90-
let header_value = header_value.to_str().context(ErrorKind::InvalidEncoding)?;
91-
for v in header_value.split(',').map(str::trim) {
92-
let mut v = v.splitn(2, ";q=");
93-
let encoding = v.next().unwrap();
94-
95-
match Encoding::parse(encoding) {
96-
Ok(encoding) => {
97-
if let Some(qval) = v.next() {
98-
let qval = match qval.parse::<f32>() {
99-
Ok(f) => f,
100-
Err(_) => return Err(ErrorKind::InvalidEncoding)?,
101-
};
102-
if (qval - 1.0f32).abs() < 0.01 {
103-
preferred_encoding = encoding;
104-
break;
105-
} else if qval > 1.0 {
106-
return Err(ErrorKind::InvalidEncoding)?; // q-values over 1 are unacceptable
107-
} else if qval > max_qval {
108-
preferred_encoding = encoding;
109-
max_qval = qval;
110-
}
111-
} else {
112-
preferred_encoding = encoding;
113-
break;
114-
}
115-
}
116-
Err(_) => continue, // ignore unknown encodings for now
117-
}
95+
for (encoding, qval) in encodings(headers)? {
96+
if (qval - 1.0f32).abs() < 0.01 {
97+
preferred_encoding = encoding;
98+
break;
99+
} else if qval > max_qval {
100+
preferred_encoding = encoding;
101+
max_qval = qval;
118102
}
119103
}
120104

121105
Ok(preferred_encoding)
122106
}
107+
108+
/// Parse a set of HTTP headers into a vector containing tuples of encodings and their corresponding q-values.
109+
///
110+
/// If you're looking for more fine-grained control over what encoding to choose for the client, or if you don't support every [`Encoding`] listed, this is likely what you want.
111+
/// ## Example
112+
/// ```rust
113+
/// # use failure::Error;
114+
/// use accept_encoding::Encoding;
115+
/// use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING};
116+
///
117+
/// # fn main () -> Result<(), failure::Error> {
118+
/// let mut headers = HeaderMap::new();
119+
/// headers.insert(ACCEPT_ENCODING, HeaderValue::from_str("zstd;q=1.0, deflate;q=0.8, br;q=0.9")?);
120+
///
121+
/// let encodings = accept_encoding::encodings(&headers)?;
122+
/// for (encoding, qval) in encodings {
123+
/// println!("{:?} {}", encoding, qval);
124+
/// }
125+
/// # Ok(())}
126+
/// ```
127+
pub fn encodings(headers: &HeaderMap) -> Result<Vec<(Encoding, f32)>> {
128+
headers
129+
.get_all(ACCEPT_ENCODING)
130+
.iter()
131+
.map(|hval| {
132+
hval.to_str()
133+
.context(ErrorKind::InvalidEncoding)
134+
.map_err(std::convert::Into::into)
135+
})
136+
.collect::<Result<Vec<&str>>>()?
137+
.iter()
138+
.flat_map(|s| s.split(',').map(str::trim))
139+
.filter_map(|v| {
140+
let mut v = v.splitn(2, ";q=");
141+
let encoding = match Encoding::parse(v.next().unwrap()) {
142+
Ok(encoding) => encoding,
143+
Err(_) => return None,
144+
};
145+
let qval = if let Some(qval) = v.next() {
146+
let qval = match qval.parse::<f32>() {
147+
Ok(f) => f,
148+
Err(_) => return Some(Err(ErrorKind::InvalidEncoding)),
149+
};
150+
if qval > 1.0 {
151+
return Some(Err(ErrorKind::InvalidEncoding)); // q-values over 1 are unacceptable
152+
}
153+
qval
154+
} else {
155+
1.0f32
156+
};
157+
Some(Ok((encoding, qval)))
158+
})
159+
.map(|v| v.map_err(std::convert::Into::into))
160+
.collect::<Result<Vec<(Encoding, f32)>>>()
161+
}

tests/test.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,40 @@ fn multiple_encodings_with_qval_3() -> Result<(), Error> {
7474
);
7575

7676
let encoding = accept_encoding::parse(&headers)?;
77-
assert!(encoding.is_none());
77+
assert!(encoding.is_any());
7878

7979
Ok(())
8080
}
81+
82+
#[test]
83+
fn list_encodings() -> Result<(), Error> {
84+
use accept_encoding::Encoding;
85+
86+
let mut headers = HeaderMap::new();
87+
headers.insert(
88+
ACCEPT_ENCODING,
89+
HeaderValue::from_str("zstd;q=1.0, deflate;q=0.8, br;q=0.9")?,
90+
);
91+
92+
let encodings = accept_encoding::encodings(&headers)?;
93+
assert_eq!(encodings[0], (Encoding::Zstd, 1.0));
94+
assert_eq!(encodings[1], (Encoding::Deflate, 0.8));
95+
assert_eq!(encodings[2], (Encoding::Brotli, 0.9));
96+
Ok(())
97+
}
98+
99+
#[test]
100+
fn list_encodings_ignore_unknown() -> Result<(), Error> {
101+
use accept_encoding::Encoding;
102+
103+
let mut headers = HeaderMap::new();
104+
headers.insert(
105+
ACCEPT_ENCODING,
106+
HeaderValue::from_str("zstd;q=1.0, unknown;q=0.8, br;q=0.9")?,
107+
);
108+
109+
let encodings = accept_encoding::encodings(&headers)?;
110+
assert_eq!(encodings[0], (Encoding::Zstd, 1.0));
111+
assert_eq!(encodings[1], (Encoding::Brotli, 0.9));
112+
Ok(())
113+
}

0 commit comments

Comments
 (0)