Skip to main content

use_email_envelope/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
5
6use use_email_address::{AddressValidationError, EmailAddress};
7
8/// SMTP envelope address. This is transport identity, not a visible header mailbox.
9#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
10pub struct EnvelopeAddress(EmailAddress);
11
12impl EnvelopeAddress {
13    /// Creates an envelope address from address text.
14    pub fn new(value: impl AsRef<str>) -> Result<Self, AddressValidationError> {
15        Ok(Self(EmailAddress::new(value)?))
16    }
17
18    /// Creates an envelope address from a validated email address.
19    #[must_use]
20    pub const fn from_email_address(address: EmailAddress) -> Self {
21        Self(address)
22    }
23
24    /// Returns the validated email address.
25    #[must_use]
26    pub const fn email_address(&self) -> &EmailAddress {
27        &self.0
28    }
29}
30
31impl From<EmailAddress> for EnvelopeAddress {
32    fn from(value: EmailAddress) -> Self {
33        Self(value)
34    }
35}
36
37impl fmt::Display for EnvelopeAddress {
38    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
39        write!(formatter, "{}", self.0)
40    }
41}
42
43/// SMTP reverse-path, including the null reverse-path.
44#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
45pub enum ReversePath {
46    /// Null reverse-path, rendered as `<>`.
47    Null,
48    /// Address reverse-path.
49    Address(EnvelopeAddress),
50}
51
52impl ReversePath {
53    /// Creates an address reverse-path.
54    pub fn new(value: impl AsRef<str>) -> Result<Self, AddressValidationError> {
55        Ok(Self::Address(EnvelopeAddress::new(value)?))
56    }
57
58    /// Creates a null reverse-path.
59    #[must_use]
60    pub const fn null() -> Self {
61        Self::Null
62    }
63}
64
65impl fmt::Display for ReversePath {
66    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
67        match self {
68            Self::Null => formatter.write_str("<>"),
69            Self::Address(address) => write!(formatter, "<{address}>"),
70        }
71    }
72}
73
74/// SMTP forward-path.
75#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
76pub struct ForwardPath(EnvelopeAddress);
77
78impl ForwardPath {
79    /// Creates a forward-path from address text.
80    pub fn new(value: impl AsRef<str>) -> Result<Self, AddressValidationError> {
81        Ok(Self(EnvelopeAddress::new(value)?))
82    }
83
84    /// Creates a forward-path from a validated envelope address.
85    #[must_use]
86    pub const fn from_address(address: EnvelopeAddress) -> Self {
87        Self(address)
88    }
89
90    /// Returns the envelope address.
91    #[must_use]
92    pub const fn address(&self) -> &EnvelopeAddress {
93        &self.0
94    }
95}
96
97impl fmt::Display for ForwardPath {
98    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
99        write!(formatter, "<{}>", self.0)
100    }
101}
102
103/// MAIL FROM path wrapper.
104#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
105pub struct MailFromPath(ReversePath);
106
107impl MailFromPath {
108    /// Creates a MAIL FROM path from address text.
109    pub fn new(value: impl AsRef<str>) -> Result<Self, AddressValidationError> {
110        Ok(Self(ReversePath::new(value)?))
111    }
112
113    /// Creates a null MAIL FROM path.
114    #[must_use]
115    pub const fn null() -> Self {
116        Self(ReversePath::Null)
117    }
118
119    /// Returns the reverse-path.
120    #[must_use]
121    pub const fn reverse_path(&self) -> &ReversePath {
122        &self.0
123    }
124}
125
126impl fmt::Display for MailFromPath {
127    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
128        write!(formatter, "{}", self.0)
129    }
130}
131
132/// RCPT TO path wrapper.
133#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
134pub struct RcptToPath(ForwardPath);
135
136impl RcptToPath {
137    /// Creates an RCPT TO path from address text.
138    pub fn new(value: impl AsRef<str>) -> Result<Self, AddressValidationError> {
139        Ok(Self(ForwardPath::new(value)?))
140    }
141
142    /// Creates an RCPT TO path from a validated forward-path.
143    #[must_use]
144    pub const fn from_forward_path(path: ForwardPath) -> Self {
145        Self(path)
146    }
147
148    /// Returns the forward-path.
149    #[must_use]
150    pub const fn forward_path(&self) -> &ForwardPath {
151        &self.0
152    }
153}
154
155impl fmt::Display for RcptToPath {
156    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
157        write!(formatter, "{}", self.0)
158    }
159}
160
161/// Envelope sender path.
162#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
163pub struct EnvelopeSender(MailFromPath);
164
165impl EnvelopeSender {
166    /// Creates an envelope sender from address text.
167    pub fn new(value: impl AsRef<str>) -> Result<Self, AddressValidationError> {
168        Ok(Self(MailFromPath::new(value)?))
169    }
170
171    /// Creates a null envelope sender.
172    #[must_use]
173    pub const fn null() -> Self {
174        Self(MailFromPath::null())
175    }
176
177    /// Returns the MAIL FROM path.
178    #[must_use]
179    pub const fn path(&self) -> &MailFromPath {
180        &self.0
181    }
182}
183
184impl fmt::Display for EnvelopeSender {
185    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
186        write!(formatter, "{}", self.0)
187    }
188}
189
190/// Envelope recipient path.
191#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
192pub struct EnvelopeRecipient(RcptToPath);
193
194impl EnvelopeRecipient {
195    /// Creates an envelope recipient from address text.
196    pub fn new(value: impl AsRef<str>) -> Result<Self, AddressValidationError> {
197        Ok(Self(RcptToPath::new(value)?))
198    }
199
200    /// Creates an envelope recipient from a validated address.
201    #[must_use]
202    pub fn from_email_address(address: EmailAddress) -> Self {
203        Self(RcptToPath::from_forward_path(ForwardPath::from_address(
204            EnvelopeAddress::from(address),
205        )))
206    }
207
208    /// Returns the RCPT TO path.
209    #[must_use]
210    pub const fn path(&self) -> &RcptToPath {
211        &self.0
212    }
213}
214
215impl fmt::Display for EnvelopeRecipient {
216    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
217        write!(formatter, "{}", self.0)
218    }
219}
220
221/// SMTP envelope with transport sender and recipients.
222#[derive(Clone, Debug, Eq, PartialEq)]
223pub struct Envelope {
224    sender: EnvelopeSender,
225    recipients: Vec<EnvelopeRecipient>,
226}
227
228impl Envelope {
229    /// Creates an envelope with no recipients.
230    #[must_use]
231    pub const fn new(sender: EnvelopeSender) -> Self {
232        Self {
233            sender,
234            recipients: Vec::new(),
235        }
236    }
237
238    /// Adds a recipient and returns the updated envelope.
239    #[must_use]
240    pub fn with_recipient(mut self, recipient: EnvelopeRecipient) -> Self {
241        self.recipients.push(recipient);
242        self
243    }
244
245    /// Appends a recipient.
246    pub fn push_recipient(&mut self, recipient: EnvelopeRecipient) {
247        self.recipients.push(recipient);
248    }
249
250    /// Returns the envelope sender.
251    #[must_use]
252    pub const fn sender(&self) -> &EnvelopeSender {
253        &self.sender
254    }
255
256    /// Returns envelope recipients.
257    #[must_use]
258    pub fn recipients(&self) -> &[EnvelopeRecipient] {
259        &self.recipients
260    }
261}
262
263#[cfg(test)]
264mod tests {
265    use super::{Envelope, EnvelopeRecipient, EnvelopeSender, ReversePath};
266    use use_email_address::AddressValidationError;
267
268    #[test]
269    fn models_envelope_identity() -> Result<(), AddressValidationError> {
270        let envelope = Envelope::new(EnvelopeSender::new("bounce@example.com")?)
271            .with_recipient(EnvelopeRecipient::new("jane@example.com")?);
272
273        assert_eq!(envelope.sender().to_string(), "<bounce@example.com>");
274        assert_eq!(envelope.recipients()[0].to_string(), "<jane@example.com>");
275        assert_eq!(ReversePath::null().to_string(), "<>");
276        Ok(())
277    }
278}