Skip to content

Commit 38d297b

Browse files
committed
refactor(headers): Use header!() macro for 3 headers with a "*" value
`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`
1 parent 8f1c829 commit 38d297b

File tree

4 files changed

+113
-58
lines changed

4 files changed

+113
-58
lines changed

src/header/common/if_match.rs

Lines changed: 24 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,31 @@
1-
use header::{EntityTag, Header, HeaderFormat};
2-
use header::parsing::{from_comma_delimited, fmt_comma_delimited, from_one_raw_str};
3-
use std::fmt;
1+
use header::EntityTag;
42

5-
/// The `If-Match` header
6-
///
7-
/// The `If-Match` request-header field is used with a method to make
8-
/// it conditional. The client provides a list of entity tags, and
9-
/// the request is only executed if one of those tags matches the
10-
/// current entity.
11-
///
12-
/// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.24
13-
#[derive(Clone, PartialEq, Debug)]
14-
pub enum IfMatch {
15-
/// This corresponds to '*'.
16-
Any,
17-
/// The header field names which will influence the response representation.
18-
EntityTags(Vec<EntityTag>)
19-
}
20-
21-
impl Header for IfMatch {
22-
fn header_name() -> &'static str {
23-
"If-Match"
24-
}
25-
26-
fn parse_header(raw: &[Vec<u8>]) -> Option<IfMatch> {
27-
from_one_raw_str(raw).and_then(|s: String| {
28-
let slice = &s[..];
29-
match slice {
30-
"" => None,
31-
"*" => Some(IfMatch::Any),
32-
_ => from_comma_delimited(raw).map(IfMatch::EntityTags),
33-
}
34-
})
35-
}
36-
}
37-
38-
impl HeaderFormat for IfMatch {
39-
fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
40-
match *self {
41-
IfMatch::Any => write!(fmt, "*"),
42-
IfMatch::EntityTags(ref fields) => fmt_comma_delimited(fmt, &fields[..])
43-
}
44-
}
3+
header! {
4+
#[doc="`If-Match` header, defined in"]
5+
#[doc="[RFC7232](https://tools.ietf.org/html/rfc7232#section-3.1)"]
6+
#[doc=""]
7+
#[doc="The `If-Match` header field makes the request method conditional on"]
8+
#[doc="the recipient origin server either having at least one current"]
9+
#[doc="representation of the target resource, when the field-value is \"*\","]
10+
#[doc="or having a current representation of the target resource that has an"]
11+
#[doc="entity-tag matching a member of the list of entity-tags provided in"]
12+
#[doc="the field-value."]
13+
#[doc=""]
14+
#[doc="An origin server MUST use the strong comparison function when"]
15+
#[doc="comparing entity-tags for `If-Match`, since the client"]
16+
#[doc="intends this precondition to prevent the method from being applied if"]
17+
#[doc="there have been any changes to the representation data."]
18+
#[doc=""]
19+
#[doc="# ABNF"]
20+
#[doc="```plain"]
21+
#[doc="If-Match = \"*\" / 1#entity-tag"]
22+
#[doc="```"]
23+
(IfMatch, "If-Match") => {Any / (EntityTag)+}
4524
}
4625

4726
#[test]
4827
fn test_parse_header() {
28+
use header::Header;
4929
{
5030
let a: IfMatch = Header::parse_header(
5131
[b"*".to_vec()].as_ref()).unwrap();
@@ -54,7 +34,7 @@ fn test_parse_header() {
5434
{
5535
let a: IfMatch = Header::parse_header(
5636
[b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\"".to_vec()].as_ref()).unwrap();
57-
let b = IfMatch::EntityTags(
37+
let b = IfMatch::Items(
5838
vec![EntityTag::new(false, "xyzzy".to_string()),
5939
EntityTag::new(false, "r2d2xxxx".to_string()),
6040
EntityTag::new(false, "c3piozzzz".to_string())]);

src/header/common/if_none_match.rs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,28 @@
1-
use header::{Header, HeaderFormat, EntityTag};
2-
use header::parsing::{from_comma_delimited, fmt_comma_delimited, from_one_raw_str};
3-
use std::fmt::{self};
1+
use header::EntityTag;
42

5-
/// The `If-None-Match` header defined by HTTP/1.1.
3+
header! {
4+
#[doc="`If-None-Match` header, defined in"]
5+
#[doc="[RFC7232](https://tools.ietf.org/html/rfc7232#section-3.2)"]
6+
#[doc=""]
7+
#[doc="The `If-None-Match` header field makes the request method conditional"]
8+
#[doc="on a recipient cache or origin server either not having any current"]
9+
#[doc="representation of the target resource, when the field-value is \"*\","]
10+
#[doc="or having a selected representation with an entity-tag that does not"]
11+
#[doc="match any of those listed in the field-value."]
12+
#[doc=""]
13+
#[doc="A recipient MUST use the weak comparison function when comparing"]
14+
#[doc="entity-tags for If-None-Match (Section 2.3.2), since weak entity-tags"]
15+
#[doc="can be used for cache validation even if there have been changes to"]
16+
#[doc="the representation data."]
17+
#[doc=""]
18+
#[doc="# ABNF"]
19+
#[doc="```plain"]
20+
#[doc="If-None-Match = \"*\" / 1#entity-tag"]
21+
#[doc="```"]
22+
(IfNoneMatch, "If-None-Match") => {Any / (EntityTag)+}
23+
}
24+
25+
/*/// The `If-None-Match` header defined by HTTP/1.1.
626
///
727
/// The "If-None-Match" header field makes the request method conditional
828
/// on a recipient cache or origin server either not having any current
@@ -50,7 +70,7 @@ impl HeaderFormat for IfNoneMatch {
5070
IfNoneMatch::EntityTags(ref fields) => { fmt_comma_delimited(fmt, &fields[..]) }
5171
}
5272
}
53-
}
73+
}*/
5474

5575
#[cfg(test)]
5676
mod tests {
@@ -71,7 +91,7 @@ mod tests {
7191
let weak_etag = EntityTag::new(true, "weak-etag".to_string());
7292
entities.push(foobar_etag);
7393
entities.push(weak_etag);
74-
assert_eq!(if_none_match, Some(IfNoneMatch::EntityTags(entities)));
94+
assert_eq!(if_none_match, Some(IfNoneMatch::Items(entities)));
7595
}
7696
}
7797

src/header/common/mod.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,47 @@ macro_rules! header {
173173
}
174174
}
175175
};
176+
// List header, one or more items with "*" option
177+
($(#[$a:meta])*($id:ident, $n:expr) => {Any / ($item:ty)+}) => {
178+
$(#[$a])*
179+
#[derive(Clone, Debug, PartialEq)]
180+
pub enum $id {
181+
/// Any value is a match
182+
Any,
183+
/// Only the listed items are a match
184+
Items(Vec<$item>),
185+
}
186+
impl $crate::header::Header for $id {
187+
fn header_name() -> &'static str {
188+
$n
189+
}
190+
fn parse_header(raw: &[Vec<u8>]) -> Option<Self> {
191+
// FIXME: Return None if no item is in $id::Only
192+
if raw.len() == 1 {
193+
if raw[0] == b"*" {
194+
return Some($id::Any)
195+
} else if raw[0] == b"" {
196+
return None
197+
}
198+
}
199+
$crate::header::parsing::from_comma_delimited(raw).map(|vec| $id::Items(vec))
200+
}
201+
}
202+
impl $crate::header::HeaderFormat for $id {
203+
fn fmt_header(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
204+
match *self {
205+
$id::Any => write!(f, "*"),
206+
$id::Items(ref fields) => $crate::header::parsing::fmt_comma_delimited(f, &fields[..])
207+
}
208+
}
209+
}
210+
impl ::std::fmt::Display for $id {
211+
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
212+
use $crate::header::HeaderFormat;
213+
self.fmt_header(f)
214+
}
215+
}
216+
};
176217
}
177218

178219
mod access_control;

src/header/common/vary.rs

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
1-
use header::{Header, HeaderFormat};
2-
use std::fmt::{self};
3-
use header::parsing::{from_comma_delimited, fmt_comma_delimited, from_one_raw_str};
41
use unicase::UniCase;
52

6-
/// The `Allow` header.
3+
header! {
4+
#[doc="`Vary` header, defined in [RFC7231](https://tools.ietf.org/html/rfc7231#section-7.1.4)"]
5+
#[doc=""]
6+
#[doc="The \"Vary\" header field in a response describes what parts of a"]
7+
#[doc="request message, aside from the method, Host header field, and"]
8+
#[doc="request target, might influence the origin server's process for"]
9+
#[doc="selecting and representing this response. The value consists of"]
10+
#[doc="either a single asterisk (\"*\") or a list of header field names"]
11+
#[doc="(case-insensitive)."]
12+
#[doc=""]
13+
#[doc="# ABNF"]
14+
#[doc="```plain"]
15+
#[doc="Vary = \"*\" / 1#field-name"]
16+
#[doc="```"]
17+
(Vary, "Vary") => {Any / (UniCase<String>)+}
18+
}
19+
20+
/*/// The `Allow` header.
721
/// See also https://tools.ietf.org/html/rfc7231#section-7.1.4
822
923
#[derive(Clone, PartialEq, Debug)]
@@ -38,7 +52,7 @@ impl HeaderFormat for Vary {
3852
Vary::Headers(ref fields) => { fmt_comma_delimited(fmt, &fields[..]) }
3953
}
4054
}
41-
}
55+
}*/
4256

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

5569
vary = Header::parse_header([b"etag,cookie,allow".to_vec()].as_ref());
56-
assert_eq!(vary, Some(Vary::Headers(vec!["eTag".parse().unwrap(),
57-
"cookIE".parse().unwrap(),
58-
"AlLOw".parse().unwrap(),])));
70+
assert_eq!(vary, Some(Vary::Items(vec!["eTag".parse().unwrap(),
71+
"cookIE".parse().unwrap(),
72+
"AlLOw".parse().unwrap(),])));
5973
}
6074
}

0 commit comments

Comments
 (0)