From 38d297b16e5d14d533947988f770f03b49d47a17 Mon Sep 17 00:00:00 2001 From: Pyfisch Date: Thu, 2 Apr 2015 18:41:50 +0200 Subject: [PATCH] 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` --- src/header/common/if_match.rs | 68 +++++++++++------------------- src/header/common/if_none_match.rs | 32 +++++++++++--- src/header/common/mod.rs | 41 ++++++++++++++++++ src/header/common/vary.rs | 30 +++++++++---- 4 files changed, 113 insertions(+), 58 deletions(-) diff --git a/src/header/common/if_match.rs b/src/header/common/if_match.rs index 640bc1c547..58f55955ca 100644 --- a/src/header/common/if_match.rs +++ b/src/header/common/if_match.rs @@ -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) -} - -impl Header for IfMatch { - fn header_name() -> &'static str { - "If-Match" - } - - fn parse_header(raw: &[Vec]) -> Option { - 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(); @@ -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())]); diff --git a/src/header/common/if_none_match.rs b/src/header/common/if_none_match.rs index 6e0c941f40..edc8c6c3a8 100644 --- a/src/header/common/if_none_match.rs +++ b/src/header/common/if_none_match.rs @@ -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 @@ -50,7 +70,7 @@ impl HeaderFormat for IfNoneMatch { IfNoneMatch::EntityTags(ref fields) => { fmt_comma_delimited(fmt, &fields[..]) } } } -} +}*/ #[cfg(test)] mod tests { @@ -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))); } } diff --git a/src/header/common/mod.rs b/src/header/common/mod.rs index dd57a0a557..0f301d3868 100644 --- a/src/header/common/mod.rs +++ b/src/header/common/mod.rs @@ -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]) -> Option { + // 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; diff --git a/src/header/common/vary.rs b/src/header/common/vary.rs index b6a7e84a4e..a49aebedbd 100644 --- a/src/header/common/vary.rs +++ b/src/header/common/vary.rs @@ -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)+} +} + +/*/// The `Allow` header. /// See also https://tools.ietf.org/html/rfc7231#section-7.1.4 #[derive(Clone, PartialEq, Debug)] @@ -38,7 +52,7 @@ impl HeaderFormat for Vary { Vary::Headers(ref fields) => { fmt_comma_delimited(fmt, &fields[..]) } } } -} +}*/ #[cfg(test)] mod tests { @@ -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(),]))); } }