feat(header): change Cookie to be map-like
				
					
				
			The `Cookie` header now has 'set' and `get` methods, to treat the list of cookies as a map. Closes #1145 BREAKING CHANGE: The `Cookie` header is no longer a wrapper over a `Vec<String>`. It must be accessed via its `get` and `append` methods.
This commit is contained in:
		| @@ -1,7 +1,10 @@ | |||||||
| use header::{Header, Raw}; | use std::borrow::Cow; | ||||||
| use std::fmt::{self, Display}; | use std::fmt; | ||||||
| use std::str::from_utf8; | use std::str::from_utf8; | ||||||
|  |  | ||||||
|  | use header::{Header, Raw}; | ||||||
|  | use header::internals::VecMap; | ||||||
|  |  | ||||||
| /// `Cookie` header, defined in [RFC6265](http://tools.ietf.org/html/rfc6265#section-5.4) | /// `Cookie` header, defined in [RFC6265](http://tools.ietf.org/html/rfc6265#section-5.4) | ||||||
| /// | /// | ||||||
| /// If the user agent does attach a Cookie header field to an HTTP | /// If the user agent does attach a Cookie header field to an HTTP | ||||||
| @@ -20,17 +23,69 @@ use std::str::from_utf8; | |||||||
| /// use hyper::header::{Headers, Cookie}; | /// use hyper::header::{Headers, Cookie}; | ||||||
| /// | /// | ||||||
| /// let mut headers = Headers::new(); | /// let mut headers = Headers::new(); | ||||||
|  | /// let mut cookie = Cookie::new(); | ||||||
|  | /// cookie.append("foo", "bar"); | ||||||
| /// | /// | ||||||
| /// headers.set( | /// assert_eq!(cookie.get("foo"), Some("bar")); | ||||||
| ///    Cookie(vec![ | /// | ||||||
| ///        String::from("foo=bar") | /// headers.set(cookie); | ||||||
| ///    ]) |  | ||||||
| /// ); |  | ||||||
| /// ``` | /// ``` | ||||||
| #[derive(Clone, PartialEq, Debug)] | #[derive(Clone)] | ||||||
| pub struct Cookie(pub Vec<String>); | pub struct Cookie(VecMap<Cow<'static, str>, Cow<'static, str>>); | ||||||
|  |  | ||||||
| __hyper__deref!(Cookie => Vec<String>); | impl Cookie { | ||||||
|  |     /// Creates a new `Cookie` header. | ||||||
|  |     pub fn new() -> Cookie { | ||||||
|  |         Cookie(VecMap::with_capacity(0)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Sets a name and value for the `Cookie`. | ||||||
|  |     /// | ||||||
|  |     /// # Note | ||||||
|  |     ///  | ||||||
|  |     /// This will remove all other instances with the same name, | ||||||
|  |     /// and insert the new value. | ||||||
|  |     pub fn set<K, V>(&mut self, key: K, value: V) | ||||||
|  |     where K: Into<Cow<'static, str>>, | ||||||
|  |           V: Into<Cow<'static, str>>, | ||||||
|  |     { | ||||||
|  |         let key = key.into(); | ||||||
|  |         let value = value.into(); | ||||||
|  |         self.0.remove_all(&key); | ||||||
|  |         self.0.append(key, value); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Append a name and value for the `Cookie`. | ||||||
|  |     /// | ||||||
|  |     /// # Note | ||||||
|  |     /// | ||||||
|  |     /// Cookies are allowed to set a name with a | ||||||
|  |     /// a value multiple times. For example: | ||||||
|  |     /// | ||||||
|  |     /// ``` | ||||||
|  |     /// use hyper::header::Cookie; | ||||||
|  |     /// let mut cookie = Cookie::new(); | ||||||
|  |     /// cookie.append("foo", "bar"); | ||||||
|  |     /// cookie.append("foo", "quux"); | ||||||
|  |     /// assert_eq!(cookie.to_string(), "foo=bar; foo=quux"); | ||||||
|  |     pub fn append<K, V>(&mut self, key: K, value: V) | ||||||
|  |     where K: Into<Cow<'static, str>>, | ||||||
|  |           V: Into<Cow<'static, str>>, | ||||||
|  |     { | ||||||
|  |         self.0.append(key.into(), value.into()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Get a value for the name, if it exists. | ||||||
|  |     /// | ||||||
|  |     /// # Note  | ||||||
|  |     /// | ||||||
|  |     /// Only returns the first instance found. To access | ||||||
|  |     /// any other values associated with the name, parse | ||||||
|  |     /// the `str` representation. | ||||||
|  |     pub fn get(&self, key: &str) -> Option<&str> { | ||||||
|  |         self.0.get(key).map(AsRef::as_ref) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| impl Header for Cookie { | impl Header for Cookie { | ||||||
|     fn header_name() -> &'static str { |     fn header_name() -> &'static str { | ||||||
| @@ -39,16 +94,22 @@ impl Header for Cookie { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn parse_header(raw: &Raw) -> ::Result<Cookie> { |     fn parse_header(raw: &Raw) -> ::Result<Cookie> { | ||||||
|         let mut cookies = Vec::with_capacity(raw.len()); |         let mut vec_map = VecMap::with_capacity(raw.len()); | ||||||
|         for cookies_raw in raw.iter() { |         for cookies_raw in raw.iter() { | ||||||
|             let cookies_str = try!(from_utf8(&cookies_raw[..])); |             let cookies_str = try!(from_utf8(&cookies_raw[..])); | ||||||
|             for cookie_str in cookies_str.split(';') { |             for cookie_str in cookies_str.split(';') { | ||||||
|                 cookies.push(cookie_str.trim().to_owned()) |                 let mut key_val = cookie_str.splitn(2, '='); | ||||||
|  |                 let key_val = (key_val.next(), key_val.next()); | ||||||
|  |                 if let (Some(key), Some(val)) = key_val { | ||||||
|  |                     vec_map.insert(key.trim().to_owned().into(), val.trim().to_owned().into()); | ||||||
|  |                 } else { | ||||||
|  |                     return Err(::Error::Header); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if !cookies.is_empty() { |         if vec_map.len() != 0 { | ||||||
|             Ok(Cookie(cookies)) |             Ok(Cookie(vec_map)) | ||||||
|         } else { |         } else { | ||||||
|             Err(::Error::Header) |             Err(::Error::Header) | ||||||
|         } |         } | ||||||
| @@ -59,16 +120,110 @@ impl Header for Cookie { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | impl PartialEq for Cookie { | ||||||
|  |     fn eq(&self, other: &Cookie) -> bool { | ||||||
|  |         if self.0.len() == other.0.len() { | ||||||
|  |             for &(ref k, ref v) in self.0.iter() { | ||||||
|  |                 if other.get(k) != Some(v) { | ||||||
|  |                     return false; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             true | ||||||
|  |         } else { | ||||||
|  |             false | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl fmt::Debug for Cookie { | ||||||
|  |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|  |         f.debug_map() | ||||||
|  |             .entries(self.0.iter().map(|&(ref k, ref v)| (k, v))) | ||||||
|  |             .finish() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| impl fmt::Display for Cookie { | impl fmt::Display for Cookie { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|         let cookies = &self.0; |         let mut iter = self.0.iter(); | ||||||
|         for (i, cookie) in cookies.iter().enumerate() { |         if let Some(&(ref key, ref val)) = iter.next() { | ||||||
|             if i != 0 { |             try!(write!(f, "{}={}", key, val)); | ||||||
|                 try!(f.write_str("; ")); |         } | ||||||
|             } |         for &(ref key, ref val) in iter { | ||||||
|             try!(Display::fmt(&cookie, f)); |             try!(write!(f, "; {}={}", key, val)); | ||||||
|         } |         } | ||||||
|         Ok(()) |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(test)] | ||||||
|  | mod tests { | ||||||
|  |     use ::header::Header; | ||||||
|  |     use super::Cookie; | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_set_and_get() { | ||||||
|  |         let mut cookie = Cookie::new(); | ||||||
|  |         cookie.append("foo", "bar"); | ||||||
|  |         cookie.append(String::from("dyn"), String::from("amic")); | ||||||
|  |  | ||||||
|  |         assert_eq!(cookie.get("foo"), Some("bar")); | ||||||
|  |         assert_eq!(cookie.get("dyn"), Some("amic")); | ||||||
|  |         assert!(cookie.get("nope").is_none()); | ||||||
|  |  | ||||||
|  |         cookie.append("foo", "notbar"); | ||||||
|  |         assert_eq!(cookie.get("foo"), Some("bar")); | ||||||
|  |  | ||||||
|  |         cookie.set("foo", "hi"); | ||||||
|  |         assert_eq!(cookie.get("foo"), Some("hi")); | ||||||
|  |         assert_eq!(cookie.get("dyn"), Some("amic")); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_eq() { | ||||||
|  |         let mut cookie = Cookie::new(); | ||||||
|  |         let mut cookie2 = Cookie::new(); | ||||||
|  |  | ||||||
|  |         // empty is equal | ||||||
|  |         assert_eq!(cookie, cookie2); | ||||||
|  |  | ||||||
|  |         // left has more params | ||||||
|  |         cookie.append("foo", "bar"); | ||||||
|  |         assert!(cookie != cookie2); | ||||||
|  |  | ||||||
|  |         // same len, different params | ||||||
|  |         cookie2.append("bar", "foo"); | ||||||
|  |         assert!(cookie != cookie2); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         // right has more params, and matching KV | ||||||
|  |         cookie2.append("foo", "bar"); | ||||||
|  |         assert!(cookie != cookie2); | ||||||
|  |  | ||||||
|  |         // same params, different order | ||||||
|  |         cookie.append("bar", "foo"); | ||||||
|  |         assert_eq!(cookie, cookie2); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_parse() { | ||||||
|  |         let mut cookie = Cookie::new(); | ||||||
|  |  | ||||||
|  |         let parsed = Cookie::parse_header(&b"foo=bar".to_vec().into()).unwrap(); | ||||||
|  |         cookie.append("foo", "bar"); | ||||||
|  |         assert_eq!(cookie, parsed); | ||||||
|  |  | ||||||
|  |         let parsed = Cookie::parse_header(&b"foo=bar; baz=quux".to_vec().into()).unwrap(); | ||||||
|  |         cookie.append("baz", "quux"); | ||||||
|  |         assert_eq!(cookie, parsed); | ||||||
|  |  | ||||||
|  |         let parsed = Cookie::parse_header(&b" foo  =    bar;baz= quux  ".to_vec().into()).unwrap(); | ||||||
|  |         assert_eq!(cookie, parsed); | ||||||
|  |  | ||||||
|  |         let parsed = Cookie::parse_header(&vec![b"foo  =    bar".to_vec(),b"baz= quux  ".to_vec()].into()).unwrap(); | ||||||
|  |         assert_eq!(cookie, parsed); | ||||||
|  |  | ||||||
|  |         Cookie::parse_header(&b"foo;bar=baz;quux".to_vec().into()).unwrap_err(); | ||||||
|  |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -19,6 +19,11 @@ impl<K: PartialEq, V> VecMap<K, V> { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     #[inline] | ||||||
|  |     pub fn append(&mut self, key: K, value: V) { | ||||||
|  |         self.vec.push((key, value)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     #[inline] |     #[inline] | ||||||
|     pub fn entry(&mut self, key: K) -> Entry<K, V> { |     pub fn entry(&mut self, key: K) -> Entry<K, V> { | ||||||
|         match self.find(&key) { |         match self.find(&key) { | ||||||
| @@ -55,10 +60,23 @@ impl<K: PartialEq, V> VecMap<K, V> { | |||||||
|     pub fn iter(&self) -> ::std::slice::Iter<(K, V)> { |     pub fn iter(&self) -> ::std::slice::Iter<(K, V)> { | ||||||
|         self.vec.iter() |         self.vec.iter() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[inline] |     #[inline] | ||||||
|     pub fn remove<K2: PartialEq<K> + ?Sized>(&mut self, key: &K2) -> Option<V> { |     pub fn remove<K2: PartialEq<K> + ?Sized>(&mut self, key: &K2) -> Option<V> { | ||||||
|         self.find(key).map(|pos| self.vec.remove(pos)).map(|(_, v)| v) |         self.find(key).map(|pos| self.vec.remove(pos)).map(|(_, v)| v) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     #[inline] | ||||||
|  |     pub fn remove_all<K2: PartialEq<K> + ?Sized>(&mut self, key: &K2) { | ||||||
|  |         let len = self.vec.len(); | ||||||
|  |         for i in (0..len).rev() { | ||||||
|  |             if key == &self.vec[i].0 { | ||||||
|  |                 self.vec.remove(i); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     #[inline] |     #[inline] | ||||||
|     pub fn clear(&mut self) { |     pub fn clear(&mut self) { | ||||||
|         self.vec.clear(); |         self.vec.clear(); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user