mirror of
https://github.com/actix/actix-web.git
synced 2024-12-30 12:00:38 +00:00
add method for getting accept type preference (#1793)
This commit is contained in:
parent
2f11ef089b
commit
70f4747a23
3 changed files with 138 additions and 9 deletions
|
@ -3,6 +3,7 @@
|
|||
## Unreleased - 2020-xx-xx
|
||||
### Added
|
||||
* HttpResponse builders for 1xx status codes. [#1768]
|
||||
* `Accept::mime_precedence` and `Accept::mime_preference`. [#1793]
|
||||
|
||||
### Fixed
|
||||
* Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767]
|
||||
|
@ -12,6 +13,7 @@
|
|||
|
||||
[#1767]: https://github.com/actix/actix-web/pull/1767
|
||||
[#1768]: https://github.com/actix/actix-web/pull/1768
|
||||
[#1793]: https://github.com/actix/actix-web/pull/1793
|
||||
|
||||
|
||||
## 2.1.0 - 2020-10-30
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::cmp::Ordering;
|
||||
|
||||
use mime::Mime;
|
||||
|
||||
use crate::header::{qitem, QualityItem};
|
||||
|
@ -7,7 +9,7 @@ header! {
|
|||
/// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2)
|
||||
///
|
||||
/// The `Accept` header field can be used by user agents to specify
|
||||
/// response media types that are acceptable. Accept header fields can
|
||||
/// response media types that are acceptable. Accept header fields can
|
||||
/// be used to indicate that the request is specifically limited to a
|
||||
/// small set of desired types, as in the case of a request for an
|
||||
/// in-line image
|
||||
|
@ -97,14 +99,14 @@ header! {
|
|||
test_header!(
|
||||
test1,
|
||||
vec![b"audio/*; q=0.2, audio/basic"],
|
||||
Some(HeaderField(vec![
|
||||
Some(Accept(vec![
|
||||
QualityItem::new("audio/*".parse().unwrap(), q(200)),
|
||||
qitem("audio/basic".parse().unwrap()),
|
||||
])));
|
||||
test_header!(
|
||||
test2,
|
||||
vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"],
|
||||
Some(HeaderField(vec![
|
||||
Some(Accept(vec![
|
||||
QualityItem::new(mime::TEXT_PLAIN, q(500)),
|
||||
qitem(mime::TEXT_HTML),
|
||||
QualityItem::new(
|
||||
|
@ -138,23 +140,148 @@ header! {
|
|||
}
|
||||
|
||||
impl Accept {
|
||||
/// A constructor to easily create `Accept: */*`.
|
||||
/// Construct `Accept: */*`.
|
||||
pub fn star() -> Accept {
|
||||
Accept(vec![qitem(mime::STAR_STAR)])
|
||||
}
|
||||
|
||||
/// A constructor to easily create `Accept: application/json`.
|
||||
/// Construct `Accept: application/json`.
|
||||
pub fn json() -> Accept {
|
||||
Accept(vec![qitem(mime::APPLICATION_JSON)])
|
||||
}
|
||||
|
||||
/// A constructor to easily create `Accept: text/*`.
|
||||
/// Construct `Accept: text/*`.
|
||||
pub fn text() -> Accept {
|
||||
Accept(vec![qitem(mime::TEXT_STAR)])
|
||||
}
|
||||
|
||||
/// A constructor to easily create `Accept: image/*`.
|
||||
/// Construct `Accept: image/*`.
|
||||
pub fn image() -> Accept {
|
||||
Accept(vec![qitem(mime::IMAGE_STAR)])
|
||||
}
|
||||
|
||||
/// Construct `Accept: text/html`.
|
||||
pub fn html() -> Accept {
|
||||
Accept(vec![qitem(mime::TEXT_HTML)])
|
||||
}
|
||||
|
||||
/// Returns a sorted list of mime types from highest to lowest preference, accounting for
|
||||
/// [q-factor weighting] and specificity.
|
||||
///
|
||||
/// [q-factor weighting]: https://tools.ietf.org/html/rfc7231#section-5.3.2
|
||||
pub fn mime_precedence(&self) -> Vec<Mime> {
|
||||
let mut types = self.0.clone();
|
||||
|
||||
// use stable sort so items with equal q-factor and specificity retain listed order
|
||||
types.sort_by(|a, b| {
|
||||
// sort by q-factor descending
|
||||
b.quality.cmp(&a.quality).then_with(|| {
|
||||
// use specificity rules on mime types with
|
||||
// same q-factor (eg. text/html > text/* > */*)
|
||||
|
||||
// subtypes are not comparable if main type is star, so return
|
||||
match (a.item.type_(), b.item.type_()) {
|
||||
(mime::STAR, mime::STAR) => return Ordering::Equal,
|
||||
|
||||
// a is sorted after b
|
||||
(mime::STAR, _) => return Ordering::Greater,
|
||||
|
||||
// a is sorted before b
|
||||
(_, mime::STAR) => return Ordering::Less,
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// in both these match expressions, the returned ordering appears
|
||||
// inverted because sort is high-to-low ("descending") precedence
|
||||
match (a.item.subtype(), b.item.subtype()) {
|
||||
(mime::STAR, mime::STAR) => Ordering::Equal,
|
||||
|
||||
// a is sorted after b
|
||||
(mime::STAR, _) => Ordering::Greater,
|
||||
|
||||
// a is sorted before b
|
||||
(_, mime::STAR) => Ordering::Less,
|
||||
|
||||
_ => Ordering::Equal,
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
types.into_iter().map(|qitem| qitem.item).collect()
|
||||
}
|
||||
|
||||
/// Extracts the most preferable mime type, accounting for [q-factor weighting].
|
||||
///
|
||||
/// If no q-factors are provided, the first mime type is chosen. Note that items without
|
||||
/// q-factors are given the maximum preference value.
|
||||
///
|
||||
/// Returns `None` if contained list is empty.
|
||||
///
|
||||
/// [q-factor weighting]: https://tools.ietf.org/html/rfc7231#section-5.3.2
|
||||
pub fn mime_preference(&self) -> Option<Mime> {
|
||||
let types = self.mime_precedence();
|
||||
types.first().cloned()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::header::q;
|
||||
|
||||
#[test]
|
||||
fn test_mime_precedence() {
|
||||
let test = Accept(vec![]);
|
||||
assert!(test.mime_precedence().is_empty());
|
||||
|
||||
let test = Accept(vec![qitem(mime::APPLICATION_JSON)]);
|
||||
assert_eq!(test.mime_precedence(), vec!(mime::APPLICATION_JSON));
|
||||
|
||||
let test = Accept(vec![
|
||||
qitem(mime::TEXT_HTML),
|
||||
"application/xhtml+xml".parse().unwrap(),
|
||||
QualityItem::new("application/xml".parse().unwrap(), q(0.9)),
|
||||
QualityItem::new(mime::STAR_STAR, q(0.8)),
|
||||
]);
|
||||
assert_eq!(
|
||||
test.mime_precedence(),
|
||||
vec![
|
||||
mime::TEXT_HTML,
|
||||
"application/xhtml+xml".parse().unwrap(),
|
||||
"application/xml".parse().unwrap(),
|
||||
mime::STAR_STAR,
|
||||
]
|
||||
);
|
||||
|
||||
let test = Accept(vec![
|
||||
qitem(mime::STAR_STAR),
|
||||
qitem(mime::IMAGE_STAR),
|
||||
qitem(mime::IMAGE_PNG),
|
||||
]);
|
||||
assert_eq!(
|
||||
test.mime_precedence(),
|
||||
vec![mime::IMAGE_PNG, mime::IMAGE_STAR, mime::STAR_STAR]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mime_preference() {
|
||||
let test = Accept(vec![
|
||||
qitem(mime::TEXT_HTML),
|
||||
"application/xhtml+xml".parse().unwrap(),
|
||||
QualityItem::new("application/xml".parse().unwrap(), q(0.9)),
|
||||
QualityItem::new(mime::STAR_STAR, q(0.8)),
|
||||
]);
|
||||
assert_eq!(test.mime_preference(), Some(mime::TEXT_HTML));
|
||||
|
||||
let test = Accept(vec![
|
||||
QualityItem::new("video/*".parse().unwrap(), q(0.8)),
|
||||
qitem(mime::IMAGE_PNG),
|
||||
QualityItem::new(mime::STAR_STAR, q(0.5)),
|
||||
qitem(mime::IMAGE_SVG),
|
||||
QualityItem::new(mime::IMAGE_STAR, q(0.8)),
|
||||
]);
|
||||
assert_eq!(test.mime_preference(), Some(mime::IMAGE_PNG));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ use self::internal::IntoQuality;
|
|||
///
|
||||
/// [RFC7231 Section 5.3.1](https://tools.ietf.org/html/rfc7231#section-5.3.1)
|
||||
/// gives more information on quality values in HTTP header fields.
|
||||
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Quality(u16);
|
||||
|
||||
impl Default for Quality {
|
||||
|
@ -121,7 +121,7 @@ fn from_f32(f: f32) -> Quality {
|
|||
/// Convenience function to wrap a value in a `QualityItem`
|
||||
/// Sets `q` to the default 1.0
|
||||
pub fn qitem<T>(item: T) -> QualityItem<T> {
|
||||
QualityItem::new(item, Default::default())
|
||||
QualityItem::new(item, Quality::default())
|
||||
}
|
||||
|
||||
/// Convenience function to create a `Quality` from a float or integer.
|
||||
|
|
Loading…
Reference in a new issue