know/classes/
email_message.rs

1// This is free and unencumbered software released into the public domain.
2
3use crate::{
4    datatypes::{DateTime, EmailAddress, EmailMessageId},
5    formatters::{
6        DisplayConcise, DisplayDetailed, DisplayInline, DisplayJsonLd, DisplayMime, DisplayOneliner,
7    },
8};
9use alloc::fmt;
10
11/// See: https://datatracker.ietf.org/doc/html/rfc5322#section-3.6
12#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
13#[cfg_attr(feature = "serde", cfg_eval::cfg_eval, serde_with::serde_as)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15#[cfg_attr(feature = "serde", serde(default, rename_all = "camelCase"))]
16pub struct EmailMessage {
17    /// See: https://datatracker.ietf.org/doc/html/draft-ietf-emailcore-rfc5322bis-12#name-the-origination-date-field
18    pub date: DateTime,
19
20    /// See: https://datatracker.ietf.org/doc/html/draft-ietf-emailcore-rfc5322bis-12#name-originator-fields
21    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Vec::is_empty"))]
22    pub from: Vec<EmailAddress>,
23
24    /// See: https://datatracker.ietf.org/doc/html/draft-ietf-emailcore-rfc5322bis-12#name-originator-fields
25    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
26    pub sender: Option<EmailAddress>,
27
28    /// See: https://datatracker.ietf.org/doc/html/draft-ietf-emailcore-rfc5322bis-12#name-originator-fields
29    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Vec::is_empty"))]
30    pub reply_to: Vec<EmailAddress>,
31
32    /// See: https://datatracker.ietf.org/doc/html/draft-ietf-emailcore-rfc5322bis-12#name-destination-address-fields
33    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Vec::is_empty"))]
34    pub to: Vec<EmailAddress>,
35
36    /// See: https://datatracker.ietf.org/doc/html/draft-ietf-emailcore-rfc5322bis-12#name-destination-address-fields
37    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Vec::is_empty"))]
38    pub cc: Vec<EmailAddress>,
39
40    /// See: https://datatracker.ietf.org/doc/html/draft-ietf-emailcore-rfc5322bis-12#name-destination-address-fields
41    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Vec::is_empty"))]
42    pub bcc: Vec<EmailAddress>,
43
44    /// See: https://datatracker.ietf.org/doc/html/draft-ietf-emailcore-rfc5322bis-12#name-informational-fields
45    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
46    pub subject: Option<String>,
47
48    /// See: https://datatracker.ietf.org/doc/html/draft-ietf-emailcore-rfc5322bis-12#section-3.6.4
49    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
50    pub id: Option<EmailMessageId>,
51
52    /// See: https://datatracker.ietf.org/doc/html/draft-ietf-emailcore-rfc5322bis-12#section-3.6.4
53    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Vec::is_empty"))]
54    pub in_reply_to: Vec<EmailMessageId>,
55
56    /// See: https://datatracker.ietf.org/doc/html/draft-ietf-emailcore-rfc5322bis-12#section-3.6.4
57    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Vec::is_empty"))]
58    pub references: Vec<EmailMessageId>,
59
60    /// See: https://datatracker.ietf.org/doc/html/draft-ietf-emailcore-rfc5322bis-12#name-body
61    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
62    pub body: Option<String>,
63}
64
65impl EmailMessage {
66    pub fn inline(&self) -> DisplayInline<EmailMessage> {
67        DisplayInline(self)
68    }
69
70    pub fn oneliner(&self) -> DisplayOneliner<EmailMessage> {
71        DisplayOneliner(self)
72    }
73
74    pub fn concise(&self) -> DisplayConcise<EmailMessage> {
75        DisplayConcise(self)
76    }
77
78    pub fn detailed(&self) -> DisplayDetailed<EmailMessage> {
79        DisplayDetailed(self)
80    }
81
82    pub fn mime(&self) -> DisplayMime<EmailMessage> {
83        DisplayMime(self)
84    }
85
86    pub fn jsonld(&self) -> DisplayJsonLd<EmailMessage> {
87        DisplayJsonLd(self)
88    }
89}
90
91impl fmt::Display for EmailMessage {
92    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
93        writeln!(f, "{}", self.oneliner())
94    }
95}
96
97impl fmt::Display for DisplayInline<'_, EmailMessage> {
98    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
99        write!(f, "{}", self.0.date)?;
100        for addr in &self.0.from {
101            write!(f, " {}:", addr.as_str())?;
102        }
103        if let Some(ref subject) = self.0.subject {
104            write!(f, " {}", subject)?;
105        }
106        Ok(())
107    }
108}
109
110impl fmt::Display for DisplayOneliner<'_, EmailMessage> {
111    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
112        writeln!(f, "{}", self.0.inline())
113    }
114}
115
116impl fmt::Display for DisplayConcise<'_, EmailMessage> {
117    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
118        if let Some(ref subject) = self.0.subject {
119            writeln!(f, "✉️  {}", subject)?;
120        }
121        writeln!(f, "\tDate: {}", self.0.date.inline())?;
122        if !self.0.from.is_empty() {
123            writeln!(
124                f,
125                "\tFrom: {}",
126                &self
127                    .0
128                    .from
129                    .iter()
130                    .map(|addr| format!("{}", addr.inline()))
131                    .collect::<Vec<String>>()
132                    .join(", ")
133            )?;
134        }
135        if !self.0.to.is_empty() {
136            writeln!(
137                f,
138                "\tTo: {}",
139                &self
140                    .0
141                    .to
142                    .iter()
143                    .map(|addr| format!("{}", addr.inline()))
144                    .collect::<Vec<String>>()
145                    .join(", ")
146            )?;
147        }
148        if !self.0.cc.is_empty() {
149            writeln!(
150                f,
151                "\tCc: {}",
152                &self
153                    .0
154                    .cc
155                    .iter()
156                    .map(|addr| format!("{}", addr.inline()))
157                    .collect::<Vec<String>>()
158                    .join(", ")
159            )?;
160        }
161        if !self.0.bcc.is_empty() {
162            writeln!(
163                f,
164                "\tBcc: {}",
165                &self
166                    .0
167                    .bcc
168                    .iter()
169                    .map(|addr| format!("{}", addr.inline()))
170                    .collect::<Vec<String>>()
171                    .join(", ")
172            )?;
173        }
174        if let Some(ref id) = self.0.id {
175            writeln!(f, "\tMessage-ID: {}", id.inline())?;
176        }
177        Ok(())
178    }
179}
180
181impl fmt::Display for DisplayDetailed<'_, EmailMessage> {
182    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
183        if let Some(ref subject) = self.0.subject {
184            writeln!(f, "✉️  {}", subject)?;
185        }
186        writeln!(f, "\tDate: {}", self.0.date.inline())?;
187        for addr in &self.0.from {
188            writeln!(f, "\tFrom: {}", addr.inline())?;
189        }
190        for addr in &self.0.to {
191            writeln!(f, "\tTo: {}", addr.inline())?;
192        }
193        for addr in &self.0.cc {
194            writeln!(f, "\tCc: {}", addr.inline())?;
195        }
196        for addr in &self.0.bcc {
197            writeln!(f, "\tBcc: {}", addr.inline())?;
198        }
199        if let Some(ref id) = self.0.id {
200            writeln!(f, "\tMessage-ID: {}", id.inline())?;
201        }
202        for in_reply_to in &self.0.in_reply_to {
203            writeln!(f, "\tIn-Reply-To: {}", in_reply_to.inline())?;
204        }
205        for references in &self.0.references {
206            writeln!(f, "\tReferences: {}", references.inline())?;
207        }
208        Ok(())
209    }
210}
211
212impl fmt::Display for DisplayMime<'_, EmailMessage> {
213    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
214        writeln!(f, "Date: {}", self.0.date.mime())?;
215        for addr in &self.0.from {
216            writeln!(f, "From: {}", addr.as_str())?;
217        }
218        for addr in &self.0.to {
219            writeln!(f, "To: {}", addr.as_str())?;
220        }
221        for addr in &self.0.cc {
222            writeln!(f, "Cc: {}", addr.as_str())?;
223        }
224        for addr in &self.0.bcc {
225            writeln!(f, "Bcc: {}", addr.as_str())?;
226        }
227        if let Some(ref subject) = self.0.subject {
228            writeln!(f, "Subject: {}", subject)?;
229        }
230        if let Some(ref id) = self.0.id {
231            writeln!(f, "Message-ID: <{}>", id.as_str())?;
232        }
233        for in_reply_to in &self.0.in_reply_to {
234            writeln!(f, "In-Reply-To: {}", in_reply_to.inline())?;
235        }
236        for references in &self.0.references {
237            writeln!(f, "References: {}", references.inline())?;
238        }
239        if let Some(ref body) = self.0.body {
240            writeln!(f)?;
241            writeln!(f, "{}", body)?;
242        }
243        Ok(())
244    }
245}
246
247#[cfg(feature = "tldr")]
248impl tldr::Tldr for EmailMessage {
249    type Error = Box<dyn core::error::Error>;
250
251    fn what(&self, ctx: &tldr::TldrContext) -> tldr::TldrResult<String, Self::Error> {
252        use core::fmt::Write;
253        use tldr::TldrLanguage::*;
254        Ok(match ctx.language {
255            English => {
256                let timespan = DateTime::now().since(&self.date)?.round()?;
257
258                let mut tldr = String::new();
259                write!(tldr, "An email message dated {timespan} ago")?;
260                if let Some(from) = &self.from.first() {
261                    write!(tldr, ", from {}", from)?;
262                }
263                if !self.to.is_empty() {
264                    write!(tldr, ", addressed to ")?;
265                    for (i, addr) in self.to.iter().enumerate() {
266                        if i > 0 {
267                            write!(tldr, ", ")?;
268                        }
269                        write!(tldr, "{}", addr)?;
270                    }
271                }
272                if let Some(subject) = &self.subject {
273                    write!(tldr, ", with the subject \"{}\"", subject)?;
274                }
275                Some(tldr)
276            },
277            _ => None,
278        })
279    }
280}
281
282#[cfg(feature = "imap-proto")]
283include!("email_message/imap_proto.rs");
284
285#[cfg(feature = "mail-parser")]
286include!("email_message/mail_parser.rs");
287
288#[cfg(feature = "maildir")]
289include!("email_message/maildir.rs");
290
291#[cfg(feature = "mailparse")]
292include!("email_message/mailparse.rs");
293
294#[cfg(feature = "serde")]
295include!("email_message/serde.rs");