aboutsummaryrefslogtreecommitdiff
path: root/libgitmail/src/patch/mod.rs
blob: b4b09a4b8de4898fe7a84e28b05326a17e305ce6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
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,
    })
  }
}