improve perf on display impl for quality

This commit is contained in:
Rob Ede 2021-12-05 02:17:12 +00:00
parent c61d945d58
commit 4844f7b918
No known key found for this signature in database
GPG Key ID: 97C636207D3EF933
11 changed files with 350 additions and 188 deletions

View File

@ -5,8 +5,10 @@ lint-all = "clippy --workspace --all-features --tests --examples --bins -- -Dcli
# lib checking # lib checking
ci-check-min = "hack --workspace check --no-default-features" ci-check-min = "hack --workspace check --no-default-features"
ci-check-default = "hack --workspace check" ci-check-default = "hack --workspace check"
ci-check-default-tests = "check --workspace --tests"
ci-check-all-feature-powerset="hack --workspace --feature-powerset --skip=__compress,io-uring check" ci-check-all-feature-powerset="hack --workspace --feature-powerset --skip=__compress,io-uring check"
ci-check-all-feature-powerset-linux="hack --workspace --feature-powerset --skip=__compress check" ci-check-all-feature-powerset-linux="hack --workspace --feature-powerset --skip=__compress check"
# testing # testing
ci-doctest-default = "test --workspace --doc --no-fail-fast -- --nocapture"
ci-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocapture" ci-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocapture"

View File

@ -11,8 +11,9 @@
* `impl Clone for ws::HandshakeError`. [#2468] * `impl Clone for ws::HandshakeError`. [#2468]
* `#[must_use]` for `ws::Codec` to prevent subtle bugs. [#1920] * `#[must_use]` for `ws::Codec` to prevent subtle bugs. [#1920]
* `impl Default ` for `ws::Codec`. [#1920] * `impl Default ` for `ws::Codec`. [#1920]
* `header::QualityItem::{max, min}`. [#????] * `header::QualityItem::{max, min}`. [#2486]
* `header::Quality::{MAX, MIN}`. [#????] * `header::Quality::{MAX, MIN}`. [#2486]
* `impl Display` for `header::Quality`. [#2486]
### Changed ### Changed
* Rename `body::BoxBody::{from_body => new}`. [#2468] * Rename `body::BoxBody::{from_body => new}`. [#2468]
@ -29,12 +30,13 @@
* Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468] * Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468]
* Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468] * Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468]
* `impl Copy` for `ws::Codec`. [#1920] * `impl Copy` for `ws::Codec`. [#1920]
* `header::qitem` helper. Replaced with `header::QualityItem::max` [#????] * `header::qitem` helper. Replaced with `header::QualityItem::max` [#2486]
* `impl TryFrom<u16>` for `header::Quality` [#2486]
[#2483]: https://github.com/actix/actix-web/pull/2483 [#2483]: https://github.com/actix/actix-web/pull/2483
[#2468]: https://github.com/actix/actix-web/pull/2468 [#2468]: https://github.com/actix/actix-web/pull/2468
[#1920]: https://github.com/actix/actix-web/pull/1920 [#1920]: https://github.com/actix/actix-web/pull/1920
[#????]: https://github.com/actix/actix-web/pull/???? [#2486]: https://github.com/actix/actix-web/pull/2486
## 3.0.0-beta.14 - 2021-11-30 ## 3.0.0-beta.14 - 2021-11-30

View File

@ -112,3 +112,7 @@ harness = false
[[bench]] [[bench]]
name = "uninit-headers" name = "uninit-headers"
harness = false harness = false
[[bench]]
name = "quality-value"
harness = false

View File

@ -0,0 +1,90 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
const CODES: &[u16] = &[0, 1000, 201, 800, 550];
fn bench_quality_display_impls(c: &mut Criterion) {
let mut group = c.benchmark_group("quality value display impls");
for i in CODES.iter() {
group.bench_with_input(BenchmarkId::new("New (fast?)", i), i, |b, &i| {
b.iter(|| _new::Quality(i).to_string())
});
group.bench_with_input(BenchmarkId::new("Naive", i), i, |b, &i| {
b.iter(|| _naive::Quality(i).to_string())
});
}
group.finish();
}
criterion_group!(benches, bench_quality_display_impls);
criterion_main!(benches);
mod _new {
use std::fmt;
pub struct Quality(pub(crate) u16);
impl fmt::Display for Quality {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
0 => f.write_str("0"),
1000 => f.write_str("1"),
// some number in the range 1999
x => {
f.write_str("0.")?;
// this implementation avoids string allocation otherwise required
// for `.trim_end_matches('0')`
if x < 10 {
f.write_str("00")?;
// 0 is handled so it's not possible to have a trailing 0, we can just return
itoa::fmt(f, x)
} else if x < 100 {
f.write_str("0")?;
if x % 10 == 0 {
// trailing 0, divide by 10 and write
itoa::fmt(f, x / 10)
} else {
itoa::fmt(f, x)
}
} else {
// x is in range 101999
if x % 100 == 0 {
// two trailing 0s, divide by 100 and write
itoa::fmt(f, x / 100)
} else if x % 10 == 0 {
// one trailing 0, divide by 10 and write
itoa::fmt(f, x / 10)
} else {
itoa::fmt(f, x)
}
}
}
}
}
}
}
mod _naive {
use std::fmt;
pub struct Quality(pub(crate) u16);
impl fmt::Display for Quality {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
0 => f.write_str("0"),
1000 => f.write_str("1"),
x => {
write!(f, "{}", format!("{:03}", x).trim_end_matches('0'))
}
}
}
}
}

View File

@ -4,11 +4,13 @@ mod charset;
mod content_encoding; mod content_encoding;
mod extended; mod extended;
mod http_date; mod http_date;
mod quality;
mod quality_item; mod quality_item;
pub use self::charset::Charset; pub use self::charset::Charset;
pub use self::content_encoding::ContentEncoding; pub use self::content_encoding::ContentEncoding;
pub use self::extended::{parse_extended_value, ExtendedValue}; pub use self::extended::{parse_extended_value, ExtendedValue};
pub use self::http_date::HttpDate; pub use self::http_date::HttpDate;
pub use self::quality_item::{q, Quality, QualityItem}; pub use self::quality::{q, Quality};
pub use self::quality_item::QualityItem;
pub use language_tags::LanguageTag; pub use language_tags::LanguageTag;

View File

@ -0,0 +1,194 @@
use std::{
convert::{TryFrom, TryInto},
fmt,
};
use derive_more::{Display, Error};
pub(super) const MAX_QUALITY_INT: u16 = 1000;
pub(super) const MAX_QUALITY_FLOAT: f32 = 1.0;
/// Represents a quality used in q-factor values.
///
/// The default value is equivalent to `q=1.0` (the [max](Self::MAX) value).
///
/// # Implementation notes
/// The quality value is defined as a number between 0.0 and 1.0 with three decimal places.
/// This means there are 1001 possible values. Since floating point numbers are not exact and the
/// smallest floating point data type (`f32`) consumes four bytes, we use an `u16` value to store
/// the quality internally.
///
/// [RFC 7231 §5.3.1] gives more information on quality values in HTTP header fields.
///
/// # Examples
/// ```
/// use actix_http::header::{Quality, q};
/// assert_eq!(q(1.0), Quality::MAX);
///
/// assert_eq!(q(0.42).to_string(), "0.42");
/// assert_eq!(q(1.0).to_string(), "1");
/// assert_eq!(Quality::MIN.to_string(), "0");
/// ```
///
/// [RFC 7231 §5.3.1]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Quality(pub(super) u16);
impl Quality {
/// The maximum quality value, equivalent to `q=1.0`.
pub const MAX: Quality = Quality(MAX_QUALITY_INT);
/// The minimum quality value, equivalent to `q=0.0`.
pub const MIN: Quality = Quality(0);
/// Converts a float in the range 0.01.0 to a `Quality`.
///
/// Intentionally private. External uses should rely on the `TryFrom` impl.
///
/// # Panics
/// Panics in debug mode when value is not in the range 0.0 <= n <= 1.0.
pub(super) fn from_f32(value: f32) -> Self {
// Check that `value` is within range should be done before calling this method.
// Just in case, this debug_assert should catch if we were forgetful.
debug_assert!(
(0.0f32..=1.0f32).contains(&value),
"q value must be between 0.0 and 1.0"
);
Quality((value * MAX_QUALITY_INT as f32) as u16)
}
}
/// The default value is [`Quality::MAX`].
impl Default for Quality {
fn default() -> Quality {
Quality::MAX
}
}
// In benchmarks this is twice as fast as a naive approach using something like
// `format!("{}").trim_end_matches('0')` for non-fast path quality values
impl fmt::Display for Quality {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
0 => f.write_str("0"),
MAX_QUALITY_INT => f.write_str("1"),
// some number in the range 1999
x => {
f.write_str("0.")?;
// this implementation avoids string allocation otherwise required
// for `.trim_end_matches('0')`
if x < 10 {
f.write_str("00")?;
// 0 is handled so it's not possible to have a trailing 0, we can just return
itoa::fmt(f, x)
} else if x < 100 {
f.write_str("0")?;
if x % 10 == 0 {
// trailing 0, divide by 10 and write
itoa::fmt(f, x / 10)
} else {
itoa::fmt(f, x)
}
} else {
// x is in range 101999
if x % 100 == 0 {
// two trailing 0s, divide by 100 and write
itoa::fmt(f, x / 100)
} else if x % 10 == 0 {
// one trailing 0, divide by 10 and write
itoa::fmt(f, x / 10)
} else {
itoa::fmt(f, x)
}
}
}
}
}
}
#[derive(Debug, Clone, Display, Error)]
#[display(fmt = "quality out of bounds")]
#[non_exhaustive]
pub struct QualityOutOfBounds;
impl TryFrom<f32> for Quality {
type Error = QualityOutOfBounds;
fn try_from(value: f32) -> Result<Self, Self::Error> {
if (0.0..=MAX_QUALITY_FLOAT).contains(&value) {
Ok(Quality::from_f32(value))
} else {
Err(QualityOutOfBounds)
}
}
}
/// Convenience function to create a [`Quality`] from a `u16` (01000) or `f32` (0.01.0).
///
/// Not recommended for use with user input. Rely on the `TryFrom` impls where possible.
///
/// # Panics
/// Panics if value is out of range.
///
/// # Examples
/// ```
/// # use actix_http::header::{q, Quality};
/// let q1 = q(1.0);
/// assert_eq!(q1, Quality::MAX);
///
/// let q2 = q(0.0);
/// assert_eq!(q2, Quality::MIN);
///
/// let q3 = q(0.42);
/// ```
///
/// An out-of-range `f32` quality will panic.
/// ```should_panic
/// # use actix_http::header::q;
/// let _q2 = q(1.42);
/// ```
pub fn q<T>(quality: T) -> Quality
where
T: TryInto<Quality>,
T::Error: fmt::Debug,
{
quality.try_into().expect("quality value was out of bounds")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn q_helper() {
assert_eq!(q(0.5), Quality(500));
}
#[test]
fn display_output() {
assert_eq!(q(0.0).to_string(), "0");
assert_eq!(q(1.0).to_string(), "1");
assert_eq!(q(0.001).to_string(), "0.001");
assert_eq!(q(0.5).to_string(), "0.5");
assert_eq!(q(0.22).to_string(), "0.22");
assert_eq!(q(0.123).to_string(), "0.123");
assert_eq!(q(0.999).to_string(), "0.999");
}
#[test]
#[should_panic]
fn negative_quality() {
q(-1.0);
}
#[test]
#[should_panic]
fn quality_out_of_bounds() {
q(2.0);
}
}

View File

@ -1,95 +1,19 @@
use std::{ use std::{cmp, fmt, str};
cmp,
convert::{TryFrom, TryInto},
fmt, str,
};
use derive_more::{Display, Error};
use crate::error::ParseError; use crate::error::ParseError;
const MAX_QUALITY_INT: u16 = 1000; use super::{
const MAX_QUALITY_FLOAT: f32 = 1.0; quality::{MAX_QUALITY_FLOAT, MAX_QUALITY_INT},
Quality,
/// Represents a quality used in q-factor values. };
///
/// The default value is [`Quality::MAX`].
///
/// # Implementation notes
/// The quality value is defined as a number between 0 and 1 with three decimal places. This means
/// there are 1001 possible values. Since floating point numbers are not exact and the smallest
/// floating point data type (`f32`) consumes four bytes, we use an `u16` value to store the
/// quality internally. For performance reasons you may set quality directly to a value between 0
/// and 1000 e.g. `Quality(532)` matches the quality `q=0.532`.
///
/// [RFC 7231 §5.3.1] gives more information on quality values in HTTP header fields.
///
/// [RFC 7231 §5.3.1]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Quality(u16);
impl Quality {
/// The maximum quality value, equivalent to `q=1.0`.
pub const MAX: Quality = Quality(MAX_QUALITY_INT);
/// The minimum quality value, equivalent to `q=0.0`.
pub const MIN: Quality = Quality(0);
/// Converts a float in the range 0.01.0 to a `Quality`.
///
/// Intentionally private. External uses should rely on the `TryFrom` impl.
///
/// # Panics
/// Panics in debug mode when value is not in the range 0.0 <= n <= 1.0.
fn from_f32(value: f32) -> Self {
// Check that `value` is within range should be done before calling this method.
// Just in case, this debug_assert should catch if we were forgetful.
debug_assert!(
(0.0f32..=1.0f32).contains(&value),
"q value must be between 0.0 and 1.0"
);
Quality((value * MAX_QUALITY_INT as f32) as u16)
}
}
/// The default value is [`Quality::MAX`].
impl Default for Quality {
fn default() -> Quality {
Quality::MAX
}
}
#[derive(Debug, Clone, Display, Error)]
#[non_exhaustive]
pub struct QualityOutOfBounds;
impl TryFrom<u16> for Quality {
type Error = QualityOutOfBounds;
fn try_from(value: u16) -> Result<Self, Self::Error> {
if (0..=MAX_QUALITY_INT).contains(&value) {
Ok(Quality(value))
} else {
Err(QualityOutOfBounds)
}
}
}
impl TryFrom<f32> for Quality {
type Error = QualityOutOfBounds;
fn try_from(value: f32) -> Result<Self, Self::Error> {
if (0.0..=MAX_QUALITY_FLOAT).contains(&value) {
Ok(Quality::from_f32(value))
} else {
Err(QualityOutOfBounds)
}
}
}
/// Represents an item with a quality value as defined /// Represents an item with a quality value as defined
/// in [RFC 7231 §5.3.1](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1). /// in [RFC 7231 §5.3.1](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1).
///
/// # Examples
/// ```
///
/// ```
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct QualityItem<T> { pub struct QualityItem<T> {
/// The wrapped contents of the field. /// The wrapped contents of the field.
@ -128,10 +52,13 @@ impl<T: fmt::Display> fmt::Display for QualityItem<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.item, f)?; fmt::Display::fmt(&self.item, f)?;
match self.quality.0 { match self.quality {
MAX_QUALITY_INT => Ok(()), // q-factor value is implied for max value
0 => f.write_str("; q=0"), Quality(MAX_QUALITY_INT) => Ok(()),
x => write!(f, "; q=0.{}", format!("{:03}", x).trim_end_matches('0')),
Quality(0) => f.write_str("; q=0"),
q => write!(f, "; q={}", q),
} }
} }
} }
@ -177,7 +104,7 @@ impl<T: str::FromStr> str::FromStr for QualityItem<T> {
let q_value = q_val.parse::<f32>().map_err(|_| ParseError::Header)?; let q_value = q_val.parse::<f32>().map_err(|_| ParseError::Header)?;
if (0f32..=1f32).contains(&q_value) { if (0f32..=MAX_QUALITY_FLOAT).contains(&q_value) {
quality = q_value; quality = q_value;
raw_item = parts[1]; raw_item = parts[1];
} else { } else {
@ -193,44 +120,6 @@ impl<T: str::FromStr> str::FromStr for QualityItem<T> {
} }
} }
/// Convenience function to create a [`Quality`] from a `u16` (01000) or `f32` (0.01.0).
///
/// Not recommended for use with user input. Rely on the `TryFrom` impls where possible.
///
/// # Panics
/// Panics if value is out of range.
///
/// # Examples
/// ```
/// # use actix_http::header::{q, Quality};
/// let q1 = q(1000);
/// assert_eq!(q1, Quality::MAX);
///
/// let q2 = q(0.0);
/// assert_eq!(q2, Quality::MIN);
///
/// assert_eq!(q(0.42), q(420));
/// ```
///
/// An out-of-range `u16` quality will panic.
/// ```should_panic
/// # use actix_http::header::q;
/// let _q1 = q(1042);
/// ```
///
/// An out-of-range `f32` quality will panic.
/// ```should_panic
/// # use actix_http::header::q;
/// let _q2 = q(1.42);
/// ```
pub fn q<T>(quality: T) -> Quality
where
T: TryInto<Quality>,
T::Error: fmt::Debug,
{
quality.try_into().expect("quality value was out of bounds")
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -285,7 +174,7 @@ mod tests {
#[test] #[test]
fn test_quality_item_fmt_q_1() { fn test_quality_item_fmt_q_1() {
use Encoding::*; use Encoding::*;
let x = qitem(Chunked); let x = QualityItem::max(Chunked);
assert_eq!(format!("{}", x), "chunked"); assert_eq!(format!("{}", x), "chunked");
} }
#[test] #[test]
@ -384,25 +273,8 @@ mod tests {
fn test_quality_item_ordering() { fn test_quality_item_ordering() {
let x: QualityItem<Encoding> = "gzip; q=0.5".parse().ok().unwrap(); let x: QualityItem<Encoding> = "gzip; q=0.5".parse().ok().unwrap();
let y: QualityItem<Encoding> = "gzip; q=0.273".parse().ok().unwrap(); let y: QualityItem<Encoding> = "gzip; q=0.273".parse().ok().unwrap();
let comparision_result: bool = x.gt(&y); let comparison_result: bool = x.gt(&y);
assert!(comparision_result) assert!(comparison_result)
}
#[test]
fn test_quality() {
assert_eq!(q(0.5), Quality(500));
}
#[test]
#[should_panic]
fn test_quality_invalid() {
q(-1.0);
}
#[test]
#[should_panic]
fn test_quality_invalid2() {
q(2.0);
} }
#[test] #[test]

View File

@ -65,15 +65,9 @@ crate::http::header::common_header! {
/// Accept(vec![ /// Accept(vec![
/// QualityItem::max(mime::TEXT_HTML), /// QualityItem::max(mime::TEXT_HTML),
/// QualityItem::max("application/xhtml+xml".parse().unwrap()), /// QualityItem::max("application/xhtml+xml".parse().unwrap()),
/// QualityItem::new( /// QualityItem::new(mime::TEXT_XML, q(0.9)),
/// mime::TEXT_XML,
/// q(900)
/// ),
/// QualityItem::max("image/webp".parse().unwrap()), /// QualityItem::max("image/webp".parse().unwrap()),
/// QualityItem::new( /// QualityItem::new(mime::STAR_STAR, q(0.8)),
/// mime::STAR_STAR,
/// q(800)
/// ),
/// ]) /// ])
/// ); /// );
/// ``` /// ```
@ -85,7 +79,7 @@ crate::http::header::common_header! {
test1, test1,
vec![b"audio/*; q=0.2, audio/basic"], vec![b"audio/*; q=0.2, audio/basic"],
Some(Accept(vec![ Some(Accept(vec![
QualityItem::new("audio/*".parse().unwrap(), q(200)), QualityItem::new("audio/*".parse().unwrap(), q(0.2)),
QualityItem::max("audio/basic".parse().unwrap()), QualityItem::max("audio/basic".parse().unwrap()),
]))); ])));
@ -93,11 +87,11 @@ crate::http::header::common_header! {
test2, test2,
vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"], vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"],
Some(Accept(vec![ Some(Accept(vec![
QualityItem::new(mime::TEXT_PLAIN, q(500)), QualityItem::new(mime::TEXT_PLAIN, q(0.5)),
QualityItem::max(mime::TEXT_HTML), QualityItem::max(mime::TEXT_HTML),
QualityItem::new( QualityItem::new(
"text/x-dvi".parse().unwrap(), "text/x-dvi".parse().unwrap(),
q(800)), q(0.8)),
QualityItem::max("text/x-c".parse().unwrap()), QualityItem::max("text/x-c".parse().unwrap()),
]))); ])));
@ -113,7 +107,7 @@ crate::http::header::common_header! {
vec![b"text/plain; charset=utf-8; q=0.5"], vec![b"text/plain; charset=utf-8; q=0.5"],
Some(Accept(vec![ Some(Accept(vec![
QualityItem::new(mime::TEXT_PLAIN_UTF_8, QualityItem::new(mime::TEXT_PLAIN_UTF_8,
q(500)), q(0.5)),
]))); ])));
#[test] #[test]
@ -213,10 +207,10 @@ impl Accept {
/// ///
/// [q-factor weighting]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2 /// [q-factor weighting]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2
pub fn preference(&self) -> Mime { pub fn preference(&self) -> Mime {
use actix_http::header::q; use actix_http::header::Quality;
let mut max_item = None; let mut max_item = None;
let mut max_pref = q(0); let mut max_pref = Quality::MIN;
// uses manual max lookup loop since we want the first occurrence in the case of same // uses manual max lookup loop since we want the first occurrence in the case of same
// preference but `Iterator::max_by_key` would give us the last occurrence // preference but `Iterator::max_by_key` would give us the last occurrence

View File

@ -37,8 +37,8 @@ crate::http::header::common_header! {
/// let mut builder = HttpResponse::Ok(); /// let mut builder = HttpResponse::Ok();
/// builder.insert_header( /// builder.insert_header(
/// AcceptCharset(vec![ /// AcceptCharset(vec![
/// QualityItem::new(Charset::Us_Ascii, q(900)), /// QualityItem::new(Charset::Us_Ascii, q(0.9)),
/// QualityItem::new(Charset::Iso_8859_10, q(200)), /// QualityItem::new(Charset::Iso_8859_10, q(0.2)),
/// ]) /// ])
/// ); /// );
/// ``` /// ```

View File

@ -59,7 +59,7 @@ common_header! {
/// builder.insert_header( /// builder.insert_header(
/// AcceptEncoding(vec![ /// AcceptEncoding(vec![
/// QualityItem::max(Encoding::Chunked), /// QualityItem::max(Encoding::Chunked),
/// QualityItem::new(Encoding::Gzip, q(600)), /// QualityItem::new(Encoding::Gzip, q(0.60)),
/// QualityItem::min(Encoding::EncodingExt("*".to_owned())), /// QualityItem::min(Encoding::EncodingExt("*".to_owned())),
/// ]) /// ])
/// ); /// );
@ -79,3 +79,5 @@ common_header! {
common_header_test!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]); common_header_test!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]);
} }
} }
// TODO: shortcut for EncodingExt(*) = Any

View File

@ -50,8 +50,8 @@ common_header! {
/// builder.insert_header( /// builder.insert_header(
/// AcceptLanguage(vec![ /// AcceptLanguage(vec![
/// QualityItem::max("da".parse().unwrap()), /// QualityItem::max("da".parse().unwrap()),
/// QualityItem::new("en-GB".parse().unwrap(), q(800)), /// QualityItem::new("en-GB".parse().unwrap(), q(0.8)),
/// QualityItem::new("en".parse().unwrap(), q(700)), /// QualityItem::new("en".parse().unwrap(), q(0.7)),
/// ]) /// ])
/// ); /// );
/// ``` /// ```
@ -73,7 +73,7 @@ common_header! {
vec![b"en-US, en; q=0.5, fr"], vec![b"en-US, en; q=0.5, fr"],
Some(AcceptLanguage(vec![ Some(AcceptLanguage(vec![
QualityItem::max("en-US".parse().unwrap()), QualityItem::max("en-US".parse().unwrap()),
QualityItem::new("en".parse().unwrap(), q(500)), QualityItem::new("en".parse().unwrap(), q(0.5)),
QualityItem::max("fr".parse().unwrap()), QualityItem::max("fr".parse().unwrap()),
])) ]))
); );
@ -83,10 +83,10 @@ common_header! {
vec![b"fr-CH, fr; q=0.9, en; q=0.8, de; q=0.7, *; q=0.5"], vec![b"fr-CH, fr; q=0.9, en; q=0.8, de; q=0.7, *; q=0.5"],
Some(AcceptLanguage(vec![ Some(AcceptLanguage(vec![
QualityItem::max("fr-CH".parse().unwrap()), QualityItem::max("fr-CH".parse().unwrap()),
QualityItem::new("fr".parse().unwrap(), q(900)), QualityItem::new("fr".parse().unwrap(), q(0.9)),
QualityItem::new("en".parse().unwrap(), q(800)), QualityItem::new("en".parse().unwrap(), q(0.8)),
QualityItem::new("de".parse().unwrap(), q(700)), QualityItem::new("de".parse().unwrap(), q(0.7)),
QualityItem::new("*".parse().unwrap(), q(500)), QualityItem::new("*".parse().unwrap(), q(0.5)),
])) ]))
); );
} }
@ -155,11 +155,11 @@ mod tests {
assert_eq!(test.ranked(), vec!("fr-CH".parse().unwrap())); assert_eq!(test.ranked(), vec!("fr-CH".parse().unwrap()));
let test = AcceptLanguage(vec![ let test = AcceptLanguage(vec![
QualityItem::new("fr".parse().unwrap(), q(900)), QualityItem::new("fr".parse().unwrap(), q(0.900)),
QualityItem::new("fr-CH".parse().unwrap(), q(1000)), QualityItem::new("fr-CH".parse().unwrap(), q(1.0)),
QualityItem::new("en".parse().unwrap(), q(800)), QualityItem::new("en".parse().unwrap(), q(0.800)),
QualityItem::new("*".parse().unwrap(), q(500)), QualityItem::new("*".parse().unwrap(), q(0.500)),
QualityItem::new("de".parse().unwrap(), q(700)), QualityItem::new("de".parse().unwrap(), q(0.700)),
]); ]);
assert_eq!( assert_eq!(
test.ranked(), test.ranked(),
@ -194,11 +194,11 @@ mod tests {
#[test] #[test]
fn preference_selection() { fn preference_selection() {
let test = AcceptLanguage(vec![ let test = AcceptLanguage(vec![
QualityItem::new("fr".parse().unwrap(), q(900)), QualityItem::new("fr".parse().unwrap(), q(0.900)),
QualityItem::new("fr-CH".parse().unwrap(), q(1000)), QualityItem::new("fr-CH".parse().unwrap(), q(1.0)),
QualityItem::new("en".parse().unwrap(), q(800)), QualityItem::new("en".parse().unwrap(), q(0.800)),
QualityItem::new("*".parse().unwrap(), q(500)), QualityItem::new("*".parse().unwrap(), q(0.500)),
QualityItem::new("de".parse().unwrap(), q(700)), QualityItem::new("de".parse().unwrap(), q(0.700)),
]); ]);
assert_eq!( assert_eq!(
test.preference(), test.preference(),