diff --git a/core/codegen/src/decorators/route.rs b/core/codegen/src/decorators/route.rs index dc8921ac..231ae984 100644 --- a/core/codegen/src/decorators/route.rs +++ b/core/codegen/src/decorators/route.rs @@ -29,10 +29,10 @@ fn media_type_to_expr(ecx: &ExtCtxt, ct: Option) -> Option> { let (top, sub) = (ct.top().as_str(), ct.sub().as_str()); quote_expr!(ecx, ::rocket::http::MediaType { source: ::rocket::http::Source::None, - top: ::rocket::http::IndexedStr::Concrete( + top: ::rocket::http::Indexed::Concrete( ::std::borrow::Cow::Borrowed($top) ), - sub: ::rocket::http::IndexedStr::Concrete( + sub: ::rocket::http::Indexed::Concrete( ::std::borrow::Cow::Borrowed($sub) ), params: ::rocket::http::MediaParams::Static(&[]) diff --git a/core/lib/Cargo.toml b/core/lib/Cargo.toml index beab2a9a..bc3983d6 100644 --- a/core/lib/Cargo.toml +++ b/core/lib/Cargo.toml @@ -29,8 +29,7 @@ time = "0.1" memchr = "2" base64 = "0.9" smallvec = "0.6" -pear = { git = "http://github.com/SergioBenitez/pear", rev = "a96a7fd" } -pear_codegen = { git = "http://github.com/SergioBenitez/pear", rev = "a96a7fd" } +pear = { git = "http://github.com/SergioBenitez/Pear", rev = "54667ae" } rustls = { version = "0.12.0", optional = true } hyper = { version = "0.10.13", default-features = false } indexmap = "1.0" diff --git a/core/lib/src/config/toml_ext.rs b/core/lib/src/config/toml_ext.rs index d5cc29da..d5e4a1bf 100644 --- a/core/lib/src/config/toml_ext.rs +++ b/core/lib/src/config/toml_ext.rs @@ -1,9 +1,9 @@ use std::fmt; -use std::collections::BTreeMap; +use std::result::Result as StdResult; use config::Value; -use pear::{ParseResult, ParseError}; +use pear::{Result, parser, switch}; use pear::parsers::*; use pear::combinators::*; @@ -30,43 +30,37 @@ fn is_ident_char(byte: char) -> bool { } #[parser] -fn array<'a>(input: &mut &'a str) -> ParseResult<&'a str, Value> { - let array = (eat('['), collect!(value(), eat(',')), eat(']')).1; - Value::Array(array) +fn array<'a>(input: &mut &'a str) -> Result { + Value::Array(collection('[', value, ',', ']')?) } #[parser] -fn key<'a>(input: &mut &'a str) -> ParseResult<&'a str, String> { - take_some_while(is_ident_char).to_string() +fn key<'a>(input: &mut &'a str) -> Result { + take_some_while(is_ident_char)?.to_string() } #[parser] -fn table<'a>(input: &mut &'a str) -> ParseResult<&'a str, Value> { - eat('{'); - - let mut values = BTreeMap::new(); - try_repeat_while!(eat(','), { - let key = surrounded(key, is_whitespace); - (eat('='), skip_while(is_whitespace)); - values.insert(key, value()) - }); - - eat('}'); - Value::Table(values) +fn key_value<'a>(input: &mut &'a str) -> Result<(String, Value), &'a str> { + let key = (surrounded(key, is_whitespace)?, eat('=')?).0.to_string(); + (key, surrounded(value, is_whitespace)?) } #[parser] -fn value<'a>(input: &mut &'a str) -> ParseResult<&'a str, Value> { - skip_while(is_whitespace); +fn table<'a>(input: &mut &'a str) -> Result { + Value::Table(collection('{', key_value, ',', '}')?) +} + +#[parser] +fn value<'a>(input: &mut &'a str) -> Result { + skip_while(is_whitespace)?; let val = switch! { eat_slice("true") => Value::Boolean(true), eat_slice("false") => Value::Boolean(false), - peek('{') => table(), - peek('[') => array(), - peek('"') => Value::String(delimited('"', |_| true, '"').to_string()), + peek('{') => table()?, + peek('[') => array()?, + peek('"') => Value::String(delimited('"', |_| true, '"')?.to_string()), _ => { - let value_str = take_some_while(is_not_separator); - // FIXME: Silence warning from pear. + let value_str = take_some_while(is_not_separator)?; if let Ok(int) = value_str.parse::() { Value::Integer(int) } else if let Ok(float) = value_str.parse::() { @@ -77,15 +71,12 @@ fn value<'a>(input: &mut &'a str) -> ParseResult<&'a str, Value> { } }; - skip_while(is_whitespace); + skip_while(is_whitespace)?; val } -pub fn parse_simple_toml_value(mut input: &str) -> Result { - let result: Result> = - parse!(&mut input, (value(), eof()).0).into(); - - result.map_err(|e| e.to_string()) +pub fn parse_simple_toml_value(mut input: &str) -> StdResult { + parse!(value: &mut input).map_err(|e| e.to_string()) } /// A simple wrapper over a `Value` reference with a custom implementation of diff --git a/core/lib/src/http/accept.rs b/core/lib/src/http/accept.rs index 11e1b465..afb97ef4 100644 --- a/core/lib/src/http/accept.rs +++ b/core/lib/src/http/accept.rs @@ -85,6 +85,21 @@ pub enum AcceptParams { Dynamic(SmallVec<[QMediaType; 1]>) } +impl ::pear::parsers::Collection for AcceptParams { + type Item = QMediaType; + + fn new() -> Self { + AcceptParams::Dynamic(SmallVec::new()) + } + + fn add(&mut self, item: Self::Item) { + match *self { + AcceptParams::Static(..) => panic!("can't add to static collection!"), + AcceptParams::Dynamic(ref mut v) => v.push(item) + } + } +} + impl PartialEq for AcceptParams { fn eq(&self, other: &AcceptParams) -> bool { #[inline(always)] @@ -143,7 +158,7 @@ impl PartialEq for AcceptParams { /// let response = Response::build().header(Accept::JSON).finalize(); /// ``` #[derive(Debug, Clone, PartialEq)] -pub struct Accept(AcceptParams); +pub struct Accept(pub(crate) AcceptParams); macro_rules! accept_constructor { ($($name:ident ($check:ident): $str:expr, $t:expr, diff --git a/core/lib/src/http/media_type.rs b/core/lib/src/http/media_type.rs index 7fd3b8ac..7dadfacb 100644 --- a/core/lib/src/http/media_type.rs +++ b/core/lib/src/http/media_type.rs @@ -5,21 +5,36 @@ use std::hash::{Hash, Hasher}; use ext::IntoCollection; use http::uncased::{uncased_eq, UncasedStr}; -use http::parse::{IndexedStr, parse_media_type}; +use http::parse::{Indexed, IndexedString, parse_media_type}; use smallvec::SmallVec; #[derive(Debug, Clone)] struct MediaParam { - key: IndexedStr, - value: IndexedStr, + key: IndexedString, + value: IndexedString, } // FIXME: `Static` is needed for `const` items. Need `const SmallVec::new`. #[derive(Debug, Clone)] pub enum MediaParams { - Static(&'static [(IndexedStr, IndexedStr)]), - Dynamic(SmallVec<[(IndexedStr, IndexedStr); 2]>) + Static(&'static [(IndexedString, IndexedString)]), + Dynamic(SmallVec<[(IndexedString, IndexedString); 2]>) +} + +impl ::pear::parsers::Collection for MediaParams { + type Item = (IndexedString, IndexedString); + + fn new() -> Self { + MediaParams::Dynamic(SmallVec::new()) + } + + fn add(&mut self, item: Self::Item) { + match *self { + MediaParams::Static(..) => panic!("can't add to static collection!"), + MediaParams::Dynamic(ref mut v) => v.push(item) + } + } } #[derive(Debug, Clone, PartialEq, Eq)] @@ -90,17 +105,17 @@ pub struct MediaType { pub source: Source, /// The top-level type. #[doc(hidden)] - pub top: IndexedStr, + pub top: IndexedString, /// The subtype. #[doc(hidden)] - pub sub: IndexedStr, + pub sub: IndexedString, /// The parameters, if any. #[doc(hidden)] pub params: MediaParams } macro_rules! media_str { - ($string:expr) => (IndexedStr::Concrete(Cow::Borrowed($string))) + ($string:expr) => (Indexed::Concrete(Cow::Borrowed($string))) } macro_rules! media_types { @@ -276,8 +291,8 @@ impl MediaType { { MediaType { source: Source::None, - top: IndexedStr::Concrete(top.into()), - sub: IndexedStr::Concrete(sub.into()), + top: Indexed::Concrete(top.into()), + sub: Indexed::Concrete(sub.into()), params: MediaParams::Static(&[]), } } @@ -313,14 +328,14 @@ impl MediaType { P: IntoCollection<(K, V)> { let params = ps.mapped(|(key, val)| ( - IndexedStr::Concrete(key.into()), - IndexedStr::Concrete(val.into()) + Indexed::Concrete(key.into()), + Indexed::Concrete(val.into()) )); MediaType { source: Source::None, - top: IndexedStr::Concrete(top.into()), - sub: IndexedStr::Concrete(sub.into()), + top: Indexed::Concrete(top.into()), + sub: Indexed::Concrete(sub.into()), params: MediaParams::Dynamic(params) } } @@ -344,7 +359,7 @@ impl MediaType { /// ``` #[inline] pub fn top(&self) -> &UncasedStr { - self.top.to_str(self.source.as_str()).into() + self.top.from_source(self.source.as_str()).into() } /// Returns the subtype for this media type. The return type, @@ -362,7 +377,7 @@ impl MediaType { /// ``` #[inline] pub fn sub(&self) -> &UncasedStr { - self.sub.to_str(self.source.as_str()).into() + self.sub.from_source(self.source.as_str()).into() } /// Returns a `u8` representing how specific the top-level type and subtype @@ -471,7 +486,7 @@ impl MediaType { param_slice.iter() .map(move |&(ref key, ref val)| { let source_str = self.source.as_str(); - (key.to_str(source_str), val.to_str(source_str)) + (key.from_source(source_str), val.from_source(source_str)) }) } diff --git a/core/lib/src/http/mod.rs b/core/lib/src/http/mod.rs index 89bc9ebc..b9bc05eb 100644 --- a/core/lib/src/http/mod.rs +++ b/core/lib/src/http/mod.rs @@ -24,7 +24,7 @@ pub(crate) mod parse; // We need to export these for codegen, but otherwise it's unnecessary. // TODO: Expose a `const fn` from ContentType when possible. (see RFC#1817) pub mod uncased; -#[doc(hidden)] pub use self::parse::IndexedStr; +#[doc(hidden)] pub use self::parse::Indexed; #[doc(hidden)] pub use self::media_type::{MediaParams, Source}; pub use self::method::Method; diff --git a/core/lib/src/http/parse/accept.rs b/core/lib/src/http/parse/accept.rs index 0519e828..023faefd 100644 --- a/core/lib/src/http/parse/accept.rs +++ b/core/lib/src/http/parse/accept.rs @@ -1,37 +1,34 @@ -use pear::{ParseResult, ParseError}; +use pear::parser; use pear::parsers::*; use http::parse::checkers::is_whitespace; use http::parse::media_type::media_type; -use http::{MediaType, Accept, QMediaType}; +use http::{Accept, QMediaType}; +use http::parse::{Input, Result}; -fn q<'a>(_: &'a str, media_type: &MediaType) -> ParseResult<&'a str, Option> { - match media_type.params().next() { +#[parser] +fn weighted_media_type<'a>(input: &mut Input<'a>) -> Result<'a, QMediaType> { + let media_type = media_type()?; + let weight = match media_type.params().next() { Some(("q", value)) if value.len() <= 5 => match value.parse::().ok() { - Some(q) if q > 1. => ParseError::custom("accept", "q value must be <= 1"), - Some(q) if q < 0. => ParseError::custom("accept", "q value must be > 0"), - Some(q) => ParseResult::Done(Some(q)), - None => ParseError::custom("accept", "q value must be float") + Some(q) if q > 1. => return Err(pear_error!("q value must be <= 1")), + Some(q) if q < 0. => return Err(pear_error!("q value must be > 0")), + Some(q) => Some(q), + None => return Err(pear_error!("invalid media-type weight")) }, - _ => ParseResult::Done(None) - } + _ => None + }; + + QMediaType(media_type, weight) } #[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(); - let weight = q(&media_type); - media_types.push(QMediaType(media_type, weight)); - }); - - Accept::new(media_types) +fn accept<'a>(input: &mut Input<'a>) -> Result<'a, Accept> { + Accept(series(false, ',', is_whitespace, weighted_media_type)?) } -pub fn parse_accept(mut input: &str) -> Result> { - parse!(&mut input, (accept(), eof()).0).into() +pub fn parse_accept(input: &str) -> Result { + parse!(accept: &mut input.into()) } #[cfg(test)] diff --git a/core/lib/src/http/parse/indexed.rs b/core/lib/src/http/parse/indexed.rs new file mode 100644 index 00000000..43dedd6c --- /dev/null +++ b/core/lib/src/http/parse/indexed.rs @@ -0,0 +1,321 @@ +#![allow(dead_code)] + +use std::borrow::Cow; +use std::ops::{Index, Range}; +use std::fmt::{self, Debug}; + +use pear::{Input, Length}; + +pub type IndexedString = Indexed<'static, str>; + +pub trait AsPtr { + fn as_ptr(&self) -> *const u8; + // unsafe fn from_raw<'a>(raw: *const u8, length: usize) -> &T; +} + +impl AsPtr for str { + #[inline(always)] + fn as_ptr(&self) -> *const u8 { + str::as_ptr(self) + } +} + +impl AsPtr for [u8] { + #[inline(always)] + fn as_ptr(&self) -> *const u8 { + <[u8]>::as_ptr(self) + } +} + + +#[derive(PartialEq)] +pub enum Indexed<'a, T: ?Sized + ToOwned + 'a> { + Indexed(usize, usize), + Concrete(Cow<'a, T>) +} + +impl<'a, T: ?Sized + ToOwned + 'a, C: Into>> From for Indexed<'a, T> { + #[inline(always)] + fn from(value: C) -> Indexed<'a, T> { + Indexed::Concrete(value.into()) + } +} + +impl<'a, T: ?Sized + ToOwned + 'a> Indexed<'a, T> { + #[inline(always)] + pub unsafe fn coerce(self) -> Indexed<'a, U> { + match self { + Indexed::Indexed(a, b) => Indexed::Indexed(a, b), + _ => panic!("cannot convert indexed T to U unless indexed") + } + } +} + +impl<'a, T: ?Sized + ToOwned + 'a> Indexed<'a, T> { + #[inline(always)] + pub unsafe fn coerce_lifetime<'b>(self) -> Indexed<'b, T> { + match self { + Indexed::Indexed(a, b) => Indexed::Indexed(a, b), + _ => panic!("cannot coerce lifetime unless indexed") + } + } +} + +use std::ops::Add; + +impl<'a, T: ?Sized + ToOwned + 'a> Add for Indexed<'a, T> { + type Output = Indexed<'a, T>; + + #[inline] + fn add(self, other: Indexed<'a, T>) -> Indexed<'a, T> { + match self { + Indexed::Indexed(a, b) => match other { + Indexed::Indexed(c, d) if b == c && a < d => Indexed::Indexed(a, d), + _ => panic!("+ requires indexed") + } + _ => panic!("+ requires indexed") + } + } +} + +impl<'a, T: ?Sized + ToOwned + 'a> Indexed<'a, T> + where T: Length + AsPtr + Index, Output = T> +{ + // Returns `None` if `needle` is not a substring of `haystack`. + pub fn checked_from(needle: &T, haystack: &T) -> Option> { + let haystack_start = haystack.as_ptr() as usize; + let needle_start = needle.as_ptr() as usize; + + if needle_start < haystack_start { + return None; + } + + if (needle_start + needle.len()) > (haystack_start + haystack.len()) { + return None; + } + + let start = needle_start - haystack_start; + let end = start + needle.len(); + Some(Indexed::Indexed(start, end)) + } + + // Caller must ensure that `needle` is a substring of `haystack`. + pub unsafe fn unchecked_from(needle: &T, haystack: &T) -> Indexed<'a, T> { + let haystack_start = haystack.as_ptr() as usize; + let needle_start = needle.as_ptr() as usize; + + let start = needle_start - haystack_start; + let end = start + needle.len(); + Indexed::Indexed(start, end) + } + + /// Whether this string is derived from indexes or not. + #[inline] + pub fn is_indexed(&self) -> bool { + match *self { + Indexed::Indexed(..) => true, + Indexed::Concrete(..) => false, + } + } + + /// Whether this string is derived from indexes or not. + #[inline] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Retrieves the string `self` corresponds to. If `self` is derived from + /// indexes, the corresponding subslice of `source` is returned. Otherwise, + /// the concrete string is returned. + /// + /// # Panics + /// + /// Panics if `self` is an indexed string and `string` is None. + // pub fn to_source(&self, source: Option<&'a T>) -> &T { + pub fn from_cow_source<'s>(&'s self, source: &'s Option>) -> &'s T { + if self.is_indexed() && source.is_none() { + panic!("Cannot convert indexed str to str without base string!") + } + + match *self { + Indexed::Indexed(i, j) => &source.as_ref().unwrap()[i..j], + Indexed::Concrete(ref mstr) => mstr.as_ref(), + } + } + + /// Retrieves the string `self` corresponds to. If `self` is derived from + /// indexes, the corresponding subslice of `string` is returned. Otherwise, + /// the concrete string is returned. + /// + /// # Panics + /// + /// Panics if `self` is an indexed string and `string` is None. + pub fn from_source<'s>(&'s self, source: Option<&'s T>) -> &'s T { + if self.is_indexed() && source.is_none() { + panic!("Cannot convert indexed str to str without base string!") + } + + match *self { + Indexed::Indexed(i, j) => &source.unwrap()[(i as usize)..(j as usize)], + Indexed::Concrete(ref mstr) => &*mstr, + } + } +} + +impl<'a, T: ToOwned + ?Sized + 'a> Clone for Indexed<'a, T> { + fn clone(&self) -> Self { + match *self { + Indexed::Indexed(a, b) => Indexed::Indexed(a, b), + Indexed::Concrete(ref cow) => Indexed::Concrete(cow.clone()) + } + } +} + +impl<'a, T: ?Sized + 'a> Debug for Indexed<'a, T> + where T: ToOwned + Debug, T::Owned: Debug +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Indexed::Indexed(a, b) => fmt::Debug::fmt(&(a, b), f), + Indexed::Concrete(ref cow) => fmt::Debug::fmt(cow, f), + } + } +} + +impl<'a, T: ?Sized + Length + ToOwned + 'a> Length for Indexed<'a, T> { + #[inline(always)] + fn len(&self) -> usize { + match *self { + Indexed::Indexed(a, b) => (b - a) as usize, + Indexed::Concrete(ref cow) => cow.len() + } + } +} + +#[derive(Debug)] +pub struct IndexedInput<'a, T: ?Sized + 'a> { + source: &'a T, + current: &'a T +} + +impl<'a, T: ?Sized + 'a> IndexedInput<'a, T> { + #[inline(always)] + pub fn source(&self) -> &T { + self.source + } +} + +impl<'a, T: ToOwned + ?Sized + 'a> IndexedInput<'a, T> { + #[inline(always)] + pub fn cow_source(&self) -> Cow<'a, T> { + Cow::Borrowed(self.source) + } +} + +impl<'a> IndexedInput<'a, [u8]> { + pub fn backtrack(&mut self, n: usize) -> ::pear::Result<(), Self> { + let source_addr = self.source.as_ptr() as usize; + let current_addr = self.current.as_ptr() as usize; + if current_addr > n && (current_addr - n) >= source_addr { + let size = self.current.len() + n; + let addr = (current_addr - n) as *const u8; + self.current = unsafe { ::std::slice::from_raw_parts(addr, size) }; + Ok(()) + } else { + let diag = format!("({}, {:x} in {:x})", n, current_addr, source_addr); + Err(pear_error!([backtrack; self] "internal error: {}", diag)) + } + } + + pub fn len(&self) -> usize { + self.source.len() + } +} + +macro_rules! impl_indexed_input { + ($T:ty, token = $token:ty) => ( + impl<'a> From<&'a $T> for IndexedInput<'a, $T> { + #[inline(always)] + fn from(source: &'a $T) -> Self { + IndexedInput { source: source, current: source } + } + } + + impl<'a> Input for IndexedInput<'a, $T> { + type Token = $token; + type InSlice = &'a $T; + type Slice = Indexed<'static, $T>; + type Many = Indexed<'static, $T>; + type Context = Context; + + #[inline(always)] + fn peek(&mut self) -> Option { + self.current.peek() + } + + #[inline(always)] + fn peek_slice(&mut self, slice: Self::InSlice) -> Option { + self.current.peek_slice(slice) + .map(|slice| unsafe { + Indexed::unchecked_from(slice, self.source) + }) + } + + #[inline(always)] + fn skip_many(&mut self, cond: F) -> usize + where F: FnMut(Self::Token) -> bool + { + self.current.skip_many(cond) + } + + #[inline(always)] + fn take_many(&mut self, cond: F) -> Self::Many + where F: FnMut(Self::Token) -> bool + { + let many = self.current.take_many(cond); + unsafe { Indexed::unchecked_from(many, self.source) } + } + + #[inline(always)] + fn advance(&mut self, count: usize) { + self.current.advance(count) + } + + #[inline(always)] + fn is_empty(&mut self) -> bool { + self.current.is_empty() + } + + fn context(&mut self) -> Option { + let offset = self.source.len() - self.current.len(); + let bytes: &[u8] = self.current.as_ref(); + let string = String::from_utf8(bytes.into()).ok()?; + Some(Context { offset, string }) + } + } + ) +} + +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub struct Context { + pub offset: usize, + pub string: String +} + +impl ::std::fmt::Display for Context { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + const LIMIT: usize = 7; + write!(f, "{}", self.offset)?; + + if self.string.len() > LIMIT { + write!(f, " ({}..)", &self.string[..LIMIT]) + } else if !self.string.is_empty() { + write!(f, " ({})", &self.string) + } else { + Ok(()) + } + } +} + +impl_indexed_input!([u8], token = u8); +impl_indexed_input!(str, token = char); diff --git a/core/lib/src/http/parse/indexed_str.rs b/core/lib/src/http/parse/indexed_str.rs deleted file mode 100644 index cef612b7..00000000 --- a/core/lib/src/http/parse/indexed_str.rs +++ /dev/null @@ -1,54 +0,0 @@ -use std::borrow::Cow; - -type Index = u32; - -#[derive(Debug, Clone)] -pub enum IndexedStr { - Indexed(Index, Index), - Concrete(Cow<'static, str>) -} - -impl IndexedStr { - /// Whether this string is derived from indexes or not. - pub fn is_indexed(&self) -> bool { - match *self { - IndexedStr::Indexed(..) => true, - IndexedStr::Concrete(..) => false, - } - } - - /// Retrieves the string `self` corresponds to. If `self` is derived from - /// indexes, the corresponding subslice of `string` is returned. Otherwise, - /// the concrete string is returned. - /// - /// # Panics - /// - /// Panics if `self` is an indexed string and `string` is None. - pub fn to_str<'a>(&'a self, string: Option<&'a str>) -> &'a str { - if self.is_indexed() && string.is_none() { - panic!("Cannot convert indexed str to str without base string!") - } - - match *self { - IndexedStr::Indexed(i, j) => &string.unwrap()[(i as usize)..(j as usize)], - IndexedStr::Concrete(ref mstr) => &*mstr, - } - } - - pub fn from(needle: &str, haystack: &str) -> Option { - let haystack_start = haystack.as_ptr() as usize; - let needle_start = needle.as_ptr() as usize; - - if needle_start < haystack_start { - return None; - } - - if (needle_start + needle.len()) > (haystack_start + haystack.len()) { - return None; - } - - let start = needle_start - haystack_start; - let end = start + needle.len(); - Some(IndexedStr::Indexed(start as Index, end as Index)) - } -} diff --git a/core/lib/src/http/parse/media_type.rs b/core/lib/src/http/parse/media_type.rs index f938ed0f..0f14870d 100644 --- a/core/lib/src/http/parse/media_type.rs +++ b/core/lib/src/http/parse/media_type.rs @@ -1,65 +1,59 @@ use std::borrow::Cow; -use pear::{ParseError, ParseResult}; +use pear::{parser, switch}; use pear::parsers::*; -use pear::combinators::*; -use smallvec::SmallVec; -use http::{MediaType, Source, MediaParams}; +use http::{MediaType, Source}; use http::parse::checkers::{is_whitespace, is_valid_token}; -use http::parse::IndexedStr; +use http::parse::{Input, Slice, Result}; #[parser] -fn quoted_string<'a>(input: &mut &'a str) -> ParseResult<&'a str, &'a str> { - eat('"'); +fn quoted_string<'a>(input: &mut Input<'a>) -> Result<'a, Slice<'a>> { + eat('"')?; let mut is_escaped = false; let inner = take_while(|c| { if is_escaped { is_escaped = false; return true; } if c == '\\' { is_escaped = true; return true; } c != '"' - }); + })?; - eat('"'); + eat('"')?; inner } #[parser] -pub fn media_type<'a>(input: &mut &'a str) -> ParseResult<&'a str, MediaType> { - let source: &str = *input; +fn media_param<'a>(input: &mut Input<'a>) -> Result<'a, (Slice<'a>, Slice<'a>)> { + let key = (take_some_while_until(is_valid_token, '=')?, eat('=')?).0; + let value = switch! { + peek('"') => quoted_string()?, + _ => take_some_while_until(is_valid_token, ';')? + }; - let top = take_some_while(|c| is_valid_token(c) && c != '/'); - eat('/'); - let sub = take_some_while(is_valid_token); + (key, value) +} - let mut params = SmallVec::new(); - switch_repeat! { - surrounded(|i| eat(i, ';'), is_whitespace) => { - let key = take_some_while(|c| is_valid_token(c) && c != '='); - eat('='); +#[parser] +pub fn media_type<'a>(input: &mut Input<'a>) -> Result<'a, MediaType> { + // FIXME: Explain the coerce safety. + let (top, sub, params) = unsafe { + let top = (take_some_while_until(is_valid_token, '/')?, eat('/')?).0; + let sub = take_some_while_until(is_valid_token, ';')?; + let params = series(true, ';', is_whitespace, |i| { + media_param(i).map(|(k, v)| (k.coerce_lifetime(), v.coerce_lifetime())) + })?; - let value = switch! { - peek('"') => quoted_string(), - _ => take_some_while(|c| is_valid_token(c) && c != ';') - }; - - let indexed_key = IndexedStr::from(key, source).expect("key"); - let indexed_val = IndexedStr::from(value, source).expect("val"); - params.push((indexed_key, indexed_val)) - }, - _ => break - } + (top.coerce_lifetime(), sub.coerce_lifetime(), params) + }; MediaType { - source: Source::Custom(Cow::Owned(source.to_string())), - top: IndexedStr::from(top, source).expect("top in source"), - sub: IndexedStr::from(sub, source).expect("sub in source"), - params: MediaParams::Dynamic(params) + source: Source::Custom(Cow::Owned(input.source().to_string())), + top, sub, params } } -pub fn parse_media_type(mut input: &str) -> Result> { - parse!(&mut input, (media_type(), eof()).0).into() +pub fn parse_media_type(input: &str) -> Result { + parse!(media_type: &mut input.into()) } #[cfg(test)] diff --git a/core/lib/src/http/parse/mod.rs b/core/lib/src/http/parse/mod.rs index 7ef09082..a160f529 100644 --- a/core/lib/src/http/parse/mod.rs +++ b/core/lib/src/http/parse/mod.rs @@ -1,8 +1,12 @@ mod media_type; mod accept; -mod indexed_str; +mod indexed; mod checkers; -pub use self::indexed_str::*; +pub use self::indexed::*; pub use self::media_type::*; pub use self::accept::*; + +pub type Input<'a> = IndexedInput<'a, str>; +pub type Slice<'a> = Indexed<'a, str>; +pub type Result<'a, T> = ::pear::Result>; diff --git a/core/lib/src/lib.rs b/core/lib/src/lib.rs index 355d3d47..539ef217 100644 --- a/core/lib/src/lib.rs +++ b/core/lib/src/lib.rs @@ -4,9 +4,10 @@ #![feature(try_trait)] #![feature(fnbox)] #![feature(never_type)] -#![recursion_limit="256"] +#![feature(proc_macro)] +#![feature(proc_macro_non_items)] -#![plugin(pear_codegen)] +#![recursion_limit="256"] // TODO: Version URLs. #![doc(html_root_url = "https://api.rocket.rs")]