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