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`
This commit is contained in:
		| @@ -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(); | ||||
| @@ -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())]); | ||||
|   | ||||
| @@ -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))); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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)] | ||||
| @@ -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(),]))); | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user