lettre/transport/stub/
mod.rs

1//! The stub transport logs message envelopes as well as contents. It can be useful for testing
2//! purposes.
3//!
4//! # Stub Transport
5//!
6//! The stub transport logs message envelopes as well as contents. It can be useful for testing
7//! purposes.
8//!
9//! # Examples
10//!
11//! ```rust
12//! # #[cfg(feature = "builder")]
13//! # {
14//! use lettre::{
15//!     message::header::ContentType, transport::stub::StubTransport, Message, Transport,
16//! };
17//!
18//! # use std::error::Error;
19//! # fn try_main() -> Result<(), Box<dyn Error>> {
20//! let email = Message::builder()
21//!     .from("NoBody <nobody@domain.tld>".parse()?)
22//!     .reply_to("Yuin <yuin@domain.tld>".parse()?)
23//!     .to("Hei <hei@domain.tld>".parse()?)
24//!     .subject("Happy new year")
25//!     .header(ContentType::TEXT_PLAIN)
26//!     .body(String::from("Be happy!"))?;
27//!
28//! let mut sender = StubTransport::new_ok();
29//! sender.send(&email)?;
30//! assert_eq!(
31//!     sender.messages(),
32//!     vec![(
33//!         email.envelope().clone(),
34//!         String::from_utf8(email.formatted()).unwrap()
35//!     )],
36//! );
37//! # Ok(())
38//! # }
39//! # try_main().unwrap();
40//! # }
41//! ```
42
43use std::{
44    error::Error as StdError,
45    fmt,
46    sync::{Arc, Mutex},
47};
48
49#[cfg(any(feature = "tokio1", feature = "async-std1"))]
50use async_trait::async_trait;
51
52#[cfg(any(feature = "tokio1", feature = "async-std1"))]
53use crate::AsyncTransport;
54use crate::{address::Envelope, Transport};
55
56/// An error returned by the stub transport
57#[non_exhaustive]
58#[derive(Debug, Copy, Clone)]
59pub struct Error;
60
61impl fmt::Display for Error {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        f.write_str("stub error")
64    }
65}
66
67impl StdError for Error {}
68
69/// This transport logs messages and always returns the given response
70#[derive(Debug, Clone)]
71pub struct StubTransport {
72    response: Result<(), Error>,
73    message_log: Arc<Mutex<Vec<(Envelope, String)>>>,
74}
75
76/// This transport logs messages and always returns the given response
77#[derive(Debug, Clone)]
78#[cfg(any(feature = "tokio1", feature = "async-std1"))]
79#[cfg_attr(docsrs, doc(cfg(any(feature = "tokio1", feature = "async-std1"))))]
80pub struct AsyncStubTransport {
81    response: Result<(), Error>,
82    message_log: Arc<Mutex<Vec<(Envelope, String)>>>,
83}
84
85impl StubTransport {
86    /// Creates a new transport that always returns the given Result
87    pub fn new(response: Result<(), Error>) -> Self {
88        Self {
89            response,
90            message_log: Arc::new(Mutex::new(vec![])),
91        }
92    }
93
94    /// Creates a new transport that always returns a success response
95    pub fn new_ok() -> Self {
96        Self {
97            response: Ok(()),
98            message_log: Arc::new(Mutex::new(vec![])),
99        }
100    }
101
102    /// Creates a new transport that always returns an error
103    pub fn new_error() -> Self {
104        Self {
105            response: Err(Error),
106            message_log: Arc::new(Mutex::new(vec![])),
107        }
108    }
109
110    /// Return all logged messages sent using [`Transport::send_raw`]
111    pub fn messages(&self) -> Vec<(Envelope, String)> {
112        self.message_log
113            .lock()
114            .expect("Couldn't acquire lock to write message log")
115            .clone()
116    }
117}
118
119#[cfg(any(feature = "async-std1", feature = "tokio1"))]
120impl AsyncStubTransport {
121    /// Creates a new transport that always returns the given Result
122    pub fn new(response: Result<(), Error>) -> Self {
123        Self {
124            response,
125            message_log: Arc::new(Mutex::new(vec![])),
126        }
127    }
128
129    /// Creates a new transport that always returns a success response
130    pub fn new_ok() -> Self {
131        Self {
132            response: Ok(()),
133            message_log: Arc::new(Mutex::new(vec![])),
134        }
135    }
136
137    /// Creates a new transport that always returns an error
138    pub fn new_error() -> Self {
139        Self {
140            response: Err(Error),
141            message_log: Arc::new(Mutex::new(vec![])),
142        }
143    }
144
145    /// Return all logged messages sent using [`AsyncTransport::send_raw`]
146    #[cfg(any(feature = "tokio1", feature = "async-std1"))]
147    pub async fn messages(&self) -> Vec<(Envelope, String)> {
148        self.message_log.lock().unwrap().clone()
149    }
150}
151
152impl Transport for StubTransport {
153    type Ok = ();
154    type Error = Error;
155
156    fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
157        self.message_log
158            .lock()
159            .expect("Couldn't acquire lock to write message log")
160            .push((envelope.clone(), String::from_utf8_lossy(email).into()));
161        self.response
162    }
163}
164
165#[cfg(any(feature = "tokio1", feature = "async-std1"))]
166#[async_trait]
167impl AsyncTransport for AsyncStubTransport {
168    type Ok = ();
169    type Error = Error;
170
171    async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
172        self.message_log
173            .lock()
174            .unwrap()
175            .push((envelope.clone(), String::from_utf8_lossy(email).into()));
176        self.response
177    }
178}