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

Commit 20ae7ba

Browse files
authored
add encodings fn, zstd support (#2)
add encodings fn, zstd support
2 parents 6303023 + 72684f1 commit 20ae7ba

File tree

4 files changed

+138
-64
lines changed

4 files changed

+138
-64
lines changed

Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ readme = "README.md"
1212
edition = "2018"
1313

1414
[dependencies]
15-
derive_is_enum_variant = "0.1.1"
1615
failure = "0.1.3"
1716
http = "0.1.13"
1817

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Determine the best encoding possible from an Accept-Encoding HTTP header.
1111
## Examples
1212
__Basic usage__
1313
```rust
14+
use accept_encoding::Encoding;
1415
use failure::Error;
1516
use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING};
1617

@@ -19,7 +20,7 @@ fn main () -> Result<(), failure::Error> {
1920
headers.insert(ACCEPT_ENCODING, HeaderValue::from_str("gzip, deflate, br")?);
2021

2122
let encoding = accept_encoding::parse(&headers)?;
22-
assert!(encoding.is_brotli());
23+
assert_eq!(encoding, Some(Encoding::Gzip));
2324
Ok(())
2425
}
2526
```

src/lib.rs

+92-52
Original file line numberDiff line numberDiff line change
@@ -7,116 +7,156 @@
77
//! ## Examples
88
//! ```rust
99
//! # use failure::Error;
10+
//! use accept_encoding::Encoding;
1011
//! use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING};
1112
//!
1213
//! # fn main () -> Result<(), failure::Error> {
1314
//! let mut headers = HeaderMap::new();
1415
//! headers.insert(ACCEPT_ENCODING, HeaderValue::from_str("gzip, deflate, br")?);
1516
//!
1617
//! let encoding = accept_encoding::parse(&headers)?;
17-
//! assert!(encoding.is_gzip());
18+
//! assert_eq!(encoding, Some(Encoding::Gzip));
1819
//! # Ok(())}
1920
//! ```
2021
//!
2122
//! ```rust
2223
//! # use failure::Error;
24+
//! use accept_encoding::Encoding;
2325
//! use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING};
2426
//!
2527
//! # fn main () -> Result<(), failure::Error> {
2628
//! let mut headers = HeaderMap::new();
2729
//! headers.insert(ACCEPT_ENCODING, HeaderValue::from_str("gzip;q=0.5, deflate;q=0.9, br;q=1.0")?);
2830
//!
2931
//! let encoding = accept_encoding::parse(&headers)?;
30-
//! assert!(encoding.is_brotli());
32+
//! assert_eq!(encoding, Some(Encoding::Brotli));
3133
//! # Ok(())}
3234
//! ```
3335
3436
mod error;
3537

3638
pub use crate::error::{Error, ErrorKind, Result};
37-
use derive_is_enum_variant::is_enum_variant;
3839
use failure::ResultExt;
3940
use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING};
4041

4142
/// Encoding levels.
42-
#[derive(Debug, Clone, Copy, Eq, PartialEq, is_enum_variant)]
43+
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
4344
pub enum Encoding {
44-
/// Gzip is the most preferred encoding present.
45+
/// The Gzip encoding.
4546
Gzip,
46-
/// Deflate is the most preferred encoding present.
47+
/// The Deflate encoding.
4748
Deflate,
48-
/// Brotli is the most preferred encoding present.
49+
/// The Brotli encoding.
4950
Brotli,
50-
/// No encoding is preferred.
51+
/// The Zstd encoding.
52+
Zstd,
53+
/// No encoding.
5154
Identity,
52-
/// 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,
5455
}
5556

5657
impl Encoding {
5758
/// Parses a given string into its corresponding encoding.
58-
fn parse(s: &str) -> Result<Encoding> {
59+
fn parse(s: &str) -> Result<Option<Encoding>> {
5960
match s {
60-
"gzip" => Ok(Encoding::Gzip),
61-
"deflate" => Ok(Encoding::Deflate),
62-
"br" => Ok(Encoding::Brotli),
63-
"identity" => Ok(Encoding::Identity),
64-
"*" => Ok(Encoding::None),
61+
"gzip" => Ok(Some(Encoding::Gzip)),
62+
"deflate" => Ok(Some(Encoding::Deflate)),
63+
"br" => Ok(Some(Encoding::Brotli)),
64+
"zstd" => Ok(Some(Encoding::Zstd)),
65+
"identity" => Ok(Some(Encoding::Identity)),
66+
"*" => Ok(None),
6567
_ => Err(ErrorKind::UnknownEncoding)?,
6668
}
6769
}
6870

6971
/// Converts the encoding into its' corresponding header value.
70-
///
71-
/// Note that [`Encoding::None`] will return a HeaderValue with the content `*`.
72-
/// 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.
7372
pub fn to_header_value(self) -> HeaderValue {
7473
match self {
7574
Encoding::Gzip => HeaderValue::from_str("gzip").unwrap(),
7675
Encoding::Deflate => HeaderValue::from_str("deflate").unwrap(),
7776
Encoding::Brotli => HeaderValue::from_str("br").unwrap(),
77+
Encoding::Zstd => HeaderValue::from_str("zstd").unwrap(),
7878
Encoding::Identity => HeaderValue::from_str("identity").unwrap(),
79-
Encoding::None => HeaderValue::from_str("*").unwrap(),
8079
}
8180
}
8281
}
8382

84-
/// Parse a set of HTTP headers into an `Encoding`.
85-
pub fn parse(headers: &HeaderMap) -> Result<Encoding> {
86-
let mut preferred_encoding = Encoding::None;
83+
/// Parse a set of HTTP headers into a single option yielding an `Encoding` that the client prefers.
84+
///
85+
/// 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.
86+
///
87+
/// Note that a result of `None` indicates there preference is expressed on which encoding to use.
88+
/// Either the `Accept-Encoding` header is not present, or `*` is set as the most preferred encoding.
89+
pub fn parse(headers: &HeaderMap) -> Result<Option<Encoding>> {
90+
let mut preferred_encoding = None;
8791
let mut max_qval = 0.0;
8892

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-
}
93+
for (encoding, qval) in encodings(headers)? {
94+
if (qval - 1.0f32).abs() < 0.01 {
95+
preferred_encoding = encoding;
96+
break;
97+
} else if qval > max_qval {
98+
preferred_encoding = encoding;
99+
max_qval = qval;
118100
}
119101
}
120102

121103
Ok(preferred_encoding)
122104
}
105+
106+
/// Parse a set of HTTP headers into a vector containing tuples of options containing encodings and their corresponding q-values.
107+
///
108+
/// 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.
109+
///
110+
/// Note that a result of `None` indicates there preference is expressed on which encoding to use.
111+
/// Either the `Accept-Encoding` header is not present, or `*` is set as the most preferred encoding.
112+
/// ## Examples
113+
/// ```rust
114+
/// # use failure::Error;
115+
/// use accept_encoding::Encoding;
116+
/// use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING};
117+
///
118+
/// # fn main () -> Result<(), failure::Error> {
119+
/// let mut headers = HeaderMap::new();
120+
/// headers.insert(ACCEPT_ENCODING, HeaderValue::from_str("zstd;q=1.0, deflate;q=0.8, br;q=0.9")?);
121+
///
122+
/// let encodings = accept_encoding::encodings(&headers)?;
123+
/// for (encoding, qval) in encodings {
124+
/// println!("{:?} {}", encoding, qval);
125+
/// }
126+
/// # Ok(())}
127+
/// ```
128+
pub fn encodings(headers: &HeaderMap) -> Result<Vec<(Option<Encoding>, f32)>> {
129+
headers
130+
.get_all(ACCEPT_ENCODING)
131+
.iter()
132+
.map(|hval| {
133+
hval.to_str()
134+
.context(ErrorKind::InvalidEncoding)
135+
.map_err(std::convert::Into::into)
136+
})
137+
.collect::<Result<Vec<&str>>>()?
138+
.iter()
139+
.flat_map(|s| s.split(',').map(str::trim))
140+
.filter_map(|v| {
141+
let mut v = v.splitn(2, ";q=");
142+
let encoding = match Encoding::parse(v.next().unwrap()) {
143+
Ok(encoding) => encoding,
144+
Err(_) => return None, // ignore unknown encodings
145+
};
146+
let qval = if let Some(qval) = v.next() {
147+
let qval = match qval.parse::<f32>() {
148+
Ok(f) => f,
149+
Err(_) => return Some(Err(ErrorKind::InvalidEncoding)),
150+
};
151+
if qval > 1.0 {
152+
return Some(Err(ErrorKind::InvalidEncoding)); // q-values over 1 are unacceptable
153+
}
154+
qval
155+
} else {
156+
1.0f32
157+
};
158+
Some(Ok((encoding, qval)))
159+
})
160+
.map(|v| v.map_err(std::convert::Into::into))
161+
.collect::<Result<Vec<(Option<Encoding>, f32)>>>()
162+
}

tests/test.rs

+44-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
extern crate accept_encoding;
22
extern crate failure;
33

4+
use accept_encoding::Encoding;
45
use failure::Error;
56
use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING};
67

@@ -9,8 +10,8 @@ fn single_encoding() -> Result<(), Error> {
910
let mut headers = HeaderMap::new();
1011
headers.insert(ACCEPT_ENCODING, HeaderValue::from_str("gzip")?);
1112

12-
let encoding = accept_encoding::parse(&headers)?;
13-
assert!(encoding.is_gzip());
13+
let encoding = accept_encoding::parse(&headers)?.unwrap();
14+
assert_eq!(encoding, Encoding::Gzip);
1415

1516
Ok(())
1617
}
@@ -20,8 +21,8 @@ fn multiple_encodings() -> Result<(), Error> {
2021
let mut headers = HeaderMap::new();
2122
headers.insert(ACCEPT_ENCODING, HeaderValue::from_str("gzip, deflate, br")?);
2223

23-
let encoding = accept_encoding::parse(&headers)?;
24-
assert!(encoding.is_gzip());
24+
let encoding = accept_encoding::parse(&headers)?.unwrap();
25+
assert_eq!(encoding, Encoding::Gzip);
2526

2627
Ok(())
2728
}
@@ -31,8 +32,8 @@ fn single_encoding_with_qval() -> Result<(), Error> {
3132
let mut headers = HeaderMap::new();
3233
headers.insert(ACCEPT_ENCODING, HeaderValue::from_str("deflate;q=1.0")?);
3334

34-
let encoding = accept_encoding::parse(&headers)?;
35-
assert!(encoding.is_deflate());
35+
let encoding = accept_encoding::parse(&headers)?.unwrap();
36+
assert_eq!(encoding, Encoding::Deflate);
3637

3738
Ok(())
3839
}
@@ -45,8 +46,8 @@ fn multiple_encodings_with_qval_1() -> Result<(), Error> {
4546
HeaderValue::from_str("deflate, gzip;q=1.0, *;q=0.5")?,
4647
);
4748

48-
let encoding = accept_encoding::parse(&headers)?;
49-
assert!(encoding.is_deflate());
49+
let encoding = accept_encoding::parse(&headers)?.unwrap();
50+
assert_eq!(encoding, Encoding::Deflate);
5051

5152
Ok(())
5253
}
@@ -59,8 +60,8 @@ fn multiple_encodings_with_qval_2() -> Result<(), Error> {
5960
HeaderValue::from_str("gzip;q=0.5, deflate;q=1.0, *;q=0.5")?,
6061
);
6162

62-
let encoding = accept_encoding::parse(&headers)?;
63-
assert!(encoding.is_deflate());
63+
let encoding = accept_encoding::parse(&headers)?.unwrap();
64+
assert_eq!(encoding, Encoding::Deflate);
6465

6566
Ok(())
6667
}
@@ -78,3 +79,36 @@ fn multiple_encodings_with_qval_3() -> Result<(), Error> {
7879

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

0 commit comments

Comments
 (0)