summaryrefslogtreecommitdiffstats
path: root/headers/src/map/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'headers/src/map/mod.rs')
-rw-r--r--headers/src/map/mod.rs1042
1 files changed, 1042 insertions, 0 deletions
diff --git a/headers/src/map/mod.rs b/headers/src/map/mod.rs
new file mode 100644
index 0000000..22de165
--- /dev/null
+++ b/headers/src/map/mod.rs
@@ -0,0 +1,1042 @@
+//! Module containing the `HeaderMap`.
+//!
+//! It also contains some helper types like iterator types
+//! for the HeaderMap etc.
+use std::marker::PhantomData;
+use std::iter::ExactSizeIterator;
+use std::fmt::{self, Debug};
+use std::collections::HashSet;
+use std::cmp::PartialEq;
+use std::hash::{Hash, Hasher};
+
+use total_order_multi_map::{
+ self,
+ TotalOrderMultiMap,
+ EntryValues,
+ EntryValuesMut
+};
+
+use ::error::{
+ HeaderTypeError,
+ HeaderValidationError,
+ BuildInValidationError
+};
+
+use ::name::{
+ HeaderName, HasHeaderName
+};
+
+use ::header::{
+ Header, HeaderKind,
+ HeaderObj, HeaderObjTrait,
+ MaxOneMarker
+};
+
+mod into_iter;
+pub use self::into_iter::*;
+
+/// The type of an validator used to check more complex header contraints.
+///
+/// An example constraint would be if a `From` header field contains more than
+/// one mailbox a `Sender` header field is required to be present.
+pub type HeaderMapValidator = fn(&HeaderMap) -> Result<(), ::error::HeaderValidationError>;
+
+//TODO extend example to use get,get_mut etc.
+/// A header map is a collection representing a number
+/// of mail headers in an specific order.
+///
+///
+/// # Example
+///
+/// ```
+/// # #[macro_use]
+/// # extern crate mail_headers;
+///
+/// // just import all headers
+/// use mail_headers::HeaderMap;
+/// use mail_headers::headers::*;
+/// use mail_headers::error::ComponentCreationError;
+///
+/// fn create_headers() -> Result<HeaderMap, ComponentCreationError> {
+/// headers!{
+/// // from and to can have multiple values
+/// // until specialization is stable is array
+/// // is necessary
+/// _From: [("My Fancy Display Name", "theduck@example.com")],
+/// _To: [ "unknown@example.com", ],
+/// Subject: "Who are you?"
+/// }
+/// }
+///
+/// fn main() {
+/// let headers = create_headers().unwrap();
+/// assert_eq!(headers.len(), 3);
+/// }
+/// ```
+///
+/// # Note
+///
+/// A number of methods implemented on HeaderMap appear in two variations,
+/// one which accepts a type hint (a normally zero sized struct implementing
+/// HeaderKind) and on which just accepts the type and needs to be called with
+/// the turbofish operator. The later one is prefixed by a `_` as the former
+/// one is more nice to use, but in some situations, e.g. when wrapping
+/// `HeaderMap` in custom code the only type accepting variations are more
+/// useful.
+///
+/// ```rust,ignore
+/// let _ = map.get(Subject);
+/// //is equivalent to
+/// let _ = map._get::<Subject>();
+/// ```
+///
+/// # MaxOne (In-)Consistency
+///
+/// Most headers can only appear up to one time in a header section.
+/// They are marked with `H::MAX_ONE == true` and implement `MaxOneMarker`,
+/// also as object you can use `is_max_one` to check it.
+///
+/// Not only can they only appear max one time, it is normal for a user
+/// who is not aware about the other headers to expect that when you insert
+/// them into a header map which already contains them that they replace
+/// the existing header. Even more so most headers which can appear more
+/// then one time are unlikely to appear in a application of this library
+/// like e.g. all `Resent-*` header which normally get just prepended
+/// to existing mail in text format or the `Comment` header which isn't
+/// used that much.
+///
+/// Because of this it was decided that when inserting a `"max one"` header
+/// it will act as expected an replace other headers with the same name and
+/// only if a `"multi"` header is inserted it is added to all headers associated
+/// with the same name.
+///
+/// But there is a single problem. If there are multiple implementations implementations
+/// for the same header which disagree in wether or not the header is `"max one"` (which
+/// would be a bug anyway!) then this can lead to a tricky situration when you first
+/// insert the version which is `"max one"` and then the one which is `"multi"`.
+/// There had been two ways to deal with this:
+///
+/// 1. return a error when inserting in such a situation
+/// 2. simple allow it and check it when running the other
+/// validators
+///
+/// Given that a header map contains additionally validators which needs
+/// to be run explicitly to make sure that a map is valid before using it
+/// as a header section in a mail it was decided to go with the later approach.
+/// Originally the first approach was implemented but turned out to be not
+/// very ergonomic, and the second approach has little disadvantages as:
+///
+/// - it's already unlikely to run into the situation
+/// - you have to run validators anyway before using the
+/// header map
+///
+///
+/// **So yes, you can not relay on the "max one" constraints
+/// to be uphold without running the validators**
+///
+///
+#[derive(Clone)]
+pub struct HeaderMap {
+ inner_map: TotalOrderMultiMap<HeaderName, Box<HeaderObj>>,
+}
+
+pub type Iter<'a> = total_order_multi_map::Iter<'a, HeaderName, Box<HeaderObj>>;
+pub type IterMut<'a> = total_order_multi_map::IterMut<'a, HeaderName, Box<HeaderObj>>;
+pub type Values<'a> = total_order_multi_map::Values<'a, HeaderName, Box<HeaderObj>>;
+pub type ValuesMut<'a> = total_order_multi_map::ValuesMut<'a, HeaderName, Box<HeaderObj>>;
+
+impl Debug for HeaderMap {
+ fn fmt(&self, fter: &mut fmt::Formatter) -> fmt::Result {
+ write!(fter, "HeaderMap {{ ")?;
+ for (key, val_cont) in self.iter() {
+ write!(fter, "{}: {:?},", key.as_str(), val_cont)?;
+ }
+ write!(fter, " }}")
+ }
+}
+
+impl Default for HeaderMap {
+ fn default() -> Self {
+ HeaderMap {
+ inner_map: Default::default()
+ }
+ }
+}
+
+impl HeaderMap {
+
+ /// create a new empty header map
+ pub fn new() -> Self {
+ Default::default()
+ }
+
+ /// returns the number of headers in this map
+ pub fn len(&self) -> usize {
+ self.inner_map.len()
+ }
+
+ /// clears the header map
+ ///
+ /// This removes all headers _and_ all validators
+ pub fn clear(&mut self) {
+ self.inner_map.clear();
+ }
+
+ /// Iterate over all `HeaderObj` added to the map.
+ pub fn values(&self) -> Values {
+ self.inner_map.values()
+ }
+
+ /// Iterate with mut refs over all `HeaderObj` added to the map.
+ pub fn values_mut(&mut self) -> ValuesMut {
+ self.inner_map.values_mut()
+ }
+
+ /// call each unique contextual validator exactly once with this map as parameter
+ ///
+ /// If multiple Headers provide the same contextual validator (e.g. the resent headers)
+ /// it's still only called once.
+ ///
+ /// # Max One
+ ///
+ /// This will also validate that for any header name for which a header
+ /// was added with `MAX_ONE == true` it will be validated that it is the
+ /// only header for that header name.
+ pub fn use_contextual_validators(&self) -> Result<(), HeaderValidationError> {
+ let mut seen_validators = HashSet::new();
+
+ let mut validate = |validator| -> Result<(), HeaderValidationError> {
+ if let Some(validator) = validator {
+ if seen_validators.insert(ValidatorHashWrapper(validator)) {
+ (validator)(self)?;
+ }
+ }
+ Ok(())
+ };
+
+ for mut group in self.inner_map.group_iter() {
+ let first = group.next().expect("[BUG] returned header without any headers inserted for it");
+ let max_one = first.is_max_one();
+ validate(first.validator())?;
+ let header_name = group.key().as_str();
+ for other in group {
+ if max_one != other.is_max_one() {
+ return Err(BuildInValidationError::MaxOneInconsistency { header_name }.into());
+ }
+ validate(other.validator())?;
+ }
+ }
+ Ok(())
+ }
+
+ /// Returns true if this map contains a header with the given name.
+ pub fn contains<H: HasHeaderName>(&self, name: H) -> bool {
+ self.inner_map.contains_key(name.get_name())
+ }
+
+ /// Returns the single header associated with the given header kind.
+ ///
+ /// As this uses the `MaxOneMarker` trait which _should_ only be implemented
+ /// for `HeaderKind` impl with `MAX_ONE == true` this function can only
+ /// be used when it's fine to ignore the possible case of more than
+ /// one header of the given kind being in the same map.
+ ///
+ /// # Type Hint
+ ///
+ /// The type hint passed in is for ergonomics, e.g. so
+ /// that it's possible to write code like `map.get_single(Subject)`
+ /// if this gets in the way `_get_single` can be used which would
+ /// lead to code like `map._get_single::<Subject>()`.
+ ///
+ /// # Error
+ ///
+ /// - If there are multiple implementations for the same header and
+ /// the inserted headers is based on a different type some `HeaderTypeError`
+ /// is returned
+ ///
+ /// - If there are multiple implementations for the same header which
+ /// disagree on the value of `H::MAX_ONE` (which is a bug) this can
+ /// in some rare situations lead to be there more then one header for
+ /// a "max one" header in the map, in which case a `HeaderTypeError`
+ /// is returned.
+ #[inline]
+ pub fn get_single<'a, H>(&'a self, _type_hint: H)
+ -> Option<Result<&'a Header<H>, HeaderTypeError>>
+ where H: MaxOneMarker
+ {
+ self._get_single::<H>()
+ }
+
+ /// A variation of `get_single` which doesn't require passing in a type hint.
+ ///
+ /// Normally using `get_single` is more ergonomic, except if you write a function
+ /// which abstracts over it in which case using `_get_single` can be better.
+ pub fn _get_single<'a, H>(&'a self)
+ -> Option<Result<&'a Header<H>, HeaderTypeError>>
+ where H: MaxOneMarker
+ {
+ let mut bodies = self.get_untyped(H::name());
+ if bodies.len() > 1 {
+ return Some(Err(HeaderTypeError::new(H::name())))
+ }
+
+ bodies.next().map(|untyped| {
+ untyped.downcast_ref::<H>()
+ .ok_or_else(|| HeaderTypeError::new(H::name()))
+ })
+ }
+
+ /// Returns a a mutable reference to the header associated with the given header kind.__internals
+ ///
+ /// See `HeaderMap::get_single` for more details.
+ #[inline]
+ pub fn get_single_mut<H>(&mut self, _type_hint: H)
+ -> Option<Result<&mut Header<H>, HeaderTypeError>>
+ where H: MaxOneMarker
+ {
+ self._get_single_mut::<H>()
+ }
+
+ /// Returns a a mutable reference to the header associated with the given header kind.__internals
+ ///
+ /// See `HeaderMap::_get_single` for more details.
+ pub fn _get_single_mut<H>(&mut self)
+ -> Option<Result<&mut Header<H>, HeaderTypeError>>
+ where H: MaxOneMarker
+ {
+ let mut bodies = self.get_untyped_mut(H::name());
+ if bodies.len() > 1 {
+ return Some(Err(HeaderTypeError::new(H::name())))
+ }
+
+ bodies.next().map(|untyped| {
+ untyped.downcast_mut::<H>()
+ .ok_or_else(|| HeaderTypeError::new(H::name()))
+ })
+ }
+
+ /// Returns all header bodies for a given header name, without trying to cast them to a concrete type
+ ///
+ /// Accepts both `HeaderName` or a type implementing `HeaderKind`.
+ ///
+ #[inline]
+ pub fn get_untyped<H: HasHeaderName>(&self, name: H) -> UntypedBodies {
+ self.inner_map.get(name.get_name())
+ }
+
+ /// Returns all header bodies for a given header name, without trying to cast them to a concrete type
+ ///
+ /// Accepts both `HeaderName` or a type implementing `HeaderKind`.
+ ///
+ #[inline]
+ pub fn get_untyped_mut<H: HasHeaderName>(&mut self, name: H) -> UntypedBodiesMut {
+ self.inner_map.get_mut(name.get_name())
+ }
+
+ /// Returns all header bodies for a given header
+ #[inline(always)]
+ pub fn get<H>(&self, _type_hint: H) -> TypedBodies<H>
+ where H: HeaderKind
+ {
+ self._get::<H>()
+ }
+
+ /// Returns all header bodies for a given header
+ pub fn _get<H>(&self) -> TypedBodies<H>
+ where H: HeaderKind
+ {
+ self.get_untyped(H::name()).into()
+ }
+
+ /// Returns all header bodies for a given header
+ #[inline(always)]
+ pub fn get_mut<H>(&mut self, _type_hint: H) -> TypedBodiesMut<H>
+ where H: HeaderKind
+ {
+ self._get_mut::<H>()
+ }
+
+ /// Returns all header bodies for a given header
+ pub fn _get_mut<H>(&mut self) -> TypedBodiesMut<H>
+ where H: HeaderKind
+ {
+ self.get_untyped_mut(H::name()).into()
+ }
+
+ /// Inserts the given header into the map either replacing or adding to existing headers.
+ ///
+ /// - If `H::MAX_ONE` is `true` then it will use "replacing insert" which means
+ /// all headers previously associated with the given header (name) are removed when
+ /// adding the new header.
+ ///
+ /// This behavior is analog to how a normal map works and
+ /// is what a user which isn't aware that there are some headers which can appear multiple
+ /// times would expect. Most common headers (`Subject`, `From`, `To`, `Sender`, etc.) fall
+ /// into this category.
+ ///
+ /// - If `H::MAX_ONE` is `false` then it will use "adding insert" which means
+ /// that it will add the header to all headers previously associated with the given
+ /// header name.
+ ///
+ pub fn insert<H>(&mut self, header: Header<H>)
+ where H: HeaderKind
+ {
+ let name = header.name();
+ let obj: Box<HeaderObj> = Box::new(header);
+ self._insert(name, H::MAX_ONE, obj)
+ }
+
+ /// Insert a HeaderObj into the header map.
+ #[doc(hidden)]
+ pub fn insert_untyped(&mut self, obj: Box<HeaderObj>) {
+ self._insert(obj.name(), obj.is_max_one(), obj)
+ }
+
+ #[inline(always)]
+ fn _insert(&mut self, name: HeaderName, max_one: bool, obj: Box<HeaderObj>) {
+ if max_one {
+ self.inner_map.set(name, obj);
+ } else {
+ self.inner_map.add(name, obj);
+ }
+ }
+
+ /// Insert all given headers in order into this header map.
+ ///
+ /// The insertion order of the given headers into this map
+ /// is the same as the order in which they had been inserted
+ /// into the header map through which they had been given to
+ /// this method.
+ ///
+ /// As this uses insertion it also means that headers with
+ /// `MAX_ONE == true` in the headers to insert can replace
+ /// existing headers associated with the same header name.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # #[macro_use]
+ /// # extern crate mail_headers;
+ /// # fn main() {
+ /// use mail_headers::headers::*;
+ ///
+ /// let mut map = headers!{
+ /// _From: [("Not Met", "it.s.me@example.com")],
+ /// Subject: "..."
+ /// }.unwrap();
+ ///
+ /// map.insert_all(headers! {
+ /// _To: [("You", "someone@example.com")],
+ /// Subject: "expected subject"
+ /// }.unwrap());
+ ///
+ /// assert_eq!(map.len(), 3);
+ /// let subject = map.get_single(Subject)
+ /// .expect("Subject to be in map (Some)")
+ /// .expect("The type to be correct (Ok)");
+ ///
+ /// assert_eq!(subject.as_str(), "expected subject");
+ /// assert!(map.contains(_From));
+ /// assert!(map.contains(_To));
+ /// # }
+ /// ```
+ ///
+ pub fn insert_all(&mut self, other: HeaderMap) {
+ for (_name, header) in other.into_iter() {
+ self.insert_untyped(header);
+ }
+ }
+
+ /// Remove all headers with the given header name.
+ ///
+ /// Returns true, if at last one header was removed.
+ pub fn remove<H: HasHeaderName>(&mut self, name: H) -> bool {
+ self.inner_map.remove_all(name.get_name())
+ }
+
+ /// iterate over all (header name, boxed body) pairs in this map
+ pub fn iter(&self) -> Iter {
+ self.inner_map.iter()
+ }
+
+}
+
+/// Iterator over all boxed bodies for a given header name
+pub type UntypedBodies<'a> = EntryValues<'a, HeaderObj>;
+pub type UntypedBodiesMut<'a> = EntryValuesMut<'a, HeaderObj>;
+
+
+/// Iterator over all boxed bodies for a given header name with knows which type they should have
+///
+/// This iterator will automatically try to cast each header body of this
+/// header to `H::Component`, i.e. the type this body _should_ have.
+pub struct TypedBodies<'a, H>
+ where H: HeaderKind
+{
+ inner: UntypedBodies<'a>,
+ _marker: PhantomData<H>
+}
+
+impl<'a, H> From<UntypedBodies<'a>> for TypedBodies<'a, H>
+ where H: HeaderKind
+{
+ fn from(untyped: UntypedBodies<'a>) -> Self {
+ Self::new(untyped)
+ }
+}
+
+impl<'a, H> TypedBodies<'a, H>
+ where H: HeaderKind,
+{
+ fn new(inner: UntypedBodies<'a>) -> Self {
+ TypedBodies {
+ inner,
+ _marker: PhantomData
+ }
+ }
+}
+
+impl<'a, H> Iterator for TypedBodies<'a, H>
+ where H: HeaderKind
+{
+ type Item = Result<&'a Header<H>, HeaderTypeError>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.inner.next()
+ .map( |tobj| {
+ tobj.downcast_ref::<H>()
+ .ok_or_else(|| HeaderTypeError::new(H::name()))
+ } )
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ self.inner.size_hint()
+ }
+}
+
+impl<'a, H> ExactSizeIterator for TypedBodies<'a, H>
+ where H: HeaderKind
+{
+ fn len(&self) -> usize {
+ self.inner.len()
+ }
+}
+
+impl<'a, H> Clone for TypedBodies<'a, H>
+ where H: HeaderKind
+{
+ fn clone(&self) -> Self {
+ TypedBodies::new(self.inner.clone())
+ }
+}
+
+impl<'a, H> Debug for TypedBodies<'a, H>
+ where H: HeaderKind
+{
+ fn fmt(&self, fter: &mut fmt::Formatter) -> fmt::Result {
+ fter.debug_struct("TypedBodies")
+ .field("inner", &self.inner)
+ .finish()
+ }
+}
+
+/// Iterator over all boxed bodies for a given header name with knows which type they should have
+///
+/// This iterator will automatically try to cast each header body of this
+/// header to `H::Component`, i.e. the type this body _should_ have.
+pub struct TypedBodiesMut<'a, H>
+ where H: HeaderKind
+{
+ inner: UntypedBodiesMut<'a>,
+ _marker: PhantomData<H>
+}
+
+impl<'a, H> From<UntypedBodiesMut<'a>> for TypedBodiesMut<'a, H>
+ where H: HeaderKind
+{
+ fn from(untyped: UntypedBodiesMut<'a>) -> Self {
+ Self::new(untyped)
+ }
+}
+
+impl<'a, H> TypedBodiesMut<'a, H>
+ where H: HeaderKind
+{
+ fn new(inner: UntypedBodiesMut<'a>) -> Self {
+ TypedBodiesMut {
+ inner,
+ _marker: PhantomData
+ }
+ }
+}
+
+impl<'a, H> Iterator for TypedBodiesMut<'a, H>
+ where H: HeaderKind
+{
+ type Item = Result<&'a mut Header<H>, HeaderTypeError>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.inner.next()
+ .map(|tobj| {
+ tobj.downcast_mut::<H>()
+ .ok_or_else(|| HeaderTypeError::new(H::name()))
+ })
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ self.inner.size_hint()
+ }
+}
+
+impl<'a, H> ExactSizeIterator for TypedBodiesMut<'a, H>
+ where H: HeaderKind
+{
+ fn len(&self) -> usize {
+ self.inner.len()
+ }
+}
+
+impl<'a, H> Debug for TypedBodiesMut<'a, H>
+ where H: HeaderKind
+{
+ fn fmt(&self, fter: &mut fmt::Formatter) -> fmt::Result {
+ fter.write_str("TypedBodiesMut { .. }")
+ }
+}
+
+/// Create a header map from a list of header's with ther fields
+///
+/// # Example
+///
+/// ```
+/// # #[macro_use]
+/// # extern crate mail_headers;
+/// # use mail_headers::headers::*;
+/// # use mail_headers::error::ComponentCreationError;
+/// # fn main() { (|| -> Result<(), ComponentCreationError> {
+/// let map = headers! {
+/// _From: ["bobo@nana.test"],
+/// Subject: "hy there"
+/// }?;
+/// # Ok(()) })(); }
+/// ```
+#[macro_export]
+macro_rules! headers {
+ ($($header:ty : $val:expr),*) => ({
+ //FIXME[rust/catch block] use catch block once available
+ (|| -> Result<$crate::HeaderMap, $crate::error::ComponentCreationError>
+ {
+ let mut map = $crate::HeaderMap::new();
+ $(
+ map.insert(<$header as $crate::HeaderKind>::auto_body($val)?);
+ )*
+ Ok(map)
+ })()
+ });
+}
+
+/// HeaderMapValidator is just a function pointer,
+/// but it does not implement Hash so we wrap it
+/// and implement Hash on it. Note that some function
+/// pointers implement Hash/Eq and other doesn't,
+/// which is caused by some limitations with wildcard
+/// implementations
+#[derive(Copy, Clone)]
+struct ValidatorHashWrapper(HeaderMapValidator);
+
+impl ValidatorHashWrapper {
+
+ fn identity_repr(&self) -> usize {
+ self.0 as usize
+ }
+}
+
+impl PartialEq<Self> for ValidatorHashWrapper {
+ fn eq(&self, other: &Self) -> bool {
+ self.identity_repr() == other.identity_repr()
+ }
+}
+
+impl Eq for ValidatorHashWrapper {}
+
+impl Debug for ValidatorHashWrapper {
+ fn fmt(&self, fter: &mut fmt::Formatter) -> fmt::Result {
+ write!(fter, "ValidatorHashWrapper(0x{:x})", self.identity_repr())
+ }
+}
+
+impl Hash for ValidatorHashWrapper {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ state.write_usize(self.identity_repr())
+ }
+}
+
+
+pub fn check_header_count_max_one(name: HeaderName, map: &HeaderMap)
+ -> Result<(), HeaderValidationError>
+{
+ let valid = map.get_untyped(name).len() <= 1;
+ if valid {
+ Ok(())
+ } else {
+ Err(HeaderValidationError::from(
+ BuildInValidationError::MoreThenOne {
+ header_name: name.as_str()
+ }
+ ))
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use failure::Context;
+ use soft_ascii_string::SoftAsciiStr;
+
+ use internals::error::{EncodingError, EncodingErrorKind};
+ use internals::encoder::{EncodableInHeader, EncodingWriter};
+
+ use ::HeaderTryFrom;
+ use ::error::{ComponentCreationError, HeaderValidationError};
+ use ::header_components::RawUnstructured;
+
+ use super::*;
+
+ use self::good_headers::*;
+ use self::bad_headers::{
+ Subject as BadSubject,
+ Comments as BadComments
+ };
+ use self::bad_headers2::{
+ Comments2 as BadComments2
+ };
+
+ #[derive(Debug, Clone, Eq, PartialEq, Hash)]
+ pub struct OtherComponent;
+
+ impl HeaderTryFrom<()> for OtherComponent {
+ fn try_from(_: ()) -> Result<OtherComponent, ComponentCreationError> {
+ Ok(OtherComponent)
+ }
+ }
+ impl EncodableInHeader for OtherComponent {
+ fn encode(&self, _encoder: &mut EncodingWriter) -> Result<(), EncodingError> {
+ Err(EncodingError::from(
+ EncodingErrorKind::Other { kind: "encoding is not implemented" }))
+ }
+
+ fn boxed_clone(&self) -> Box<EncodableInHeader> {
+ Box::new(self.clone())
+ }
+ }
+
+
+ mod good_headers {
+ use ::header_components;
+ def_headers! {
+ test_name: validate_header_names,
+ scope: header_components,
+ Subject, unchecked { "Subject" }, RawUnstructured, maxOne, None,
+ Comments, unchecked { "Comments" }, RawUnstructured, multi, None
+ }
+ }
+
+ mod bad_headers {
+ def_headers! {
+ test_name: validate_header_names,
+ scope: super,
+ Subject, unchecked { "Subject" }, OtherComponent, maxOne, None,
+ Comments, unchecked { "Comments" }, OtherComponent, multi, None
+ }
+ }
+ mod bad_headers2 {
+ def_headers! {
+ test_name: validate_header_names2,
+ scope: super,
+ Comments2, unchecked { "Comments" }, OtherComponent, maxOne, None
+ }
+ }
+
+ const TEXT_1: &str = "Random stuff XD";
+ const TEXT_2: &str = "Having a log of fun, yes a log!";
+
+ test!(max_one_mixup {
+ let headers = headers! {
+ BadComments2: (),
+ BadComments: ()
+ }?;
+
+ let res = headers.use_contextual_validators();
+ if let Err(HeaderValidationError::BuildIn(berr)) = res {
+ if let BuildInValidationError::MaxOneInconsistency { ..} = berr.get_context() {
+ return Ok(());
+ }
+ panic!("unexpected error: {:?}", berr);
+ }
+ panic!("unexpected result: {:?}", res);
+ });
+
+ #[test]
+ fn headers_macro() {
+ let headers = headers! {
+ Comments: TEXT_1,
+ Subject: TEXT_2
+ }.unwrap();
+
+
+ let count = headers
+ // all headers _could_ have multiple values, through neither
+ // ContentType nor Subject do have multiple value
+ .get(Comments)
+ .map(|h: Result<&Header<Comments>, HeaderTypeError>| {
+ let v = h.expect( "the trait object to be downcastable to Header<Comments>" );
+ assert_eq!(v.as_str(), TEXT_1);
+ })
+ .count();
+ assert_eq!(1, count);
+
+ let count = headers
+ .get(Subject)
+ .map(|h: Result<&Header<Subject>, HeaderTypeError>| {
+ let val = h.expect( "the trait object to be downcastable to Header<Subject>" );
+ assert_eq!(val.as_str(), TEXT_2);
+ })
+ .count();
+ assert_eq!(1, count);
+ }
+
+ #[test]
+ fn get_single() {
+ let headers = headers! {
+ Subject: "abc"
+ }.unwrap();
+
+ assert_eq!(
+ "abc",
+ headers.get_single(Subject)
+ .unwrap()//Some
+ .unwrap()//Result
+ .as_str()
+ );
+ }
+
+ #[test]
+ fn get_single_cast_error() {
+ let headers = headers! {
+ Subject: "abc"
+ }.unwrap();
+
+ let res = headers.get_single(BadSubject);
+ assert_err!( res.expect("where did the header go?") );
+ }
+
+ #[test]
+ fn get() {
+ let headers = headers! {
+ Subject: "abc",
+ Comments: "1st",
+ BadComments: ()
+ }.unwrap();
+
+
+ let mut res = headers.get(Comments);
+
+ assert_eq!(res.size_hint(), (2, Some(2)));
+
+ assert_eq!(
+ "1st",
+ assert_ok!(res.next().unwrap()).as_str()
+ );
+
+ assert_err!(res.next().unwrap());
+
+ assert!( res.next().is_none() )
+
+ }
+
+ #[test]
+ fn get_untyped() {
+ let headers = headers! {
+ Subject: "abc",
+ Comments: "1st",
+ BadComments: ()
+ }.unwrap();
+
+
+ let res = headers.get_untyped(Subject::name())
+ .map(|entry| entry.downcast_ref::<Subject>().unwrap().as_str() )
+ .collect::<Vec<_>>();
+
+ assert_eq!(
+ res.as_slice(),
+ &[ "abc" ]
+ );
+
+ let mut res = headers.get_untyped(Comments::name());
+
+ assert_eq!((2, Some(2)), res.size_hint());
+
+ assert_eq!(
+ res.next().unwrap().downcast_ref::<Comments>().unwrap().as_str(),
+ "1st"
+ );
+
+ assert_eq!((1, Some(1)), res.size_hint());
+
+ assert_eq!(
+ res.next().unwrap().downcast_ref::<BadComments>().unwrap().body(),
+ &OtherComponent
+ );
+
+ assert!(res.next().is_none());
+ }
+
+ #[test]
+ fn fmt_debug() {
+ let headers = headers! {
+ Subject: "hy there"
+ }.unwrap();
+
+ let res = format!("{:?}", headers);
+ assert_eq!(
+ "HeaderMap { Subject: RawUnstructured { text: Input(Owned(\"hy there\")) }, }",
+ res.as_str()
+ );
+ }
+
+ test!(combine_keeps_order {
+ let mut headers = headers! {
+ XComment: "ab@c"
+ }?;
+
+ headers.insert_all(headers! {
+ Subject: "hy there",
+ Comments: "magic+spell"
+ }?);
+
+ assert_eq!(
+ &[
+ "X-Comment",
+ "Subject",
+ "Comments"
+ ],
+ headers.into_iter()
+ .map(|(name, _val)| name.as_str())
+ .collect::<Vec<_>>()
+ .as_slice()
+ );
+ });
+
+
+ test!(remove_1 {
+ let mut headers = headers!{
+ Comments: "a",
+ Subject: "b",
+ Comments: "c",
+ Comments: "d"
+ }?;
+
+ assert_eq!( false, headers.remove(XComment::name()));
+ assert_eq!( true, headers.remove(Subject::name()));
+
+ assert_eq!( 3, headers.iter().count() );
+
+ let values = headers.get(Comments)
+ .map(|comp| comp.unwrap().as_str() )
+ .collect::<Vec<_>>();
+
+ assert_eq!(
+ &[ "a", "c", "d" ],
+ values.as_slice()
+ );
+ });
+
+ test!(remove_2 {
+ let mut headers = headers!{
+ Comments: "a",
+ Subject: "b",
+ Comments: "c",
+ Comments: "d"
+ }?;
+
+ assert_eq!(true, headers.remove(Comments::name()));
+ assert_eq!(false, headers.remove(Comments::name()));
+
+ assert_eq!(1, headers.iter().count());
+
+ let values = headers.get(Subject)
+ .map(|comp| comp.unwrap().as_str())
+ .collect::<Vec<_>>();
+
+ assert_eq!(
+ &[ "b" ],
+ values.as_slice()
+ );
+ });
+
+ #[derive(Default, Copy, Clone)]
+ struct XComment;
+ impl HeaderKind for XComment {
+ type Component = RawUnstructured;
+
+ fn name() -> HeaderName {
+ HeaderName::new(SoftAsciiStr::from_unchecked("X-Comment")).unwrap()
+ }
+
+ const VALIDATOR: Option<
+ fn(&HeaderMap)-> Result<(), HeaderValidationError>
+ > = Some(__validator);
+
+ const MAX_ONE: bool = false;
+ }
+
+ //some stupid but simple validator
+ fn __validator(map: &HeaderMap) -> Result<(), HeaderValidationError> {
+ if map.get_untyped(Comments::name()).len() != 0 {
+ return Err(HeaderValidationError::Custom(
+ Context::new("can't have X-Comment and Comments in same mail")
+ .into()
+ ));
+ }
+ Ok(())
+ }
+
+ test!(contains_works {
+ let map = headers! {
+ Subject: "soso"
+ }?;
+
+ assert_eq!( true, map.contains( Subject::name() ));
+ assert_eq!( true, map.contains( Subject ));
+ assert_eq!( false, map.contains( Comments::name() ));
+ assert_eq!( false, map.contains( Comments ));
+ });
+
+ test!(use_validator_ok {
+ let map = headers! {
+ XComment: "yay",
+ Subject: "soso"
+ }?;
+
+ assert_ok!(map.use_contextual_validators());
+ });
+
+ test!(use_validator_err {
+ let map = headers! {
+ XComment: "yay",
+ Comments: "oh no",
+ Subject: "soso"
+ }?;
+
+ assert_err!(map.use_contextual_validators());
+ });
+
+ test!(has_len {
+ let map = headers! {
+ XComment: "yay",
+ Comments: "oh no",
+ Subject: "soso"
+ }?;
+
+ assert_eq!(3, map.len());
+ });
+} \ No newline at end of file