use crate::{Error, Result}; use std::collections::HashMap; mod parsers; use parsers::get_header; pub use parsers::{Segment, Subject}; #[doc(hidden)] pub type HeaderMap = HashMap; /// Message ID of an email in a thread #[derive(Clone, Debug, Eq, PartialEq)] pub struct Id(pub String); impl Id { pub fn from(headers: &HeaderMap, key: &str) -> Result { get_header(&headers, key).and_then(|h| match h { Header::Single(h) => Ok(Self(h)), _ => Err(Error::FailedParsing), }) } } /// A semi typed header value for mail pub(crate) enum Header { /// A single header value Single(String), /// A set of values that was separated by `,` Multi(Vec), } impl Header { fn single(self) -> Result { match self { Self::Single(s) => Ok(s), Self::Multi(_) => Err(Error::FailedParsing), } } } /// A single patch, metadata and email body /// /// This type is constructed from a single email in a thread, and can /// then be combined into a [PatchSet](struct.PatchSet.html) via the /// [PatchTree](struct.PatchTree.html) builder. #[derive(Clone, Debug, Eq, PartialEq)] pub struct Patch<'mail> { pub id: Id, pub reply_to: Option, pub subject: Subject, pub headers: HeaderMap, pub raw: &'mail str, } impl<'mail> Patch<'mail> { #[doc(hidden)] pub fn preprocess(raw: &'mail str) -> HeaderMap { let mail = mailparse::parse_mail(raw.as_bytes()) .map_err(|_| Error::FailedParsing) .unwrap(); mail .headers .into_iter() .fold(HashMap::new(), |mut acc, header| { let key = header.get_key().unwrap(); let val = header.get_value().unwrap(); acc.insert(key, val); acc }) } pub fn new(raw: &'mail str) -> Result { let mail = mailparse::parse_mail(raw.as_bytes()) .map_err(|_| Error::FailedParsing)?; let headers = mail .headers .into_iter() .fold(HashMap::new(), |mut acc, header| { let key = header.get_key().unwrap(); let val = header.get_value().unwrap(); acc.insert(key, val); acc }); get_header(&headers, "X-Mailer").and_then(|h| match h { Header::Single(s) if s.contains("git-send-email") => Ok(()), _ => Err(Error::NotAGitMail), })?; Ok(Self { id: Id::from(&headers, "Message-Id")?, reply_to: Id::from(&headers, "In-Reply-To") .map(|id| Some(id)) .unwrap_or(None), subject: get_header(&headers, "Subject") .and_then(|h| h.single()) .and_then(|s| Subject::from(s))?, headers, raw, }) } }