diff --git a/core/codegen/src/http_codegen.rs b/core/codegen/src/http_codegen.rs index 889c63ae..27df7952 100644 --- a/core/codegen/src/http_codegen.rs +++ b/core/codegen/src/http_codegen.rs @@ -70,8 +70,8 @@ impl FromMeta for ContentType { impl ToTokens for ContentType { fn to_tokens(&self, tokens: &mut TokenStream) { - // Yeah, yeah. (((((i))).kn0w())) - let media_type = MediaType((self.0).clone().0); + let http_media_type = self.0.media_type().clone(); + let media_type = MediaType(http_media_type); tokens.extend(quote!(::rocket::http::ContentType(#media_type))); } } @@ -94,27 +94,13 @@ impl FromMeta for MediaType { impl ToTokens for MediaType { fn to_tokens(&self, tokens: &mut TokenStream) { - use std::iter::repeat; let (top, sub) = (self.0.top().as_str(), self.0.sub().as_str()); let (keys, values) = self.0.params().split2(); + let http = quote!(::rocket::http); - let cow = quote!(::std::borrow::Cow); - let (pub_http, http) = (quote!(::rocket::http), quote!(::rocket::http::private)); - let (http_, http__) = (repeat(&http), repeat(&http)); - let (cow_, cow__) = (repeat(&cow), repeat(&cow)); - - // TODO: Produce less code when possible (for known media types). - tokens.extend(quote!(#pub_http::MediaType { - source: #http::Source::None, - top: #http::Indexed::Concrete(#cow::Borrowed(#top)), - sub: #http::Indexed::Concrete(#cow::Borrowed(#sub)), - params: #http::MediaParams::Static(&[ - #(( - #http_::Indexed::Concrete(#cow_::Borrowed(#keys)), - #http__::Indexed::Concrete(#cow__::Borrowed(#values)) - )),* - ]) - })) + tokens.extend(quote! { + #http::MediaType::const_new(#top, #sub, &[#((#keys, #values)),*]) + }); } } diff --git a/core/http/Cargo.toml b/core/http/Cargo.toml index 7ce24ca6..63d6440b 100644 --- a/core/http/Cargo.toml +++ b/core/http/Cargo.toml @@ -35,6 +35,7 @@ log = "0.4" ref-cast = "1.0" uncased = "0.9" parking_lot = "0.11" +either = "1" [dependencies.cookie] git = "https://github.com/SergioBenitez/cookie-rs.git" diff --git a/core/http/src/accept.rs b/core/http/src/accept.rs index 59e2b2fd..a6082386 100644 --- a/core/http/src/accept.rs +++ b/core/http/src/accept.rs @@ -3,120 +3,12 @@ use std::str::FromStr; use std::fmt; use smallvec::SmallVec; +use either::Either; use crate::{Header, MediaType}; use crate::ext::IntoCollection; use crate::parse::parse_accept; -/// A `MediaType` with an associated quality value. -#[derive(Debug, Clone, PartialEq)] -pub struct QMediaType(pub MediaType, pub Option); - -impl QMediaType { - /// Retrieve the weight of the media type, if there is any. - /// - /// # Example - /// - /// ```rust - /// # extern crate rocket; - /// use rocket::http::{MediaType, QMediaType}; - /// - /// let q_type = QMediaType(MediaType::HTML, Some(0.3)); - /// assert_eq!(q_type.weight(), Some(0.3)); - /// ``` - #[inline(always)] - pub fn weight(&self) -> Option { - self.1 - } - - /// Retrieve the weight of the media type or a given default value. - /// - /// # Example - /// - /// ```rust - /// # extern crate rocket; - /// use rocket::http::{MediaType, QMediaType}; - /// - /// let q_type = QMediaType(MediaType::HTML, Some(0.3)); - /// assert_eq!(q_type.weight_or(0.9), 0.3); - /// - /// let q_type = QMediaType(MediaType::HTML, None); - /// assert_eq!(q_type.weight_or(0.9), 0.9); - /// ``` - #[inline(always)] - pub fn weight_or(&self, default: f32) -> f32 { - self.1.unwrap_or(default) - } - - /// Borrow the internal `MediaType`. - /// - /// # Example - /// - /// ```rust - /// # extern crate rocket; - /// use rocket::http::{MediaType, QMediaType}; - /// - /// let q_type = QMediaType(MediaType::HTML, Some(0.3)); - /// assert_eq!(q_type.media_type(), &MediaType::HTML); - /// ``` - #[inline(always)] - pub fn media_type(&self) -> &MediaType { - &self.0 - } -} - -impl From for QMediaType { - #[inline(always)] - fn from(media_type: MediaType) -> QMediaType { - QMediaType(media_type, None) - } -} - -impl Deref for QMediaType { - type Target = MediaType; - - #[inline(always)] - fn deref(&self) -> &MediaType { - &self.0 - } -} - -// FIXME: `Static` is needed for `const` items. Need `const SmallVec::new`. -#[derive(Debug, Clone)] -pub enum AcceptParams { - Static(&'static [QMediaType]), - Dynamic(SmallVec<[QMediaType; 1]>) -} - -impl Default for AcceptParams { - fn default() -> Self { - AcceptParams::Dynamic(SmallVec::new()) - } -} - -impl Extend for AcceptParams { - fn extend>(&mut self, iter: T) { - match self { - AcceptParams::Static(..) => panic!("can't add to static collection!"), - AcceptParams::Dynamic(ref mut v) => v.extend(iter) - } - } -} - -impl PartialEq for AcceptParams { - fn eq(&self, other: &AcceptParams) -> bool { - #[inline(always)] - fn inner_types(params: &AcceptParams) -> &[QMediaType] { - match *params { - AcceptParams::Static(params) => params, - AcceptParams::Dynamic(ref vec) => vec, - } - } - - inner_types(self) == inner_types(other) - } -} - /// The HTTP Accept header. /// /// An `Accept` header is composed of zero or more media types, each of which @@ -160,9 +52,20 @@ impl PartialEq for AcceptParams { /// /// let response = Response::build().header(Accept::JSON).finalize(); /// ``` -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct Accept(pub(crate) AcceptParams); +/// A `MediaType` with an associated quality value. +#[derive(Debug, Clone, PartialEq)] +pub struct QMediaType(pub MediaType, pub Option); + +// NOTE: `Static` is needed for `const` items. Need `const SmallVec::new`. +#[derive(Debug, Clone)] +pub enum AcceptParams { + Static(QMediaType), + Dynamic(SmallVec<[QMediaType; 1]>) +} + macro_rules! accept_constructor { ($($name:ident ($check:ident): $str:expr, $t:expr, $s:expr $(; $k:expr => $v:expr)*,)+) => { @@ -173,19 +76,12 @@ macro_rules! accept_constructor { #[doc=""] #[allow(non_upper_case_globals)] pub const $name: Accept = Accept( - AcceptParams::Static(&[QMediaType(MediaType::$name, None)]) + AcceptParams::Static(QMediaType(MediaType::$name, None)) ); )+ }; } -impl> From for Accept { - #[inline(always)] - fn from(items: T) -> Accept { - Accept(AcceptParams::Dynamic(items.mapped(|item| item.into()))) - } -} - impl Accept { /// Constructs a new `Accept` header from one or more media types. /// @@ -314,12 +210,10 @@ impl Accept { /// ``` #[inline(always)] pub fn iter<'a>(&'a self) -> impl Iterator + 'a { - let slice = match self.0 { - AcceptParams::Static(slice) => slice, - AcceptParams::Dynamic(ref vec) => &vec[..], - }; - - slice.iter() + match self.0 { + AcceptParams::Static(ref val) => Either::Left(Some(val).into_iter()), + AcceptParams::Dynamic(ref vec) => Either::Right(vec.iter()) + } } /// Returns an iterator over all of the (bare) media types in `self`. Media @@ -351,6 +245,19 @@ impl Accept { known_media_types!(accept_constructor); } +impl> From for Accept { + #[inline(always)] + fn from(items: T) -> Accept { + Accept(AcceptParams::Dynamic(items.mapped(|item| item.into()))) + } +} + +impl PartialEq for Accept { + fn eq(&self, other: &Accept) -> bool { + self.iter().eq(other.iter()) + } +} + impl fmt::Display for Accept { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for (i, media_type) in self.iter().enumerate() { @@ -384,6 +291,90 @@ impl Into> for Accept { } } +impl QMediaType { + /// Retrieve the weight of the media type, if there is any. + /// + /// # Example + /// + /// ```rust + /// # extern crate rocket; + /// use rocket::http::{MediaType, QMediaType}; + /// + /// let q_type = QMediaType(MediaType::HTML, Some(0.3)); + /// assert_eq!(q_type.weight(), Some(0.3)); + /// ``` + #[inline(always)] + pub fn weight(&self) -> Option { + self.1 + } + + /// Retrieve the weight of the media type or a given default value. + /// + /// # Example + /// + /// ```rust + /// # extern crate rocket; + /// use rocket::http::{MediaType, QMediaType}; + /// + /// let q_type = QMediaType(MediaType::HTML, Some(0.3)); + /// assert_eq!(q_type.weight_or(0.9), 0.3); + /// + /// let q_type = QMediaType(MediaType::HTML, None); + /// assert_eq!(q_type.weight_or(0.9), 0.9); + /// ``` + #[inline(always)] + pub fn weight_or(&self, default: f32) -> f32 { + self.1.unwrap_or(default) + } + + /// Borrow the internal `MediaType`. + /// + /// # Example + /// + /// ```rust + /// # extern crate rocket; + /// use rocket::http::{MediaType, QMediaType}; + /// + /// let q_type = QMediaType(MediaType::HTML, Some(0.3)); + /// assert_eq!(q_type.media_type(), &MediaType::HTML); + /// ``` + #[inline(always)] + pub fn media_type(&self) -> &MediaType { + &self.0 + } +} + +impl From for QMediaType { + #[inline(always)] + fn from(media_type: MediaType) -> QMediaType { + QMediaType(media_type, None) + } +} + +impl Deref for QMediaType { + type Target = MediaType; + + #[inline(always)] + fn deref(&self) -> &MediaType { + &self.0 + } +} + +impl Default for AcceptParams { + fn default() -> Self { + AcceptParams::Dynamic(SmallVec::new()) + } +} + +impl Extend for AcceptParams { + fn extend>(&mut self, iter: T) { + match self { + AcceptParams::Static(..) => panic!("can't add to static collection!"), + AcceptParams::Dynamic(ref mut v) => v.extend(iter) + } + } +} + #[cfg(test)] mod test { use crate::{Accept, MediaType}; diff --git a/core/http/src/lib.rs b/core/http/src/lib.rs index 98be6039..56e8760a 100644 --- a/core/http/src/lib.rs +++ b/core/http/src/lib.rs @@ -49,21 +49,17 @@ pub mod uncased { #[doc(inline)] pub use uncased::*; } +// Types that we expose for use by core. #[doc(hidden)] pub mod private { - // 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 use crate::parse::Indexed; - pub use crate::media_type::{MediaParams, Source}; pub use smallvec::{SmallVec, Array}; - // These we need to expose for core. pub mod cookie { pub use cookie::*; pub use crate::cookies::Key; } - // These as well. pub use crate::listener::{Incoming, Listener, Connection, bind_tcp}; } diff --git a/core/http/src/media_type.rs b/core/http/src/media_type.rs index ac42c01e..27b4c306 100644 --- a/core/http/src/media_type.rs +++ b/core/http/src/media_type.rs @@ -3,72 +3,14 @@ use std::str::FromStr; use std::fmt; use std::hash::{Hash, Hasher}; +use either::Either; + use crate::ext::IntoCollection; use crate::uncased::UncasedStr; use crate::parse::{Indexed, IndexedString, parse_media_type}; use smallvec::SmallVec; -#[derive(Debug, Clone)] -struct MediaParam { - key: IndexedString, - value: IndexedString, -} - -// FIXME: `Static` is needed for `const` items. Need `const SmallVec::new`. -#[derive(Debug, Clone)] -pub enum MediaParams { - Static(&'static [(IndexedString, IndexedString)]), - Dynamic(SmallVec<[(IndexedString, IndexedString); 2]>) -} - -impl Default for MediaParams { - fn default() -> Self { - MediaParams::Dynamic(SmallVec::new()) - } -} - -impl Extend<(IndexedString, IndexedString)> for MediaParams { - fn extend>(&mut self, iter: T) { - match self { - MediaParams::Static(..) => panic!("can't add to static collection!"), - MediaParams::Dynamic(ref mut v) => v.extend(iter) - } - } -} - -impl PartialEq for MediaParams { - fn eq(&self, other: &MediaParams) -> bool { - #[inline(always)] - fn inner_types(params: &MediaParams) -> &[(IndexedString, IndexedString)] { - match *params { - MediaParams::Static(params) => params, - MediaParams::Dynamic(ref vec) => vec, - } - } - - inner_types(self) == inner_types(other) - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Source { - Known(&'static str), - Custom(Cow<'static, str>), - None -} - -impl Source { - #[inline] - fn as_str(&self) -> Option<&str> { - match *self { - Source::Known(s) => Some(s), - Source::Custom(ref s) => Some(s.borrow()), - Source::None => None - } - } -} - /// An HTTP media type. /// /// # Usage @@ -110,21 +52,33 @@ impl Source { #[derive(Debug, Clone)] pub struct MediaType { /// Storage for the entire media type string. - #[doc(hidden)] - pub source: Source, + pub(crate) source: Source, /// The top-level type. - #[doc(hidden)] - pub top: IndexedString, + pub(crate) top: IndexedString, /// The subtype. - #[doc(hidden)] - pub sub: IndexedString, + pub(crate) sub: IndexedString, /// The parameters, if any. - #[doc(hidden)] - pub params: MediaParams + pub(crate) params: MediaParams } -macro_rules! media_str { - ($string:expr) => (Indexed::Concrete(Cow::Borrowed($string))) +#[derive(Debug, Clone)] +struct MediaParam { + key: IndexedString, + value: IndexedString, +} + +// FIXME: `Static` is needed for `const` items. Need `const SmallVec::new`. +#[derive(Debug, Clone)] +pub(crate) enum MediaParams { + Static(&'static [(&'static str, &'static str)]), + Dynamic(SmallVec<[(IndexedString, IndexedString); 2]>) +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum Source { + Known(&'static str), + Custom(Cow<'static, str>), + None } macro_rules! media_types { @@ -136,12 +90,10 @@ macro_rules! media_types { $(; @{$k}! @[=]! @{$v}!)* @{"`"}!. ]; #[allow(non_upper_case_globals)] - pub const $name: MediaType = MediaType { - source: Source::Known(concat!($t, "/", $s, $("; ", $k, "=", $v),*)), - top: media_str!($t), - sub: media_str!($s), - params: MediaParams::Static(&[$((media_str!($k), media_str!($v))),*]) - }; + pub const $name: MediaType = MediaType::new_known( + concat!($t, "/", $s, $("; ", $k, "=", $v),*), + $t, $s, &[$(($k, $v)),*] + ); ); )+ @@ -357,6 +309,50 @@ impl MediaType { } } + /// A `const` variant of [`MediaType::with_params()`]. Creates a new + /// `MediaType` with top-level type `top`, subtype `sub`, and parameters + /// `params`, which may be empty. + /// + /// # Example + /// + /// Create a custom `application/x-person` media type: + /// + /// ```rust + /// use rocket::http::MediaType; + /// + /// let custom = MediaType::const_new("application", "x-person", &[]); + /// assert_eq!(custom.top(), "application"); + /// assert_eq!(custom.sub(), "x-person"); + /// ``` + #[inline] + pub const fn const_new( + top: &'static str, + sub: &'static str, + params: &'static [(&'static str, &'static str)] + ) -> MediaType { + MediaType { + source: Source::None, + top: Indexed::Concrete(Cow::Borrowed(top)), + sub: Indexed::Concrete(Cow::Borrowed(sub)), + params: MediaParams::Static(params), + } + } + + #[inline] + pub(crate) const fn new_known( + source: &'static str, + top: &'static str, + sub: &'static str, + params: &'static [(&'static str, &'static str)] + ) -> MediaType { + MediaType { + source: Source::Known(source), + top: Indexed::Concrete(Cow::Borrowed(top)), + sub: Indexed::Concrete(Cow::Borrowed(sub)), + params: MediaParams::Static(params), + } + } + known_shorthands!(parse_flexible); known_extensions!(from_extension); @@ -501,16 +497,15 @@ impl MediaType { /// ``` #[inline] pub fn params<'a>(&'a self) -> impl Iterator + 'a { - let param_slice = match self.params { - MediaParams::Static(slice) => slice, - MediaParams::Dynamic(ref vec) => &vec[..], - }; - - param_slice.iter() - .map(move |&(ref key, ref val)| { - let source_str = self.source.as_str(); - (key.from_source(source_str), val.from_source(source_str)) - }) + match self.params { + MediaParams::Static(ref slice) => Either::Left(slice.iter().cloned()), + MediaParams::Dynamic(ref vec) => { + Either::Right(vec.iter().map(move |&(ref key, ref val)| { + let source_str = self.source.as_str(); + (key.from_source(source_str), val.from_source(source_str)) + })) + } + } } known_media_types!(media_types); @@ -561,3 +556,29 @@ impl fmt::Display for MediaType { } } } + +impl Default for MediaParams { + fn default() -> Self { + MediaParams::Dynamic(SmallVec::new()) + } +} + +impl Extend<(IndexedString, IndexedString)> for MediaParams { + fn extend>(&mut self, iter: T) { + match self { + MediaParams::Static(..) => panic!("can't add to static collection!"), + MediaParams::Dynamic(ref mut v) => v.extend(iter) + } + } +} + +impl Source { + #[inline] + fn as_str(&self) -> Option<&str> { + match *self { + Source::Known(s) => Some(s), + Source::Custom(ref s) => Some(s.borrow()), + Source::None => None + } + } +} diff --git a/core/http/src/parse/mod.rs b/core/http/src/parse/mod.rs index 90b6e0d5..3c0a1f87 100644 --- a/core/http/src/parse/mod.rs +++ b/core/http/src/parse/mod.rs @@ -8,5 +8,4 @@ pub use self::accept::*; pub mod uri; -// Exposed for codegen. -#[doc(hidden)] pub use self::indexed::*; +pub use self::indexed::*;