feat(headers): headers.iter() is more powerful
Previously, iter() returned (&str, HeaderView), which wasn't too useful other than to write out to a stream. Checking for a certain header is painful since header names are case-insensitive, but &str isn't. And once found, you still couldn't do anything with a HeaderView other format it. Now, iter() returns a HeaderView, with methods is(), name(), and value(). You can do `view.is::<ContentType>()` to check what header it is. And you get the typed value out by calling `view.value::<ContentType>()`, which will return an `Option<ContentType>`, similar to `headers.get()`. Headers also now implement Extend and FromIterator, so it's easier to build new Headers objects by filtering out a few headers. Because this changes .iter() to return HeaderView instead of a tuple, this is a: [breaking-change]
This commit is contained in:
		| @@ -59,13 +59,8 @@ pub trait HeaderFormat: Clone + Any + Send + Sync { | ||||
|     fn clone_box(&self) -> Box<HeaderFormat + Sync + Send> { box self.clone() } | ||||
| } | ||||
|  | ||||
| #[doc(hidden)] | ||||
| trait Is { | ||||
|     fn is<T: 'static>(self) -> bool; | ||||
| } | ||||
|  | ||||
| impl<'a> Is for &'a HeaderFormat { | ||||
|     fn is<T: 'static>(self) -> bool { | ||||
| impl HeaderFormat { | ||||
|     fn is<T: 'static>(&self) -> bool { | ||||
|         self.get_type_id() == TypeId::of::<T>() | ||||
|     } | ||||
| } | ||||
| @@ -92,7 +87,7 @@ impl Clone for Box<HeaderFormat + Send + Sync> { | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn header_name<T: Header + HeaderFormat>() -> &'static str { | ||||
| fn header_name<T: Header>() -> &'static str { | ||||
|     let name = Header::header_name(None::<T>); | ||||
|     name | ||||
| } | ||||
| @@ -189,74 +184,23 @@ impl Headers { | ||||
|     /// Get a reference to the header field's value, if it exists. | ||||
|     pub fn get<H: Header + HeaderFormat>(&self) -> Option<&H> { | ||||
|         self.get_or_parse::<H>().map(|item| { | ||||
|             let read = item.read(); | ||||
|             debug!("downcasting {}", *read); | ||||
|             let ret = match read.typed { | ||||
|                 Some(ref val) => unsafe { val.downcast_ref_unchecked() }, | ||||
|                 _ => unreachable!() | ||||
|             }; | ||||
|             unsafe { mem::transmute::<&H, &H>(ret) } | ||||
|             unsafe { | ||||
|                 mem::transmute::<&H, &H>(downcast(&*item.read())) | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     /// Get a mutable reference to the header field's value, if it exists. | ||||
|     pub fn get_mut<H: Header + HeaderFormat>(&mut self) -> Option<&mut H> { | ||||
|         self.get_or_parse::<H>().map(|item| { | ||||
|             let mut write = item.write(); | ||||
|             debug!("downcasting {}", *write); | ||||
|             let ret = match *&mut write.typed { | ||||
|                 Some(ref mut val) => unsafe { val.downcast_mut_unchecked() }, | ||||
|                 _ => unreachable!() | ||||
|             }; | ||||
|             unsafe { mem::transmute::<&mut H, &mut H>(ret) } | ||||
|             unsafe { | ||||
|                 mem::transmute::<&mut H, &mut H>(downcast_mut(&mut *item.write())) | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     fn get_or_parse<H: Header + HeaderFormat>(&self) -> Option<&RWLock<Item>> { | ||||
|         self.data.get(&CaseInsensitive(Slice(header_name::<H>()))).and_then(|item| { | ||||
|             match item.read().typed { | ||||
|                 Some(ref typed) if typed.is::<H>() => return Some(item), | ||||
|                 Some(ref typed) => { | ||||
|                     warn!("attempted to access {} as wrong type", typed); | ||||
|                     return None; | ||||
|                 } | ||||
|                 _ => () | ||||
|             } | ||||
|  | ||||
|             // Take out a write lock to do the parsing and mutation. | ||||
|             let mut write = item.write(); | ||||
|  | ||||
|             // Since this lock can queue, it's possible another thread just | ||||
|             // did the work for us. | ||||
|             match write.typed { | ||||
|                 // Check they inserted the correct type and move on. | ||||
|                 Some(ref typed) if typed.is::<H>() => return Some(item), | ||||
|  | ||||
|                 // Wrong type, another thread got here before us and parsed | ||||
|                 // as a different representation. | ||||
|                 Some(ref typed) => { | ||||
|                     debug!("other thread was here first?") | ||||
|                     warn!("attempted to access {} as wrong type", typed); | ||||
|                     return None; | ||||
|                 }, | ||||
|  | ||||
|                 // We are first in the queue or the only ones, so do the actual | ||||
|                 // work of parsing and mutation. | ||||
|                 _ => () | ||||
|             } | ||||
|  | ||||
|             let header = match write.raw { | ||||
|                 Some(ref raw) => match Header::parse_header(raw[]) { | ||||
|                     Some::<H>(h) => h, | ||||
|                     None => return None | ||||
|                 }, | ||||
|                 None => unreachable!() | ||||
|             }; | ||||
|  | ||||
|             // Mutate! | ||||
|             write.typed = Some(box header as Box<HeaderFormat + Send + Sync>); | ||||
|             Some(item) | ||||
|         }) | ||||
|         self.data.get(&CaseInsensitive(Slice(header_name::<H>()))).and_then(|item| get_or_parse::<H>(item)) | ||||
|     } | ||||
|  | ||||
|     /// Returns a boolean of whether a certain header is in the map. | ||||
| @@ -299,8 +243,8 @@ impl Headers { | ||||
|  | ||||
| impl fmt::Show for Headers { | ||||
|     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { | ||||
|         for (k, v) in self.iter() { | ||||
|             try!(write!(fmt, "{}: {}{}", k, v, LineEnding)); | ||||
|         for header in self.iter() { | ||||
|             try!(write!(fmt, "{}{}", header, LineEnding)); | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| @@ -311,22 +255,67 @@ pub struct HeadersItems<'a> { | ||||
|     inner: Entries<'a, CaseInsensitive<SendStr>, RWLock<Item>> | ||||
| } | ||||
|  | ||||
| impl<'a> Iterator<(&'a str, HeaderView<'a>)> for HeadersItems<'a> { | ||||
|     fn next(&mut self) -> Option<(&'a str, HeaderView<'a>)> { | ||||
| impl<'a> Iterator<HeaderView<'a>> for HeadersItems<'a> { | ||||
|     fn next(&mut self) -> Option<HeaderView<'a>> { | ||||
|         match self.inner.next() { | ||||
|             Some((k, v)) => Some((k.as_slice(), HeaderView(v))), | ||||
|             Some((k, v)) => Some(HeaderView(k, v)), | ||||
|             None => None | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Returned with the `HeadersItems` iterator. | ||||
| pub struct HeaderView<'a>(&'a RWLock<Item>); | ||||
| pub struct HeaderView<'a>(&'a CaseInsensitive<SendStr>, &'a RWLock<Item>); | ||||
|  | ||||
| impl<'a> HeaderView<'a> { | ||||
|     /// Check if a HeaderView is a certain Header. | ||||
|     #[inline] | ||||
|     pub fn is<H: Header>(&self) -> bool { | ||||
|         CaseInsensitive(header_name::<H>().into_maybe_owned()) == *self.0 | ||||
|     } | ||||
|  | ||||
|     /// Get the Header name as a slice. | ||||
|     #[inline] | ||||
|     pub fn name(&self) -> &'a str { | ||||
|         self.0.as_slice() | ||||
|     } | ||||
|  | ||||
|     /// Cast the value to a certain Header type. | ||||
|     #[inline] | ||||
|     pub fn value<H: Header + HeaderFormat>(&self) -> Option<&'a H> { | ||||
|         get_or_parse::<H>(self.1).map(|item| { | ||||
|             unsafe { | ||||
|                 mem::transmute::<&H, &H>(downcast(&*item.read())) | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     /// Get just the header value as a String. | ||||
|     #[inline] | ||||
|     pub fn value_string(&self) -> String { | ||||
|         (*self.1.read()).to_string() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> fmt::Show for HeaderView<'a> { | ||||
|     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { | ||||
|         let HeaderView(item) = *self; | ||||
|         item.read().fmt(fmt) | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         write!(f, "{}: {}", self.0, *self.1.read()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> Extend<HeaderView<'a>> for Headers { | ||||
|     fn extend<I: Iterator<HeaderView<'a>>>(&mut self, mut iter: I) { | ||||
|         for header in iter { | ||||
|             self.data.insert((*header.0).clone(), (*header.1).clone()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> FromIterator<HeaderView<'a>> for Headers { | ||||
|     fn from_iter<I: Iterator<HeaderView<'a>>>(iter: I) -> Headers { | ||||
|         let mut headers = Headers::new(); | ||||
|         headers.extend(iter); | ||||
|         headers | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -350,6 +339,68 @@ impl Item { | ||||
|             typed: Some(ty), | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| fn get_or_parse<H: Header + HeaderFormat>(item: &RWLock<Item>) -> Option<&RWLock<Item>> { | ||||
|     match item.read().typed { | ||||
|         Some(ref typed) if typed.is::<H>() => return Some(item), | ||||
|         Some(ref typed) => { | ||||
|             warn!("attempted to access {} as wrong type", typed); | ||||
|             return None; | ||||
|         } | ||||
|         _ => () | ||||
|     } | ||||
|  | ||||
|     // Take out a write lock to do the parsing and mutation. | ||||
|     let mut write = item.write(); | ||||
|  | ||||
|     // Since this lock can queue, it's possible another thread just | ||||
|     // did the work for us. | ||||
|     match write.typed { | ||||
|         // Check they inserted the correct type and move on. | ||||
|         Some(ref typed) if typed.is::<H>() => return Some(item), | ||||
|  | ||||
|         // Wrong type, another thread got here before us and parsed | ||||
|         // as a different representation. | ||||
|         Some(ref typed) => { | ||||
|             debug!("other thread was here first?") | ||||
|             warn!("attempted to access {} as wrong type", typed); | ||||
|             return None; | ||||
|         }, | ||||
|  | ||||
|         // We are first in the queue or the only ones, so do the actual | ||||
|         // work of parsing and mutation. | ||||
|         _ => () | ||||
|     } | ||||
|  | ||||
|     let header = match write.raw { | ||||
|         Some(ref raw) => match Header::parse_header(raw[]) { | ||||
|             Some::<H>(h) => h, | ||||
|             None => return None | ||||
|         }, | ||||
|         None => unreachable!() | ||||
|     }; | ||||
|  | ||||
|     // Mutate! | ||||
|     write.typed = Some(box header as Box<HeaderFormat + Send + Sync>); | ||||
|     Some(item) | ||||
| } | ||||
|  | ||||
| fn downcast<H: Header + HeaderFormat>(read: &Item) -> &H { | ||||
|     debug!("downcasting {}", *read); | ||||
|     match read.typed { | ||||
|         Some(ref val) => unsafe { val.downcast_ref_unchecked() }, | ||||
|         _ => unreachable!() | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn downcast_mut<H: Header + HeaderFormat>(write: &mut Item) -> &mut H { | ||||
|     debug!("downcasting {}", *write); | ||||
|     match write.typed { | ||||
|         Some(ref mut val) => unsafe { val.downcast_mut_unchecked() }, | ||||
|         _ => unreachable!() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl fmt::Show for Item { | ||||
| @@ -447,6 +498,8 @@ mod tests { | ||||
|     use super::{Headers, Header, HeaderFormat}; | ||||
|     use super::common::{ContentLength, ContentType, Accept, Host}; | ||||
|  | ||||
|     use test::Bencher; | ||||
|  | ||||
|     fn mem(s: &str) -> MemReader { | ||||
|         MemReader::new(s.as_bytes().to_vec()) | ||||
|     } | ||||
| @@ -586,4 +639,25 @@ mod tests { | ||||
|         headers.clear(); | ||||
|         assert_eq!(headers.len(), 0); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_iter() { | ||||
|         let mut headers = Headers::new(); | ||||
|         headers.set(ContentLength(11)); | ||||
|         for header in headers.iter() { | ||||
|             assert!(header.is::<ContentLength>()); | ||||
|             assert_eq!(header.name(), Header::header_name(None::<ContentLength>)); | ||||
|             assert_eq!(header.value(), Some(&ContentLength(11))); | ||||
|             assert_eq!(header.value_string(), "11".to_string()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[bench] | ||||
|     fn bench_header_view_is(b: &mut Bencher) { | ||||
|         let mut headers = Headers::new(); | ||||
|         headers.set(ContentLength(11)); | ||||
|         let mut iter = headers.iter(); | ||||
|         let view = iter.next().unwrap(); | ||||
|         b.iter(|| assert!(view.is::<ContentLength>())) | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user