From fdb464b380dcda0f9afeee080ebe988e3934e02b Mon Sep 17 00:00:00 2001 From: Katharina Fey Date: Thu, 9 Jan 2020 21:30:25 +0000 Subject: Refactoring libgitmail core to work on complete emails, with tests --- libgitmail/src/patch/mod.rs | 103 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 libgitmail/src/patch/mod.rs (limited to 'libgitmail/src/patch/mod.rs') diff --git a/libgitmail/src/patch/mod.rs b/libgitmail/src/patch/mod.rs new file mode 100644 index 0000000..b4b09a4 --- /dev/null +++ b/libgitmail/src/patch/mod.rs @@ -0,0 +1,103 @@ +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, + }) + } +} -- cgit v1.2.3