mirror of https://github.com/rwf2/Rocket.git
Handle specificity based preferences in Accept. Allow 3 decimals in q parameter.
This commit is contained in:
parent
b102a6a497
commit
7d48944080
|
@ -96,23 +96,28 @@ impl Accept {
|
|||
|
||||
pub fn preferred(&self) -> &WeightedMediaType {
|
||||
static ANY: WeightedMediaType = WeightedMediaType(MediaType::Any, None);
|
||||
//
|
||||
|
||||
// See https://tools.ietf.org/html/rfc7231#section-5.3.2.
|
||||
let mut all = self.iter();
|
||||
let mut preferred = all.next().unwrap_or(&ANY);
|
||||
for current in all {
|
||||
if current.weight().is_none() && preferred.weight().is_some() {
|
||||
preferred = current;
|
||||
} else if current.weight_or(0.0) > preferred.weight_or(1.0) {
|
||||
preferred = current;
|
||||
// FIXME: Prefer text/html over text/*, for example.
|
||||
} else if current.media_type() == preferred.media_type() {
|
||||
if current.weight() == preferred.weight() {
|
||||
let c_count = current.params().filter(|p| p.0 != "q").count();
|
||||
let p_count = preferred.params().filter(|p| p.0 != "q").count();
|
||||
if c_count > p_count {
|
||||
preferred = current;
|
||||
}
|
||||
for media_type in all {
|
||||
if media_type.weight().is_none() && preferred.weight().is_some() {
|
||||
// Media types without a `q` parameter are preferred.
|
||||
preferred = media_type;
|
||||
} else if media_type.weight_or(0.0) > preferred.weight_or(1.0) {
|
||||
// Prefer media types with a greater weight, but if one doesn't
|
||||
// have a weight, prefer the one we already have.
|
||||
preferred = media_type;
|
||||
} else if media_type.specificity() > preferred.specificity() {
|
||||
// Prefer more specific media types over less specific ones. IE:
|
||||
// text/html over application/*.
|
||||
preferred = media_type;
|
||||
} else if media_type == preferred {
|
||||
// Finally, all other things being equal, prefer a media type
|
||||
// with more parameters over one with fewer. IE: text/html; a=b
|
||||
// over text/html.
|
||||
if media_type.params().count() > preferred.params().count() {
|
||||
preferred = media_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -120,6 +125,8 @@ impl Accept {
|
|||
preferred
|
||||
}
|
||||
|
||||
// */html, text/plain
|
||||
|
||||
#[inline(always)]
|
||||
pub fn first(&self) -> Option<&WeightedMediaType> {
|
||||
self.iter().next()
|
||||
|
@ -189,13 +196,22 @@ mod test {
|
|||
#[test]
|
||||
fn test_preferred() {
|
||||
assert_preference!("text/*", "text/*");
|
||||
assert_preference!("text/*, text/html", "text/*");
|
||||
assert_preference!("text/*, text/html", "text/html");
|
||||
assert_preference!("text/*; q=0.1, text/html", "text/html");
|
||||
assert_preference!("text/*; q=1, text/html", "text/html");
|
||||
assert_preference!("text/html, text/*", "text/html");
|
||||
assert_preference!("text/*, text/html", "text/html");
|
||||
assert_preference!("text/html, text/*; q=1", "text/html");
|
||||
assert_preference!("text/html; q=1, text/html", "text/html");
|
||||
assert_preference!("text/html, text/*; q=0.1", "text/html");
|
||||
|
||||
assert_preference!("text/html, application/json", "text/html");
|
||||
assert_preference!("text/html, application/json; q=1", "text/html");
|
||||
assert_preference!("application/json; q=1, text/html", "text/html");
|
||||
|
||||
assert_preference!("text/*, application/json", "application/json");
|
||||
assert_preference!("*/*, text/*", "text/*");
|
||||
assert_preference!("*/*, text/*, text/plain", "text/plain");
|
||||
|
||||
assert_preference!("a/b; q=0.1, a/b; q=0.2", "a/b; q=0.2");
|
||||
assert_preference!("a/b; q=0.1, b/c; q=0.2", "b/c; q=0.2");
|
||||
|
|
|
@ -228,6 +228,78 @@ impl MediaType {
|
|||
self.sub.to_str(self.source.as_ref()).into()
|
||||
}
|
||||
|
||||
/// Returns a `u8` representing how specific the top-level type and subtype
|
||||
/// of this media type are.
|
||||
///
|
||||
/// The return value is either `0`, `1`, or `2`, where `2` is the most
|
||||
/// specific. A `0` is returned when both the top and sublevel types are
|
||||
/// `*`. A `1` is returned when only one of the top or sublevel types is
|
||||
/// `*`, and a `2` is returned when neither the top or sublevel types are
|
||||
/// `*`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::http::MediaType;
|
||||
///
|
||||
/// let mt = MediaType::Plain;
|
||||
/// assert_eq!(mt.specificity(), 2);
|
||||
///
|
||||
/// let mt = MediaType::new("text", "*");
|
||||
/// assert_eq!(mt.specificity(), 1);
|
||||
///
|
||||
/// let mt = MediaType::Any;
|
||||
/// assert_eq!(mt.specificity(), 0);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn specificity(&self) -> u8 {
|
||||
(self.top() != "*") as u8 + (self.sub() != "*") as u8
|
||||
}
|
||||
|
||||
/// Compares `self` with `other` and returns `true` if `self` and `other`
|
||||
/// are exactly equal to eachother, including with respect to their
|
||||
/// parameters.
|
||||
///
|
||||
/// This is different from the `PartialEq` implementation in that it
|
||||
/// considers parameters. If `PartialEq` returns false, this function is
|
||||
/// guaranteed to return false. Similarly, if this function returns `true`,
|
||||
/// `PartialEq` is guaranteed to return true. However, if `PartialEq`
|
||||
/// returns `true`, this function may or may not return `true`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::http::MediaType;
|
||||
///
|
||||
/// let plain = MediaType::Plain;
|
||||
/// let plain2 = MediaType::with_params("text", "plain", ("charset", "utf-8"));
|
||||
/// let just_plain = MediaType::new("text", "plain");
|
||||
///
|
||||
/// // The `PartialEq` implementation doesn't consider parameters.
|
||||
/// assert!(plain == just_plain);
|
||||
/// assert!(just_plain == plain2);
|
||||
/// assert!(plain == plain2);
|
||||
///
|
||||
/// // While `exact_eq` does.
|
||||
/// assert!(!plain.exact_eq(&just_plain));
|
||||
/// assert!(!plain2.exact_eq(&just_plain));
|
||||
/// assert!(plain.exact_eq(&plain2));
|
||||
/// ```
|
||||
pub fn exact_eq(&self, other: &MediaType) -> bool {
|
||||
self == other && {
|
||||
let (mut a_params, mut b_params) = (self.params(), other.params());
|
||||
loop {
|
||||
match (a_params.next(), b_params.next()) {
|
||||
(Some(a), Some(b)) if a != b => return false,
|
||||
(Some(_), Some(_)) => continue,
|
||||
(Some(_), None) => return false,
|
||||
(None, Some(_)) => return false,
|
||||
(None, None) => return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over the (key, value) pairs of the media type's
|
||||
/// parameter list. The iterator will be empty if the media type has no
|
||||
/// parameters.
|
||||
|
|
|
@ -7,8 +7,8 @@ use http::{MediaType, Accept, WeightedMediaType};
|
|||
|
||||
fn q<'a>(_: &'a str, media_type: &MediaType) -> ParseResult<&'a str, Option<f32>> {
|
||||
match media_type.params().next() {
|
||||
Some(("q", value)) if value.len() <= 4 => match value.parse::<f32>().ok() {
|
||||
Some(q) if q > 1.0 => ParseError::custom("accept", "q value must be <= 1.0"),
|
||||
Some(("q", value)) if value.len() <= 5 => match value.parse::<f32>().ok() {
|
||||
Some(q) if q > 1. => ParseError::custom("accept", "q value must be <= 1"),
|
||||
Some(q) => ParseResult::Done(Some(q)),
|
||||
None => ParseError::custom("accept", "q value must be float")
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue