Merge pull request #421 from pyfisch/refactorheaders3

refactor(headers): Use header!() macro for 3 headers with a "*" value
This commit is contained in:
Sean McArthur
2015-04-06 13:38:39 -07:00
4 changed files with 113 additions and 58 deletions

View File

@@ -1,51 +1,31 @@
use header::{EntityTag, Header, HeaderFormat}; use header::EntityTag;
use header::parsing::{from_comma_delimited, fmt_comma_delimited, from_one_raw_str};
use std::fmt;
/// The `If-Match` header header! {
/// #[doc="`If-Match` header, defined in"]
/// The `If-Match` request-header field is used with a method to make #[doc="[RFC7232](https://tools.ietf.org/html/rfc7232#section-3.1)"]
/// it conditional. The client provides a list of entity tags, and #[doc=""]
/// the request is only executed if one of those tags matches the #[doc="The `If-Match` header field makes the request method conditional on"]
/// current entity. #[doc="the recipient origin server either having at least one current"]
/// #[doc="representation of the target resource, when the field-value is \"*\","]
/// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.24 #[doc="or having a current representation of the target resource that has an"]
#[derive(Clone, PartialEq, Debug)] #[doc="entity-tag matching a member of the list of entity-tags provided in"]
pub enum IfMatch { #[doc="the field-value."]
/// This corresponds to '*'. #[doc=""]
Any, #[doc="An origin server MUST use the strong comparison function when"]
/// The header field names which will influence the response representation. #[doc="comparing entity-tags for `If-Match`, since the client"]
EntityTags(Vec<EntityTag>) #[doc="intends this precondition to prevent the method from being applied if"]
} #[doc="there have been any changes to the representation data."]
#[doc=""]
impl Header for IfMatch { #[doc="# ABNF"]
fn header_name() -> &'static str { #[doc="```plain"]
"If-Match" #[doc="If-Match = \"*\" / 1#entity-tag"]
} #[doc="```"]
(IfMatch, "If-Match") => {Any / (EntityTag)+}
fn parse_header(raw: &[Vec<u8>]) -> Option<IfMatch> {
from_one_raw_str(raw).and_then(|s: String| {
let slice = &s[..];
match slice {
"" => None,
"*" => Some(IfMatch::Any),
_ => from_comma_delimited(raw).map(IfMatch::EntityTags),
}
})
}
}
impl HeaderFormat for IfMatch {
fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self {
IfMatch::Any => write!(fmt, "*"),
IfMatch::EntityTags(ref fields) => fmt_comma_delimited(fmt, &fields[..])
}
}
} }
#[test] #[test]
fn test_parse_header() { fn test_parse_header() {
use header::Header;
{ {
let a: IfMatch = Header::parse_header( let a: IfMatch = Header::parse_header(
[b"*".to_vec()].as_ref()).unwrap(); [b"*".to_vec()].as_ref()).unwrap();
@@ -54,7 +34,7 @@ fn test_parse_header() {
{ {
let a: IfMatch = Header::parse_header( let a: IfMatch = Header::parse_header(
[b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\"".to_vec()].as_ref()).unwrap(); [b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\"".to_vec()].as_ref()).unwrap();
let b = IfMatch::EntityTags( let b = IfMatch::Items(
vec![EntityTag::new(false, "xyzzy".to_string()), vec![EntityTag::new(false, "xyzzy".to_string()),
EntityTag::new(false, "r2d2xxxx".to_string()), EntityTag::new(false, "r2d2xxxx".to_string()),
EntityTag::new(false, "c3piozzzz".to_string())]); EntityTag::new(false, "c3piozzzz".to_string())]);

View File

@@ -1,8 +1,28 @@
use header::{Header, HeaderFormat, EntityTag}; use header::EntityTag;
use header::parsing::{from_comma_delimited, fmt_comma_delimited, from_one_raw_str};
use std::fmt::{self};
/// The `If-None-Match` header defined by HTTP/1.1. header! {
#[doc="`If-None-Match` header, defined in"]
#[doc="[RFC7232](https://tools.ietf.org/html/rfc7232#section-3.2)"]
#[doc=""]
#[doc="The `If-None-Match` header field makes the request method conditional"]
#[doc="on a recipient cache or origin server either not having any current"]
#[doc="representation of the target resource, when the field-value is \"*\","]
#[doc="or having a selected representation with an entity-tag that does not"]
#[doc="match any of those listed in the field-value."]
#[doc=""]
#[doc="A recipient MUST use the weak comparison function when comparing"]
#[doc="entity-tags for If-None-Match (Section 2.3.2), since weak entity-tags"]
#[doc="can be used for cache validation even if there have been changes to"]
#[doc="the representation data."]
#[doc=""]
#[doc="# ABNF"]
#[doc="```plain"]
#[doc="If-None-Match = \"*\" / 1#entity-tag"]
#[doc="```"]
(IfNoneMatch, "If-None-Match") => {Any / (EntityTag)+}
}
/*/// The `If-None-Match` header defined by HTTP/1.1.
/// ///
/// The "If-None-Match" header field makes the request method conditional /// The "If-None-Match" header field makes the request method conditional
/// on a recipient cache or origin server either not having any current /// on a recipient cache or origin server either not having any current
@@ -50,7 +70,7 @@ impl HeaderFormat for IfNoneMatch {
IfNoneMatch::EntityTags(ref fields) => { fmt_comma_delimited(fmt, &fields[..]) } IfNoneMatch::EntityTags(ref fields) => { fmt_comma_delimited(fmt, &fields[..]) }
} }
} }
} }*/
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
@@ -71,7 +91,7 @@ mod tests {
let weak_etag = EntityTag::new(true, "weak-etag".to_string()); let weak_etag = EntityTag::new(true, "weak-etag".to_string());
entities.push(foobar_etag); entities.push(foobar_etag);
entities.push(weak_etag); entities.push(weak_etag);
assert_eq!(if_none_match, Some(IfNoneMatch::EntityTags(entities))); assert_eq!(if_none_match, Some(IfNoneMatch::Items(entities)));
} }
} }

View File

@@ -173,6 +173,47 @@ macro_rules! header {
} }
} }
}; };
// List header, one or more items with "*" option
($(#[$a:meta])*($id:ident, $n:expr) => {Any / ($item:ty)+}) => {
$(#[$a])*
#[derive(Clone, Debug, PartialEq)]
pub enum $id {
/// Any value is a match
Any,
/// Only the listed items are a match
Items(Vec<$item>),
}
impl $crate::header::Header for $id {
fn header_name() -> &'static str {
$n
}
fn parse_header(raw: &[Vec<u8>]) -> Option<Self> {
// FIXME: Return None if no item is in $id::Only
if raw.len() == 1 {
if raw[0] == b"*" {
return Some($id::Any)
} else if raw[0] == b"" {
return None
}
}
$crate::header::parsing::from_comma_delimited(raw).map(|vec| $id::Items(vec))
}
}
impl $crate::header::HeaderFormat for $id {
fn fmt_header(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
match *self {
$id::Any => write!(f, "*"),
$id::Items(ref fields) => $crate::header::parsing::fmt_comma_delimited(f, &fields[..])
}
}
}
impl ::std::fmt::Display for $id {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
use $crate::header::HeaderFormat;
self.fmt_header(f)
}
}
};
} }
mod access_control; mod access_control;

View File

@@ -1,9 +1,23 @@
use header::{Header, HeaderFormat};
use std::fmt::{self};
use header::parsing::{from_comma_delimited, fmt_comma_delimited, from_one_raw_str};
use unicase::UniCase; use unicase::UniCase;
/// The `Allow` header. header! {
#[doc="`Vary` header, defined in [RFC7231](https://tools.ietf.org/html/rfc7231#section-7.1.4)"]
#[doc=""]
#[doc="The \"Vary\" header field in a response describes what parts of a"]
#[doc="request message, aside from the method, Host header field, and"]
#[doc="request target, might influence the origin server's process for"]
#[doc="selecting and representing this response. The value consists of"]
#[doc="either a single asterisk (\"*\") or a list of header field names"]
#[doc="(case-insensitive)."]
#[doc=""]
#[doc="# ABNF"]
#[doc="```plain"]
#[doc="Vary = \"*\" / 1#field-name"]
#[doc="```"]
(Vary, "Vary") => {Any / (UniCase<String>)+}
}
/*/// The `Allow` header.
/// See also https://tools.ietf.org/html/rfc7231#section-7.1.4 /// See also https://tools.ietf.org/html/rfc7231#section-7.1.4
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]
@@ -38,7 +52,7 @@ impl HeaderFormat for Vary {
Vary::Headers(ref fields) => { fmt_comma_delimited(fmt, &fields[..]) } Vary::Headers(ref fields) => { fmt_comma_delimited(fmt, &fields[..]) }
} }
} }
} }*/
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
@@ -53,8 +67,8 @@ mod tests {
assert_eq!(vary, Some(Vary::Any)); assert_eq!(vary, Some(Vary::Any));
vary = Header::parse_header([b"etag,cookie,allow".to_vec()].as_ref()); vary = Header::parse_header([b"etag,cookie,allow".to_vec()].as_ref());
assert_eq!(vary, Some(Vary::Headers(vec!["eTag".parse().unwrap(), assert_eq!(vary, Some(Vary::Items(vec!["eTag".parse().unwrap(),
"cookIE".parse().unwrap(), "cookIE".parse().unwrap(),
"AlLOw".parse().unwrap(),]))); "AlLOw".parse().unwrap(),])));
} }
} }