aboutsummaryrefslogtreecommitdiff
path: root/libgitmail/src/patch/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'libgitmail/src/patch/mod.rs')
-rw-r--r--libgitmail/src/patch/mod.rs103
1 files changed, 103 insertions, 0 deletions
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<String, String>;
+
+/// 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<Self> {
+ 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<String>),
+}
+
+impl Header {
+ fn single(self) -> Result<String> {
+ 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<Id>,
+ 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<Self> {
+ 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,
+ })
+ }
+}