Allow shorthand for route format specifiers.

This commit is contained in:
Sergio Benitez 2018-03-22 04:02:37 -05:00
parent 29d56900c6
commit 362f0ccdac
14 changed files with 331 additions and 84 deletions

View File

@ -23,6 +23,7 @@ fn method_to_path(ecx: &ExtCtxt, method: Method) -> Path {
}) })
} }
// FIXME: I think we also want the attributes here.
fn media_type_to_expr(ecx: &ExtCtxt, ct: Option<MediaType>) -> Option<P<Expr>> { fn media_type_to_expr(ecx: &ExtCtxt, ct: Option<MediaType>) -> Option<P<Expr>> {
ct.map(|ct| { ct.map(|ct| {
let (top, sub) = (ct.top().as_str(), ct.sub().as_str()); let (top, sub) = (ct.top().as_str(), ct.sub().as_str());

View File

@ -258,7 +258,7 @@ fn parse_rank(ecx: &ExtCtxt, kv: &KVSpanned<LitKind>) -> isize {
fn parse_format(ecx: &ExtCtxt, kv: &KVSpanned<LitKind>) -> MediaType { fn parse_format(ecx: &ExtCtxt, kv: &KVSpanned<LitKind>) -> MediaType {
if let LitKind::Str(ref s, _) = *kv.value() { if let LitKind::Str(ref s, _) = *kv.value() {
if let Ok(ct) = MediaType::from_str(&s.as_str()) { if let Some(ct) = MediaType::parse_flexible(&s.as_str()) {
if !ct.is_known() { if !ct.is_known() {
let msg = format!("'{}' is not a known media type", s); let msg = format!("'{}' is not a known media type", s);
ecx.span_warn(kv.value.span, &msg); ecx.span_warn(kv.value.span, &msg);
@ -273,7 +273,8 @@ fn parse_format(ecx: &ExtCtxt, kv: &KVSpanned<LitKind>) -> MediaType {
ecx.struct_span_err(kv.span, r#"`format` must be a "media/type""#) ecx.struct_span_err(kv.span, r#"`format` must be a "media/type""#)
.help(r#"format, if specified, must be a key-value pair where .help(r#"format, if specified, must be a key-value pair where
the key is `format` and the value is a string representing the the key is `format` and the value is a string representing the
media type accepted. e.g: format = "application/json""#) media type accepted. e.g: format = "application/json".
shorthand is also accepted: format = "json"#)
.emit(); .emit();
MediaType::Any MediaType::Any

View File

@ -1,3 +1,5 @@
// must-compile-successfully
#![feature(plugin, decl_macro)] #![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)] #![plugin(rocket_codegen)]
@ -12,7 +14,4 @@ fn two() -> &'static str { "hi" }
#[get("/", format = "x-custom/x-custom")] //~ WARNING not a known media type #[get("/", format = "x-custom/x-custom")] //~ WARNING not a known media type
fn three() -> &'static str { "hi" } fn three() -> &'static str { "hi" }
// Make the test fail here so we can actually check for the warnings above.
assert!(false);
fn main() { } fn main() { }

View File

@ -30,4 +30,4 @@ error[E0277]: the trait bound `S: rocket::http::uri::FromUriParam<_>` is not sat
error: aborting due to 4 previous errors error: aborting due to 4 previous errors
If you want more information on this error, try using "rustc --explain E0277" For more information about this error, try `rustc --explain E0277`.

View File

@ -19,8 +19,9 @@ struct Person {
// In a `GET` request and all other non-payload supporting request types, the // In a `GET` request and all other non-payload supporting request types, the
// preferred media type in the Accept header is matched against the `format` in // preferred media type in the Accept header is matched against the `format` in
// the route attribute. // the route attribute. Note: if this was a real application, we'd use
#[get("/<name>/<age>", format = "application/json")] // `rocket_contrib`'s built-in JSON support and return a `JsonValue` instead.
#[get("/<name>/<age>", format = "json")]
fn get_hello(name: String, age: u8) -> content::Json<String> { fn get_hello(name: String, age: u8) -> content::Json<String> {
// In a real application, we'd use the JSON contrib type. // In a real application, we'd use the JSON contrib type.
let person = Person { name: name, age: age, }; let person = Person { name: name, age: age, };
@ -29,7 +30,7 @@ fn get_hello(name: String, age: u8) -> content::Json<String> {
// In a `POST` request and all other payload supporting request types, the // In a `POST` request and all other payload supporting request types, the
// content type is matched against the `format` in the route attribute. // content type is matched against the `format` in the route attribute.
#[post("/<age>", format = "text/plain", data = "<name>")] #[post("/<age>", format = "plain", data = "<name>")]
fn post_hello(age: u8, name: String) -> content::Json<String> { fn post_hello(age: u8, name: String) -> content::Json<String> {
let person = Person { name: name, age: age, }; let person = Person { name: name, age: age, };
content::Json(serde_json::to_string(&person).unwrap()) content::Json(serde_json::to_string(&person).unwrap())

View File

@ -25,7 +25,7 @@ struct Message {
} }
// TODO: This example can be improved by using `route` with multiple HTTP verbs. // TODO: This example can be improved by using `route` with multiple HTTP verbs.
#[post("/<id>", format = "application/json", data = "<message>")] #[post("/<id>", format = "json", data = "<message>")]
fn new(id: ID, message: Json<Message>, map: State<MessageMap>) -> JsonValue { fn new(id: ID, message: Json<Message>, map: State<MessageMap>) -> JsonValue {
let mut hashmap = map.lock().expect("map lock."); let mut hashmap = map.lock().expect("map lock.");
if hashmap.contains_key(&id) { if hashmap.contains_key(&id) {
@ -39,7 +39,7 @@ fn new(id: ID, message: Json<Message>, map: State<MessageMap>) -> JsonValue {
} }
} }
#[put("/<id>", format = "application/json", data = "<message>")] #[put("/<id>", format = "json", data = "<message>")]
fn update(id: ID, message: Json<Message>, map: State<MessageMap>) -> Option<JsonValue> { fn update(id: ID, message: Json<Message>, map: State<MessageMap>) -> Option<JsonValue> {
let mut hashmap = map.lock().unwrap(); let mut hashmap = map.lock().unwrap();
if hashmap.contains_key(&id) { if hashmap.contains_key(&id) {
@ -50,7 +50,7 @@ fn update(id: ID, message: Json<Message>, map: State<MessageMap>) -> Option<Json
} }
} }
#[get("/<id>", format = "application/json")] #[get("/<id>", format = "json")]
fn get(id: ID, map: State<MessageMap>) -> Option<Json<Message>> { fn get(id: ID, map: State<MessageMap>) -> Option<Json<Message>> {
let hashmap = map.lock().unwrap(); let hashmap = map.lock().unwrap();
hashmap.get(&id).map(|contents| { hashmap.get(&id).map(|contents| {

View File

@ -15,7 +15,7 @@ struct Message {
contents: String contents: String
} }
#[get("/<id>", format = "application/msgpack")] #[get("/<id>", format = "msgpack")]
fn get(id: usize) -> MsgPack<Message> { fn get(id: usize) -> MsgPack<Message> {
MsgPack(Message { MsgPack(Message {
id: id, id: id,
@ -23,7 +23,7 @@ fn get(id: usize) -> MsgPack<Message> {
}) })
} }
#[post("/", data = "<data>", format = "application/msgpack")] #[post("/", data = "<data>", format = "msgpack")]
fn create(data: MsgPack<Message>) -> Result<String, ()> { fn create(data: MsgPack<Message>) -> Result<String, ()> {
Ok(data.into_inner().contents) Ok(data.into_inner().contents)
} }

View File

@ -8,7 +8,7 @@ extern crate rocket;
use std::io; use std::io;
use rocket::Data; use rocket::Data;
#[post("/upload", format = "text/plain", data = "<data>")] #[post("/upload", format = "plain", data = "<data>")]
fn upload(data: Data) -> io::Result<String> { fn upload(data: Data) -> io::Result<String> {
data.stream_to_file("/tmp/upload.txt").map(|n| n.to_string()) data.stream_to_file("/tmp/upload.txt").map(|n| n.to_string())
} }

76
lib/src/docify.rs Normal file
View File

@ -0,0 +1,76 @@
macro_rules! docify {
([$($doc:tt)*]; $($tt:tt)*) => {
docify!([$($doc)*] [] $($tt)*);
};
// FIXME: Treat $a just like everywhere else. What if we start with @[]?
([$a:tt $($b:tt)*] [] $($tt:tt)*) => {
docify!([$($b)*] [stringify!($a), " "] $($tt)*);
};
([@fence @$lang:tt $($b:tt)*] [$($c:tt)+] $($tt:tt)*) => {
docify!([$($b)*] [$($c)+, "\n\n```", stringify!($lang), "\n"] $($tt)*);
};
([@fence $($b:tt)*] [$($c:tt)+] $($tt:tt)*) => {
docify!([$($b)*] [$($c)+, "\n\n```\n"] $($tt)*);
};
([@{$($a:tt),*}! $($b:tt)*] [$($c:tt)+] $($tt:tt)*) => {
docify!([$($b)*] [$($c)+, $($a),*] $($tt)*);
};
([@{$($a:tt),*} $($b:tt)*] [$($c:tt)+] $($tt:tt)*) => {
docify!([$($b)*] [$($c)+, $($a),*, " "] $($tt)*);
};
([@code{$($a:tt)+}! $($b:tt)*] [$($c:tt)+] $($tt:tt)*) => {
docify!([$($b)*] [$($c)+, "`", $(stringify!($a)),*, "`"] $($tt)*);
};
([@code{$($a:tt)+} $($b:tt)*] [$($c:tt)+] $($tt:tt)*) => {
docify!([$($b)*] [$($c)+, "`", $(stringify!($a)),*, "` "] $($tt)*);
};
([@[$($a:tt)*]! $($b:tt)*] [$($c:tt)+] $($tt:tt)*) => {
docify!([$($b)*] [$($c)+, $(stringify!($a)),*] $($tt)*);
};
([@[$($a:tt)*] $($b:tt)*] [$($c:tt)+] $($tt:tt)*) => {
docify!([$($b)*] [$($c)+, $(stringify!($a)),*, " "] $($tt)*);
};
([@nl $($b:tt)*] [$($c:tt)+] $($tt:tt)*) => {
docify!([$($b)*] [$($c)+, "\n"] $($tt)*);
};
(@punct [$a:tt $p:tt $($b:tt)*] [$($c:tt)+] $($tt:tt)*) => {
docify!([$($b)*] [$($c)+, stringify!($a), stringify!($p), " "] $($tt)*);
};
(@upunct [$a:tt $p:tt $($b:tt)*] [$($c:tt)+] $($tt:tt)*) => {
docify!([$($b)*] [$($c)+, stringify!($a), stringify!($p)] $($tt)*);
};
([$a:tt . $($b:tt)*] $($rest:tt)+) => { docify!(@punct [$a . $($b)*] $($rest)+); };
([$a:tt , $($b:tt)*] $($rest:tt)+) => { docify!(@punct [$a , $($b)*] $($rest)+); };
([$a:tt ; $($b:tt)*] $($rest:tt)+) => { docify!(@punct [$a ; $($b)*] $($rest)+); };
([$a:tt : $($b:tt)*] $($rest:tt)+) => { docify!(@punct [$a : $($b)*] $($rest)+); };
([$a:tt ! $($b:tt)*] $($rest:tt)+) => { docify!(@punct [$a ! $($b)*] $($rest)+); };
([$a:tt ! $($b:tt)*] $($rest:tt)+) => { docify!(@punct [$a ! $($b)*] $($rest)+); };
([$a:tt :: $($b:tt)*] $($rest:tt)+) => { docify!(@upunct [$a :: $($b)*] $($rest)+); };
([$a:tt $($b:tt)*] [$($c:tt)+] $($tt:tt)*) => {
docify!([$($b)*] [$($c)+, stringify!($a), " "] $($tt)*);
};
([] [$($doc:expr),*] $($tt:tt)*) => {
docify!(concat!($($doc),*), $($tt)*);
};
($x:expr, $($tt:tt)*) => {
#[doc = $x]
$($tt)*
};
}

View File

@ -45,15 +45,119 @@ pub struct ContentType(pub MediaType);
macro_rules! content_types { macro_rules! content_types {
($($name:ident ($check:ident): $str:expr, $t:expr, ($($name:ident ($check:ident): $str:expr, $t:expr,
$s:expr $(; $k:expr => $v:expr)*,)+) => { $s:expr $(; $k:expr => $v:expr)*,)+) => {
$( $(
#[doc="Content-Type for <b>"] #[doc=$str] #[doc="</b>: <i>"] docify!([
#[doc=$t] #[doc="/"] #[doc=$s] Content Type for @{"**"}! @{$str}! @{"**"}!: @{"`"} @{$t}! @[/]! @{$s}!
$(#[doc="; "] #[doc=$k] #[doc=" = "] #[doc=$v])* $(; @{$k}! @[=]! @{$v}!)* @{"`"}!.
#[doc="</i>"] ];
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
pub const $name: ContentType = ContentType(MediaType::$name); pub const $name: ContentType = ContentType(MediaType::$name);
)+ );
}; )+
}}
macro_rules! from_extension {
($($ext:expr => $name:ident,)*) => (
docify!([
Returns the @[Content-Type] associated with the extension @code{ext}.
Not all extensions are recognized. If an extensions is not recognized,
@code{None} is returned. The currently recognized extensions are:
@nl
$(* @{$ext} - @{"`ContentType::"}! @[$name]! @{"`"} @nl)*
@nl
This list is likely to grow. Extensions are matched
@[case-insensitively.]
];
/// # Example
///
/// Recognized content types:
///
/// ```rust
/// use rocket::http::ContentType;
///
/// let xml = ContentType::from_extension("xml");
/// assert_eq!(xml, Some(ContentType::XML));
///
/// let xml = ContentType::from_extension("XML");
/// assert_eq!(xml, Some(ContentType::XML));
/// ```
///
/// An unrecognized content type:
///
/// ```rust
/// use rocket::http::ContentType;
///
/// let foo = ContentType::from_extension("foo");
/// assert!(foo.is_none());
/// ```
#[inline]
pub fn from_extension(ext: &str) -> Option<ContentType> {
MediaType::from_extension(ext).map(ContentType)
}
);)
}
macro_rules! parse_flexible {
($($short:expr => $name:ident,)*) => (
docify!([
Flexibly parses @code{name} into a @code{ContentType}. The parse is
@[_flexible_] because, in addition to stricly correct content types, it
recognizes the following shorthands:
@nl
$(* $short - @{"`ContentType::"}! @[$name]! @{"`"} @nl)*
@nl
];
/// For regular parsing, use the
/// [`ContentType::from_str()`](#impl-FromStr) method.
///
/// # Example
///
/// Using a shorthand:
///
/// ```rust
/// use rocket::http::ContentType;
///
/// let html = ContentType::parse_flexible("html");
/// assert_eq!(html, Some(ContentType::HTML));
///
/// let json = ContentType::parse_flexible("json");
/// assert_eq!(json, Some(ContentType::JSON));
/// ```
///
/// Using the full content-type:
///
/// ```rust
/// use rocket::http::ContentType;
///
/// let html = ContentType::parse_flexible("text/html; charset=utf-8");
/// assert_eq!(html, Some(ContentType::HTML));
///
/// let json = ContentType::parse_flexible("application/json");
/// assert_eq!(json, Some(ContentType::JSON));
///
/// let custom = ContentType::parse_flexible("application/x+custom");
/// assert_eq!(custom, Some(ContentType::new("application", "x+custom")));
/// ```
///
/// An unrecognized content-type:
///
/// ```rust
/// use rocket::http::ContentType;
///
/// let foo = ContentType::parse_flexible("foo");
/// assert_eq!(foo, None);
///
/// let bar = ContentType::parse_flexible("foo/bar/baz");
/// assert_eq!(bar, None);
/// ```
#[inline]
pub fn parse_flexible(name: &str) -> Option<ContentType> {
MediaType::parse_flexible(name).map(ContentType)
}
);)
} }
impl ContentType { impl ContentType {
@ -79,39 +183,9 @@ impl ContentType {
ContentType(MediaType::new(top, sub)) ContentType(MediaType::new(top, sub))
} }
/// Returns the Content-Type associated with the extension `ext` if the known_shorthands!(parse_flexible);
/// extension is recognized. Not all extensions are recognized. If an
/// extensions is not recognized, then this method returns `None`. The known_extensions!(from_extension);
/// currently recognized extensions are txt, html, htm, xml, csv, js, css,
/// json, png, gif, bmp, jpeg, jpg, webp, svg, pdf, ttf, otf, woff, and
/// woff2. Extensions are matched case-insensitively.
///
/// # Example
///
/// Recognized content types:
///
/// ```rust
/// use rocket::http::ContentType;
///
/// let xml = ContentType::from_extension("xml");
/// assert_eq!(xml, Some(ContentType::XML));
///
/// let xml = ContentType::from_extension("XML");
/// assert_eq!(xml, Some(ContentType::XML));
/// ```
///
/// An unrecognized content type:
///
/// ```rust
/// use rocket::http::ContentType;
///
/// let foo = ContentType::from_extension("foo");
/// assert!(foo.is_none());
/// ```
#[inline]
pub fn from_extension(ext: &str) -> Option<ContentType> {
MediaType::from_extension(ext).map(ContentType)
}
/// Creates a new `ContentType` with top-level type `top`, subtype `sub`, /// Creates a new `ContentType` with top-level type `top`, subtype `sub`,
/// and parameters `ps`. This should _only_ be used to construct uncommon or /// and parameters `ps`. This should _only_ be used to construct uncommon or

View File

@ -5,7 +5,7 @@ macro_rules! known_media_types {
HTML (is_html): "HTML", "text", "html" ; "charset" => "utf-8", HTML (is_html): "HTML", "text", "html" ; "charset" => "utf-8",
Plain (is_plain): "plain text", "text", "plain" ; "charset" => "utf-8", Plain (is_plain): "plain text", "text", "plain" ; "charset" => "utf-8",
JSON (is_json): "JSON", "application", "json", JSON (is_json): "JSON", "application", "json",
MsgPack (is_msgpack): "MessagePack", "application", "msgpack", MsgPack (is_msgpack): "MsgPack", "application", "msgpack",
Form (is_form): "forms", "application", "x-www-form-urlencoded", Form (is_form): "forms", "application", "x-www-form-urlencoded",
JavaScript (is_javascript): "JavaScript", "application", "javascript", JavaScript (is_javascript): "JavaScript", "application", "javascript",
CSS (is_css): "CSS", "text", "css" ; "charset" => "utf-8", CSS (is_css): "CSS", "text", "css" ; "charset" => "utf-8",
@ -51,3 +51,19 @@ macro_rules! known_extensions {
"woff2" => WOFF2, "woff2" => WOFF2,
}) })
} }
macro_rules! known_shorthands {
($cont:ident) => ($cont! {
"any" => Any,
"binary" => Binary,
"html" => HTML,
"plain" => Plain,
"json" => JSON,
"msgpack" => MsgPack,
"form" => Form,
"js" => JavaScript,
"css" => CSS,
"multipart" => FormData,
"xml" => XML,
})
}

View File

@ -106,11 +106,11 @@ macro_rules! media_str {
macro_rules! media_types { macro_rules! media_types {
($($name:ident ($check:ident): $str:expr, $t:expr, ($($name:ident ($check:ident): $str:expr, $t:expr,
$s:expr $(; $k:expr => $v:expr)*,)+) => { $s:expr $(; $k:expr => $v:expr)*,)+) => {
$( $(
#[doc="Media type for <b>"] #[doc=$str] #[doc="</b>: <i>"] docify!([
#[doc=$t] #[doc="/"] #[doc=$s] Media Type for @{"**"}! @{$str}! @{"**"}!: @{"`"} @{$t}! @[/]! @{$s}!
$(#[doc="; "] #[doc=$k] #[doc=" = "] #[doc=$v])* $(; @{$k}! @[=]! @{$v}!)* @{"`"}!.
#[doc="</i>"] ];
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
pub const $name: MediaType = MediaType { pub const $name: MediaType = MediaType {
source: Source::Known(concat!($t, "/", $s, $("; ", $k, "=", $v),*)), source: Source::Known(concat!($t, "/", $s, $("; ", $k, "=", $v),*)),
@ -118,34 +118,48 @@ macro_rules! media_types {
sub: media_str!($s), sub: media_str!($s),
params: MediaParams::Static(&[$((media_str!($k), media_str!($v))),*]) params: MediaParams::Static(&[$((media_str!($k), media_str!($v))),*])
}; };
);
)+
#[doc="Returns `true` if `self` is the media type for <b>"] /// Returns `true` if this MediaType is known to Rocket. In other words,
#[doc=$str] /// returns `true` if there is an associated constant for `self`.
#[doc="</b>, "] pub fn is_known(&self) -> bool {
/// without considering parameters. if let Source::Known(_) = self.source {
return true;
}
$(if self.$check() { return true })+
false
}
$(
docify!([
Returns @code{true} if the @[top-level] and sublevel types of
@code{self} are the same as those of @{"`MediaType::"}! $name
@{"`"}!.
];
#[inline(always)] #[inline(always)]
pub fn $check(&self) -> bool { pub fn $check(&self) -> bool {
*self == MediaType::$name *self == MediaType::$name
} }
)+ );
)+
/// Returns `true` if this MediaType is known to Rocket, that is, }}
/// there is an associated constant for `self`.
pub fn is_known(&self) -> bool {
$(if self.$check() { return true })+
false
}
};
}
macro_rules! from_extension { macro_rules! from_extension {
($($ext:expr => $name:ident,)*) => ( ($($ext:expr => $name:ident,)*) => (
/// Returns the Media-Type associated with the extension `ext`. Not all docify!([
/// extensions are recognized. If an extensions is not recognized, Returns the @[Media-Type] associated with the extension @code{ext}. Not
/// `None` is returned. The currently recognized extensions are all extensions are recognized. If an extensions is not recognized,
$(#[doc=$ext]#[doc=","])* @code{None} is returned. The currently recognized extensions are:
/// and is likely to grow. Extensions are matched case-insensitively.
/// @nl
$(* @{$ext} - @{"`MediaType::"}! @[$name]! @{"`"} @nl)*
@nl
This list is likely to grow. Extensions are matched
@[case-insensitively.]
];
/// # Example /// # Example
/// ///
/// Recognized media types: /// Recognized media types:
@ -174,7 +188,70 @@ macro_rules! from_extension {
_ => None _ => None
} }
} }
) );)
}
macro_rules! parse_flexible {
($($short:expr => $name:ident,)*) => (
docify!([
Flexibly parses @code{name} into a @code{MediaType}. The parse is
@[_flexible_] because, in addition to stricly correct media types, it
recognizes the following shorthands:
@nl
$(* $short - @{"`MediaType::"}! @[$name]! @{"`"} @nl)*
@nl
];
/// For regular parsing, use the
/// [`MediaType::from_str()`](#impl-FromStr) method.
///
/// # Example
///
/// Using a shorthand:
///
/// ```rust
/// use rocket::http::MediaType;
///
/// let html = MediaType::parse_flexible("html");
/// assert_eq!(html, Some(MediaType::HTML));
///
/// let json = MediaType::parse_flexible("json");
/// assert_eq!(json, Some(MediaType::JSON));
/// ```
///
/// Using the full media type:
///
/// ```rust
/// use rocket::http::MediaType;
///
/// let html = MediaType::parse_flexible("text/html; charset=utf-8");
/// assert_eq!(html, Some(MediaType::HTML));
///
/// let json = MediaType::parse_flexible("application/json");
/// assert_eq!(json, Some(MediaType::JSON));
///
/// let custom = MediaType::parse_flexible("application/x+custom");
/// assert_eq!(custom, Some(MediaType::new("application", "x+custom")));
/// ```
///
/// An unrecognized media type:
///
/// ```rust
/// use rocket::http::MediaType;
///
/// let foo = MediaType::parse_flexible("foo");
/// assert_eq!(foo, None);
///
/// let bar = MediaType::parse_flexible("foo/bar/baz");
/// assert_eq!(bar, None);
/// ```
pub fn parse_flexible(name: &str) -> Option<MediaType> {
match name {
$(x if uncased_eq(x, $short) => Some(MediaType::$name)),*,
_ => MediaType::from_str(name).ok(),
}
}
);)
} }
impl MediaType { impl MediaType {
@ -240,7 +317,6 @@ impl MediaType {
IndexedStr::Concrete(val.into()) IndexedStr::Concrete(val.into())
)); ));
MediaType { MediaType {
source: Source::None, source: Source::None,
top: IndexedStr::Concrete(top.into()), top: IndexedStr::Concrete(top.into()),
@ -249,6 +325,8 @@ impl MediaType {
} }
} }
known_shorthands!(parse_flexible);
known_extensions!(from_extension); known_extensions!(from_extension);
/// Returns the top-level type for this media type. The return type, /// Returns the top-level type for this media type. The return type,

View File

@ -4,6 +4,7 @@
#![feature(plugin, decl_macro)] #![feature(plugin, decl_macro)]
#![feature(never_type)] #![feature(never_type)]
#![feature(try_trait)] #![feature(try_trait)]
#![recursion_limit="256"]
#![plugin(pear_codegen)] #![plugin(pear_codegen)]
@ -118,6 +119,7 @@ extern crate isatty;
#[cfg(test)] #[macro_use] extern crate lazy_static; #[cfg(test)] #[macro_use] extern crate lazy_static;
#[doc(hidden)] #[macro_use] pub mod logger; #[doc(hidden)] #[macro_use] pub mod logger;
#[macro_use] mod docify;
pub mod local; pub mod local;
pub mod http; pub mod http;
pub mod request; pub mod request;

View File

@ -90,4 +90,3 @@ ctrs! {
Css: CSS, "CSS", "text/css", Css: CSS, "CSS", "text/css",
JavaScript: JavaScript, "JavaScript", "application/javascript" JavaScript: JavaScript, "JavaScript", "application/javascript"
} }