mirror of https://github.com/rwf2/Rocket.git
Add the Accept ContentType structure.
This commit is contained in:
parent
13359d4f50
commit
c09644b270
|
@ -29,8 +29,8 @@ time = "0.1"
|
|||
memchr = "1"
|
||||
base64 = "0.4"
|
||||
smallvec = "0.3"
|
||||
pear = "0.0.5"
|
||||
pear_codegen = "0.0.5"
|
||||
pear = "0.0.7"
|
||||
pear_codegen = "0.0.7"
|
||||
|
||||
[dependencies.cookie]
|
||||
version = "0.7.2"
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
use http::MediaType;
|
||||
use http::parse::parse_accept;
|
||||
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct WeightedMediaType(pub MediaType, pub Option<f32>);
|
||||
|
||||
impl WeightedMediaType {
|
||||
#[inline(always)]
|
||||
pub fn media_type(&self) -> &MediaType {
|
||||
&self.0
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn weight(&self) -> Option<f32> {
|
||||
self.1
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn weight_or(&self, default: f32) -> f32 {
|
||||
self.1.unwrap_or(default)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn into_inner(self) -> MediaType {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for WeightedMediaType {
|
||||
type Target = MediaType;
|
||||
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &MediaType {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// The HTTP Accept header.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Accept(pub Vec<WeightedMediaType>);
|
||||
|
||||
static ANY: WeightedMediaType = WeightedMediaType(MediaType::Any, None);
|
||||
|
||||
impl Accept {
|
||||
pub fn preferred(&self) -> &WeightedMediaType {
|
||||
// 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;
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
preferred
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn first(&self) -> Option<&WeightedMediaType> {
|
||||
if self.0.len() > 0 {
|
||||
Some(&self.0[0])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn iter<'a>(&'a self) -> impl Iterator<Item=&'a WeightedMediaType> + 'a {
|
||||
self.0.iter()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn media_types<'a>(&'a self) -> impl Iterator<Item=&'a MediaType> + 'a {
|
||||
self.0.iter().map(|weighted_mt| weighted_mt.media_type())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Accept {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
for (i, media_type) in self.iter().enumerate() {
|
||||
if i >= 1 { write!(f, ", ")?; }
|
||||
write!(f, "{}", media_type.0)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Accept {
|
||||
// Ideally we'd return a `ParseError`, but that requires a lifetime.
|
||||
type Err = String;
|
||||
|
||||
#[inline]
|
||||
fn from_str(raw: &str) -> Result<Accept, String> {
|
||||
parse_accept(raw).map_err(|e| e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use http::{Accept, MediaType};
|
||||
|
||||
macro_rules! assert_preference {
|
||||
($string:expr, $expect:expr) => (
|
||||
let accept: Accept = $string.parse().expect("accept string parse");
|
||||
let expected: MediaType = $expect.parse().expect("media type parse");
|
||||
let preferred = accept.preferred();
|
||||
assert_eq!(preferred.media_type().to_string(), expected.to_string());
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_preferred() {
|
||||
assert_preference!("text/*", "text/*");
|
||||
assert_preference!("text/*, text/html", "text/*");
|
||||
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/html, text/*; q=1", "text/html");
|
||||
assert_preference!("text/html, text/*; q=0.1", "text/html");
|
||||
assert_preference!("text/html, application/json", "text/html");
|
||||
|
||||
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");
|
||||
assert_preference!("a/b; q=0.5, b/c; q=0.2", "a/b; q=0.5");
|
||||
|
||||
assert_preference!("a/b; q=0.5, b/c; q=0.2, c/d", "c/d");
|
||||
assert_preference!("a/b; q=0.5; v=1, a/b", "a/b");
|
||||
|
||||
assert_preference!("a/b; v=1, a/b; v=1; c=2", "a/b; v=1; c=2");
|
||||
assert_preference!("a/b; v=1; c=2, a/b; v=1", "a/b; v=1; c=2");
|
||||
assert_preference!("a/b; q=0.5; v=1, a/b; q=0.5; v=1; c=2",
|
||||
"a/b; q=0.5; v=1; c=2");
|
||||
assert_preference!("a/b; q=0.6; v=1, a/b; q=0.5; v=1; c=2",
|
||||
"a/b; q=0.6; v=1");
|
||||
}
|
||||
}
|
|
@ -17,9 +17,10 @@ mod media_type;
|
|||
mod content_type;
|
||||
mod status;
|
||||
mod header;
|
||||
mod accept;
|
||||
mod parse;
|
||||
|
||||
// We need to export this for codegen, but otherwise it's unnecessary.
|
||||
// We need to export these for codegen, but otherwise it's unnecessary.
|
||||
// TODO: Expose a `const fn` from ContentType when possible. (see RFC#1817)
|
||||
#[doc(hidden)] pub mod ascii;
|
||||
#[doc(hidden)] pub use self::parse::IndexedStr;
|
||||
|
@ -27,6 +28,7 @@ mod parse;
|
|||
|
||||
pub use self::method::Method;
|
||||
pub use self::content_type::ContentType;
|
||||
pub use self::accept::{Accept, WeightedMediaType};
|
||||
pub use self::status::{Status, StatusClass};
|
||||
pub use self::header::{Header, HeaderMap};
|
||||
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
use pear::{ParseResult, ParseError};
|
||||
use pear::parsers::*;
|
||||
|
||||
use http::parse::checkers::is_whitespace;
|
||||
use http::parse::media_type::media_type;
|
||||
use http::{MediaType, Accept, WeightedMediaType};
|
||||
|
||||
fn q_value<'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) => ParseResult::Done(Some(q)),
|
||||
None => ParseError::custom("accept", "q value must be float")
|
||||
},
|
||||
_ => ParseResult::Done(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[parser]
|
||||
fn accept<'a>(input: &mut &'a str) -> ParseResult<&'a str, Accept> {
|
||||
let mut media_types = vec![];
|
||||
repeat_while!(eat(','), {
|
||||
skip_while(is_whitespace);
|
||||
let media_type = media_type(input);
|
||||
let weight = q_value(&media_type);
|
||||
media_types.push(WeightedMediaType(media_type, weight));
|
||||
});
|
||||
|
||||
Accept(media_types)
|
||||
}
|
||||
|
||||
pub fn parse_accept(mut input: &str) -> Result<Accept, ParseError<&str>> {
|
||||
parse!(&mut input, (accept(), eof()).0).into()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use http::{Accept, MediaType, WeightedMediaType};
|
||||
use super::{ParseResult, parse_accept};
|
||||
|
||||
macro_rules! assert_no_parse {
|
||||
($string:expr) => ({
|
||||
let result: Result<_, _> = parse_accept($string).into();
|
||||
if result.is_ok() { panic!("{:?} parsed unexpectedly.", $string) }
|
||||
});
|
||||
}
|
||||
|
||||
macro_rules! assert_parse {
|
||||
($string:expr) => ({
|
||||
match parse_accept($string) {
|
||||
Ok(accept) => accept,
|
||||
Err(e) => panic!("{:?} failed to parse: {}", $string, e)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
macro_rules! assert_parse_eq {
|
||||
($string:expr, [$($mt:expr),*]) => ({
|
||||
let expected = vec![$($mt),*];
|
||||
let result = assert_parse!($string);
|
||||
for (i, wmt) in result.iter().enumerate() {
|
||||
assert_eq!(wmt.media_type(), &expected[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
macro_rules! assert_quality_eq {
|
||||
($string:expr, [$($mt:expr),*]) => ({
|
||||
let expected = vec![$($mt),*];
|
||||
let result = assert_parse!($string);
|
||||
for (i, wmt) in result.iter().enumerate() {
|
||||
assert_eq!(wmt.media_type(), &expected[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_does_parse() {
|
||||
assert_parse!("text/html");
|
||||
assert_parse!("*/*, a/b; q=1.0; v=1, application/something, a/b");
|
||||
assert_parse!("a/b, b/c");
|
||||
assert_parse!("text/*");
|
||||
assert_parse!("text/*; q=1");
|
||||
assert_parse!("text/*; q=1; level=2");
|
||||
assert_parse!("audio/*; q=0.2, audio/basic");
|
||||
assert_parse!("text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c");
|
||||
assert_parse!("text/*, text/html, text/html;level=1, */*");
|
||||
assert_parse!("text/*;q=0.3, text/html;q=0.7, text/html;level=1, \
|
||||
text/html;level=2;q=0.4, */*;q=0.5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_parse_eq() {
|
||||
assert_parse_eq!("text/html", [MediaType::HTML]);
|
||||
assert_parse_eq!("text/html, application/json",
|
||||
[MediaType::HTML, MediaType::JSON]);
|
||||
assert_parse_eq!("text/html; charset=utf-8; v=1, application/json",
|
||||
[MediaType::HTML, MediaType::JSON]);
|
||||
assert_parse_eq!("text/html, text/html; q=0.1, text/html; q=0.2",
|
||||
[MediaType::HTML, MediaType::HTML, MediaType::HTML]);
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ fn quoted_string<'a>(input: &mut &'a str) -> ParseResult<&'a str, &'a str> {
|
|||
}
|
||||
|
||||
#[parser]
|
||||
fn media_type<'a>(input: &mut &'a str,
|
||||
pub fn media_type<'a>(input: &mut &'a str,
|
||||
source: &'a str) -> ParseResult<&'a str, MediaType> {
|
||||
let top = take_some_while(|c| is_valid_token(c) && c != '/');
|
||||
eat('/');
|
||||
|
@ -77,8 +77,7 @@ mod test {
|
|||
|
||||
macro_rules! assert_parse {
|
||||
($string:expr) => ({
|
||||
let result: Result<_, _> = parse_media_type($string).into();
|
||||
match result {
|
||||
match parse_media_type($string) {
|
||||
Ok(media_type) => media_type,
|
||||
Err(e) => panic!("{:?} failed to parse: {}", $string, e)
|
||||
}
|
||||
|
@ -90,9 +89,6 @@ mod test {
|
|||
let result = assert_parse!($string);
|
||||
assert_eq!(result, $result);
|
||||
|
||||
let result = assert_parse!($string);
|
||||
assert_eq!(result, $result);
|
||||
|
||||
let expected_params: Vec<(&str, &str)> = vec![$(($k, $v)),*];
|
||||
if expected_params.len() > 0 {
|
||||
assert_eq!(result.params().count(), expected_params.len());
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
mod media_type;
|
||||
mod accept;
|
||||
mod indexed_str;
|
||||
mod checkers;
|
||||
|
||||
pub use self::indexed_str::*;
|
||||
pub use self::media_type::*;
|
||||
pub use self::accept::*;
|
||||
|
|
Loading…
Reference in New Issue