Skip to content

Commit

Permalink
refactor(headers): Use header!() macro for 3 headers with a "*" value
Browse files Browse the repository at this point in the history
`If-Match`, `If-None-Match` and `Vary` headers are either a "*" value meaning that the header
matches every possible item or a list of items, one of them must be matched to fulfil the condition.

BREAKING CHANGE: `If-Match`, `If-None-Match` and `Vary` item variant name changed to `Items`
  • Loading branch information
pyfisch committed Apr 6, 2015
1 parent 8f1c829 commit 38d297b
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 58 deletions.
68 changes: 24 additions & 44 deletions src/header/common/if_match.rs
Original file line number Diff line number Diff line change
@@ -1,51 +1,31 @@
use header::{EntityTag, Header, HeaderFormat};
use header::parsing::{from_comma_delimited, fmt_comma_delimited, from_one_raw_str};
use std::fmt;
use header::EntityTag;

/// The `If-Match` header
///
/// The `If-Match` request-header field is used with a method to make
/// it conditional. The client provides a list of entity tags, and
/// the request is only executed if one of those tags matches the
/// current entity.
///
/// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.24
#[derive(Clone, PartialEq, Debug)]
pub enum IfMatch {
/// This corresponds to '*'.
Any,
/// The header field names which will influence the response representation.
EntityTags(Vec<EntityTag>)
}

impl Header for IfMatch {
fn header_name() -> &'static str {
"If-Match"
}

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[..])
}
}
header! {
#[doc="`If-Match` header, defined in"]
#[doc="[RFC7232](https://tools.ietf.org/html/rfc7232#section-3.1)"]
#[doc=""]
#[doc="The `If-Match` header field makes the request method conditional on"]
#[doc="the recipient origin server either having at least one current"]
#[doc="representation of the target resource, when the field-value is \"*\","]
#[doc="or having a current representation of the target resource that has an"]
#[doc="entity-tag matching a member of the list of entity-tags provided in"]
#[doc="the field-value."]
#[doc=""]
#[doc="An origin server MUST use the strong comparison function when"]
#[doc="comparing entity-tags for `If-Match`, since the client"]
#[doc="intends this precondition to prevent the method from being applied if"]
#[doc="there have been any changes to the representation data."]
#[doc=""]
#[doc="# ABNF"]
#[doc="```plain"]
#[doc="If-Match = \"*\" / 1#entity-tag"]
#[doc="```"]
(IfMatch, "If-Match") => {Any / (EntityTag)+}
}

#[test]
fn test_parse_header() {
use header::Header;
{
let a: IfMatch = Header::parse_header(
[b"*".to_vec()].as_ref()).unwrap();
Expand All @@ -54,7 +34,7 @@ fn test_parse_header() {
{
let a: IfMatch = Header::parse_header(
[b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\"".to_vec()].as_ref()).unwrap();
let b = IfMatch::EntityTags(
let b = IfMatch::Items(
vec![EntityTag::new(false, "xyzzy".to_string()),
EntityTag::new(false, "r2d2xxxx".to_string()),
EntityTag::new(false, "c3piozzzz".to_string())]);
Expand Down
32 changes: 26 additions & 6 deletions src/header/common/if_none_match.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
use header::{Header, HeaderFormat, EntityTag};
use header::parsing::{from_comma_delimited, fmt_comma_delimited, from_one_raw_str};
use std::fmt::{self};
use header::EntityTag;

/// 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
/// on a recipient cache or origin server either not having any current
Expand Down Expand Up @@ -50,7 +70,7 @@ impl HeaderFormat for IfNoneMatch {
IfNoneMatch::EntityTags(ref fields) => { fmt_comma_delimited(fmt, &fields[..]) }
}
}
}
}*/

#[cfg(test)]
mod tests {
Expand All @@ -71,7 +91,7 @@ mod tests {
let weak_etag = EntityTag::new(true, "weak-etag".to_string());
entities.push(foobar_etag);
entities.push(weak_etag);
assert_eq!(if_none_match, Some(IfNoneMatch::EntityTags(entities)));
assert_eq!(if_none_match, Some(IfNoneMatch::Items(entities)));
}
}

Expand Down
41 changes: 41 additions & 0 deletions src/header/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
30 changes: 22 additions & 8 deletions src/header/common/vary.rs
Original file line number Diff line number Diff line change
@@ -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;

/// 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
#[derive(Clone, PartialEq, Debug)]
Expand Down Expand Up @@ -38,7 +52,7 @@ impl HeaderFormat for Vary {
Vary::Headers(ref fields) => { fmt_comma_delimited(fmt, &fields[..]) }
}
}
}
}*/

#[cfg(test)]
mod tests {
Expand All @@ -53,8 +67,8 @@ mod tests {
assert_eq!(vary, Some(Vary::Any));

vary = Header::parse_header([b"etag,cookie,allow".to_vec()].as_ref());
assert_eq!(vary, Some(Vary::Headers(vec!["eTag".parse().unwrap(),
"cookIE".parse().unwrap(),
"AlLOw".parse().unwrap(),])));
assert_eq!(vary, Some(Vary::Items(vec!["eTag".parse().unwrap(),
"cookIE".parse().unwrap(),
"AlLOw".parse().unwrap(),])));
}
}

0 comments on commit 38d297b

Please sign in to comment.