Compare commits

...

14 Commits

Author SHA1 Message Date
Paolo Barbolini
12dccd2bbe Prepare v0.11.19 (#1115) 2025-10-08 19:23:57 +02:00
Leo-Tinkeam
7899da9672 docs: fix readme example (#1114)
Signed-off-by: Léo-Tinkeam <sarcyleo@gmail.com>
2025-10-01 22:07:44 +02:00
Paolo Barbolini
a85fdefe6f build(deps): upgrade semver compatible dependencies (#1113) 2025-10-01 15:24:41 +02:00
Paolo Barbolini
b73611c67f refactor: replace static_assert! with std assert! (#1112) 2025-10-01 14:45:08 +02:00
Norbiros
55cea7dbe6 feat: add method to set raw custom headers in MessageBuilder (#1108)
Closes #661
2025-10-01 14:32:57 +02:00
Paolo Barbolini
d2b9d50000 build(deps): upgrade semver compatible dependencies (#1103) 2025-08-10 17:16:37 +00:00
Paolo Barbolini
3d16344d53 Prepare v0.11.18 (#1102) 2025-07-28 09:44:38 +02:00
Francesco Luzzi
8873153178 feat: add ability to give a name to an inline attachment (#1101) 2025-07-21 08:19:31 +02:00
Paolo Barbolini
b073df7666 build(deps): upgrade socket2 to v0.6 (#1098) 2025-07-05 12:09:17 +02:00
Paolo Barbolini
cf6b767a9c Prepare v0.11.17 (#1093) 2025-06-05 21:41:17 +02:00
Paolo Barbolini
d3d8e24824 feat: add rustls-platform-verifier support (#1081) 2025-06-02 10:43:17 +02:00
Paolo Barbolini
c4df9730aa refactor(smtp/pool): remove duplicate abort_concurrent implementation (#1092) 2025-05-24 16:37:54 +02:00
Paolo Barbolini
bfed19e6ad refactor(stub): always use std Mutex (#1091) 2025-05-24 14:34:09 +00:00
David Campbell
629967ac98 docs: use Mailbox::new rather than string parsing (#1090) 2025-05-24 16:21:15 +02:00
11 changed files with 755 additions and 468 deletions

View File

@@ -1,3 +1,51 @@
<a name="v0.11.19"></a>
### v0.11.19 (2025-10-08)
#### Features
* Add raw header setter to `MessageBuilder` ([#1108])
#### Misc
* Fix README example ([#1114])
* Replace custom `static_assert!` macro with `std::assert!` ([#1112])
[#1108]: https://github.com/lettre/lettre/pull/1108
[#1112]: https://github.com/lettre/lettre/pull/1112
[#1114]: https://github.com/lettre/lettre/pull/1114
<a name="v0.11.18"></a>
### v0.11.18 (2025-07-28)
#### Features
* Allow inline attachments to be named ([#1101])
#### Misc
* Upgrade `socket2` to v0.6 ([#1098])
[#1098]: https://github.com/lettre/lettre/pull/1098
[#1101]: https://github.com/lettre/lettre/pull/1101
<a name="v0.11.17"></a>
### v0.11.17 (2025-06-06)
#### Features
* Add support for `rustls-platform-verifier` ([#1081])
#### Misc
* Change readme example to use `Mailbox::new` instead of string parsing ([#1090])
* Replace futures-util `Mutex` with std `Mutex` in `AsyncStubTransport` ([#1091])
* Avoid duplicate `abort_concurrent` implementation ([#1092])
[#1081]: https://github.com/lettre/lettre/pull/1081
[#1090]: https://github.com/lettre/lettre/pull/1090
[#1091]: https://github.com/lettre/lettre/pull/1091
[#1092]: https://github.com/lettre/lettre/pull/1092
<a name="v0.11.16"></a> <a name="v0.11.16"></a>
### v0.11.16 (2025-05-12) ### v0.11.16 (2025-05-12)

934
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
[package] [package]
name = "lettre" name = "lettre"
# remember to update html_root_url and README.md (Cargo.toml example and deps.rs badge) # remember to update html_root_url and README.md (Cargo.toml example and deps.rs badge)
version = "0.11.16" version = "0.11.19"
description = "Email client" description = "Email client"
readme = "README.md" readme = "README.md"
homepage = "https://lettre.rs" homepage = "https://lettre.rs"
@@ -42,13 +42,14 @@ serde_json = { version = "1", optional = true }
# smtp-transport # smtp-transport
nom = { version = "8", optional = true } nom = { version = "8", optional = true }
hostname = { version = "0.4", optional = true } # feature hostname = { version = "0.4", optional = true } # feature
socket2 = { version = "0.5.1", optional = true } socket2 = { version = "0.6", optional = true }
url = { version = "2.4", optional = true } url = { version = "2.4", optional = true }
percent-encoding = { version = "2.3", optional = true } percent-encoding = { version = "2.3", optional = true }
## tls ## tls
native-tls = { version = "0.2.9", optional = true } # feature native-tls = { version = "0.2.9", optional = true } # feature
rustls = { version = "0.23.18", default-features = false, features = ["logging", "std", "tls12"], optional = true } rustls = { version = "0.23.18", default-features = false, features = ["logging", "std", "tls12"], optional = true }
rustls-platform-verifier = { version = "0.6.0", optional = true }
rustls-native-certs = { version = "0.8", optional = true } rustls-native-certs = { version = "0.8", optional = true }
webpki-roots = { version = "1.0.0", optional = true } webpki-roots = { version = "1.0.0", optional = true }
boring = { version = "4", optional = true } boring = { version = "4", optional = true }

View File

@@ -28,8 +28,8 @@
</div> </div>
<div align="center"> <div align="center">
<a href="https://deps.rs/crate/lettre/0.11.16"> <a href="https://deps.rs/crate/lettre/0.11.19">
<img src="https://deps.rs/crate/lettre/0.11.16/status.svg" <img src="https://deps.rs/crate/lettre/0.11.19/status.svg"
alt="dependency status" /> alt="dependency status" />
</a> </a>
</div> </div>
@@ -67,15 +67,15 @@ lettre = "0.11"
``` ```
```rust,no_run ```rust,no_run
use lettre::message::header::ContentType; use lettre::message::{Mailbox, header::ContentType};
use lettre::transport::smtp::authentication::Credentials; use lettre::transport::smtp::authentication::Credentials;
use lettre::{Message, SmtpTransport, Transport}; use lettre::{Message, SmtpTransport, Transport};
fn main() { fn main() {
let email = Message::builder() let email = Message::builder()
.from("NoBody <nobody@domain.tld>".parse().unwrap()) .from(Mailbox::new(Some("NoBody".to_owned()), "nobody@domain.tld".parse().unwrap()))
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap()) .reply_to(Mailbox::new(Some("Yuin".to_owned()), "yuin@domain.tld".parse().unwrap()))
.to("Hei <hei@domain.tld>".parse().unwrap()) .to(Mailbox::new(Some("Hei".to_owned()), "hei@domain.tld".parse().unwrap()))
.subject("Happy new year") .subject("Happy new year")
.header(ContentType::TEXT_PLAIN) .header(ContentType::TEXT_PLAIN)
.body(String::from("Be happy!")) .body(String::from("Be happy!"))

View File

@@ -93,17 +93,20 @@
//! When the `rustls` feature is enabled, one of the following verification backends //! When the `rustls` feature is enabled, one of the following verification backends
//! MUST also be enabled. //! MUST also be enabled.
//! //!
//! * **rustls-native-certs**: verify TLS certificates using the platform's native certificate store (see [`rustls-native-certs`]) //! * **rustls-platform-verifier**: verify TLS certificate using the OS's native certificate store (see [`rustls-platform-verifier`])
//! * **rustls-native-certs**: verify TLS certificates using the platform's native certificate store (see [`rustls-native-certs`]) - when in doubt use `rustls-platform-verifier`
//! * **webpki-roots**: verify TLS certificates against Mozilla's root certificates (see [`webpki-roots`]) //! * **webpki-roots**: verify TLS certificates against Mozilla's root certificates (see [`webpki-roots`])
//! //!
//! For the `rustls-native-certs` backend to work correctly, the following packages //! The following packages will need to be installed in order for the build
//! will need to be installed in order for the build stage and the compiled program //! stage and the compiled program to run properly.
//! to run properly.
//! //!
//! | Distro | Build-time packages | Runtime packages | //! | Verification backend | Distro | Build-time packages | Runtime packages |
//! | ------------ | -------------------------- | ---------------------------- | //! | --------------------- | ------------ | -------------------------- | ---------------------------- |
//! | Debian | none | `ca-certificates` | //! | `rustls-platform-verifier` | Debian | none | `ca-certificates` |
//! | Alpine Linux | none | `ca-certificates` | //! | `rustls-platform-verifier` | Alpine Linux | none | `ca-certificates` |
//! | `rustls-native-certs` | Debian | none | `ca-certificates` |
//! | `rustls-native-certs` | Alpine Linux | none | `ca-certificates` |
//! | `webpki-roots` | any | none | none |
//! //!
//! ### Sendmail transport //! ### Sendmail transport
//! //!
@@ -151,6 +154,7 @@
//! [AWS-LC]: https://github.com/aws/aws-lc //! [AWS-LC]: https://github.com/aws/aws-lc
//! [`aws-lc-rs`]: https://crates.io/crates/aws-lc-rs //! [`aws-lc-rs`]: https://crates.io/crates/aws-lc-rs
//! [`ring`]: https://crates.io/crates/ring //! [`ring`]: https://crates.io/crates/ring
//! [`rustls-platform-verifier`]: https://crates.io/crates/rustls-platform-verifier
//! [`rustls-native-certs`]: https://crates.io/crates/rustls-native-certs //! [`rustls-native-certs`]: https://crates.io/crates/rustls-native-certs
//! [`webpki-roots`]: https://crates.io/crates/webpki-roots //! [`webpki-roots`]: https://crates.io/crates/webpki-roots
//! [Tokio 1.x]: https://docs.rs/tokio/1 //! [Tokio 1.x]: https://docs.rs/tokio/1
@@ -158,7 +162,7 @@
//! [mime 0.3]: https://docs.rs/mime/0.3 //! [mime 0.3]: https://docs.rs/mime/0.3
//! [DKIM]: https://datatracker.ietf.org/doc/html/rfc6376 //! [DKIM]: https://datatracker.ietf.org/doc/html/rfc6376
#![doc(html_root_url = "https://docs.rs/crate/lettre/0.11.16")] #![doc(html_root_url = "https://docs.rs/crate/lettre/0.11.19")]
#![doc(html_favicon_url = "https://lettre.rs/favicon.ico")] #![doc(html_favicon_url = "https://lettre.rs/favicon.ico")]
#![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/15113230?v=4")] #![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/15113230?v=4")]
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
@@ -208,12 +212,13 @@ mod compiletime_checks {
#[cfg(all( #[cfg(all(
feature = "rustls", feature = "rustls",
not(feature = "rustls-platform-verifier"),
not(feature = "rustls-native-certs"), not(feature = "rustls-native-certs"),
not(feature = "webpki-roots") not(feature = "webpki-roots")
))] ))]
compile_error!( compile_error!(
"feature `rustls` also requires either the `rustls-native-certs` or the `webpki-roots` feature to "feature `rustls` also requires either the `rustls-platform-verifier`, the `rustls-native-certs`
be enabled" or the `webpki-roots` feature to be enabled"
); );
#[cfg(all(feature = "native-tls", feature = "boring-tls"))] #[cfg(all(feature = "native-tls", feature = "boring-tls"))]

View File

@@ -16,7 +16,10 @@ enum Disposition {
/// File name /// File name
Attached(String), Attached(String),
/// Content id /// Content id
Inline(String), Inline {
content_id: String,
name: Option<String>,
},
} }
impl Attachment { impl Attachment {
@@ -81,7 +84,50 @@ impl Attachment {
/// ``` /// ```
pub fn new_inline(content_id: String) -> Self { pub fn new_inline(content_id: String) -> Self {
Attachment { Attachment {
disposition: Disposition::Inline(content_id), disposition: Disposition::Inline {
content_id,
name: None,
},
}
}
/// Create a new inline attachment giving it a name
///
/// This attachment should be displayed inline into the message
/// body:
///
/// ```html
/// <img src="cid:123">
/// ```
///
///
/// ```rust
/// # use std::error::Error;
/// use std::fs;
///
/// use lettre::message::{header::ContentType, Attachment};
///
/// # fn main() -> Result<(), Box<dyn Error>> {
/// let content_id = String::from("123");
/// let file_name = String::from("image.jpg");
/// # if false {
/// let filebody = fs::read(&file_name)?;
/// # }
/// # let filebody = fs::read("docs/lettre.png")?;
/// let content_type = ContentType::parse("image/jpeg").unwrap();
/// let attachment =
/// Attachment::new_inline_with_name(content_id, file_name).body(filebody, content_type);
///
/// // The image `attachment` will display inline into the email.
/// # Ok(())
/// # }
/// ```
pub fn new_inline_with_name(content_id: String, name: String) -> Self {
Attachment {
disposition: Disposition::Inline {
content_id,
name: Some(name),
},
} }
} }
@@ -95,9 +141,18 @@ impl Attachment {
Disposition::Attached(filename) => { Disposition::Attached(filename) => {
builder.header(header::ContentDisposition::attachment(&filename)) builder.header(header::ContentDisposition::attachment(&filename))
} }
Disposition::Inline(content_id) => builder Disposition::Inline {
content_id,
name: None,
} => builder
.header(header::ContentId::from(format!("<{content_id}>"))) .header(header::ContentId::from(format!("<{content_id}>")))
.header(header::ContentDisposition::inline()), .header(header::ContentDisposition::inline()),
Disposition::Inline {
content_id,
name: Some(name),
} => builder
.header(header::ContentId::from(format!("<{content_id}>")))
.header(header::ContentDisposition::inline_with_name(&name)),
}; };
builder = builder.header(content_type); builder = builder.header(content_type);
builder.body(content) builder.body(content)
@@ -142,4 +197,24 @@ mod tests {
) )
); );
} }
#[test]
fn attachment_inline_with_name() {
let id = String::from("id");
let name = String::from("test");
let part = super::Attachment::new_inline_with_name(id, name).body(
String::from("Hello world!"),
ContentType::parse("text/plain").unwrap(),
);
assert_eq!(
&String::from_utf8_lossy(&part.formatted()),
concat!(
"Content-ID: <id>\r\n",
"Content-Disposition: inline; filename=\"test\"\r\n",
"Content-Type: text/plain\r\n",
"Content-Transfer-Encoding: 7bit\r\n\r\n",
"Hello world!\r\n"
)
);
}
} }

View File

@@ -186,21 +186,15 @@ impl HeaderName {
/// Creates a new header name, panics on invalid name /// Creates a new header name, panics on invalid name
pub const fn new_from_ascii_str(ascii: &'static str) -> Self { pub const fn new_from_ascii_str(ascii: &'static str) -> Self {
macro_rules! static_assert { assert!(!ascii.is_empty());
($condition:expr) => { assert!(ascii.len() <= 76);
let _ = [()][(!($condition)) as usize]; assert!(ascii.is_ascii());
};
}
static_assert!(!ascii.is_empty());
static_assert!(ascii.len() <= 76);
let bytes = ascii.as_bytes(); let bytes = ascii.as_bytes();
let mut i = 0; let mut i = 0;
while i < bytes.len() { while i < bytes.len() {
static_assert!(bytes[i].is_ascii()); assert!(bytes[i] != b' ');
static_assert!(bytes[i] != b' '); assert!(bytes[i] != b':');
static_assert!(bytes[i] != b':');
i += 1; i += 1;
} }

View File

@@ -217,7 +217,7 @@ mod mimebody;
use crate::{ use crate::{
address::Envelope, address::Envelope,
message::header::{ContentTransferEncoding, Header, Headers, MailboxesHeader}, message::header::{ContentTransferEncoding, Header, HeaderValue, Headers, MailboxesHeader},
Error as EmailError, Error as EmailError,
}; };
@@ -369,6 +369,12 @@ impl MessageBuilder {
self self
} }
/// Set raw custom header to message
pub fn raw_header(mut self, raw_header: HeaderValue) -> Self {
self.headers.insert_raw(raw_header);
self
}
/// Add mailbox to header /// Add mailbox to header
pub fn mailbox<H: Header + MailboxesHeader>(self, header: H) -> Self { pub fn mailbox<H: Header + MailboxesHeader>(self, header: H) -> Self {
match self.headers.get::<H>() { match self.headers.get::<H>() {
@@ -711,7 +717,10 @@ mod test {
.header(header::To( .header(header::To(
vec!["Pony O.P. <pony@domain.tld>".parse().unwrap()].into(), vec!["Pony O.P. <pony@domain.tld>".parse().unwrap()].into(),
)) ))
.header(header::Subject::from(String::from("яңа ел белән!"))) .raw_header(header::HeaderValue::new(
header::HeaderName::new_from_ascii_str("Subject"),
"яңа ел белән!".to_owned(),
))
.body(String::from("Happy new year!")) .body(String::from("Happy new year!"))
.unwrap(); .unwrap();

View File

@@ -164,8 +164,9 @@ pub enum CertificateStore {
/// For native-tls, this will use the system certificate store on Windows, the keychain on /// For native-tls, this will use the system certificate store on Windows, the keychain on
/// macOS, and OpenSSL directories on Linux (usually `/etc/ssl`). /// macOS, and OpenSSL directories on Linux (usually `/etc/ssl`).
/// ///
/// For rustls, this will also use the system store if the `rustls-native-certs` feature is /// For rustls, this will use the system certificate verifier if the `rustls-platform-verifier`
/// enabled, or will fall back to `webpki-roots`. /// feature is enabled. If the `rustls-native-certs` feature is enabled, system certificate
/// store will be used. Otherwise, it will fall back to `webpki-roots`.
/// ///
/// The boring-tls backend uses the same logic as OpenSSL on all platforms. /// The boring-tls backend uses the same logic as OpenSSL on all platforms.
#[default] #[default]
@@ -259,6 +260,8 @@ impl TlsParametersBuilder {
/// Controls whether certificates with an invalid hostname are accepted /// Controls whether certificates with an invalid hostname are accepted
/// ///
/// This option is silently disabled when using `rustls-platform-verifier`.
///
/// Defaults to `false`. /// Defaults to `false`.
/// ///
/// # Warning /// # Warning
@@ -461,7 +464,10 @@ impl TlsParametersBuilder {
// Build TLS config // Build TLS config
let mut root_cert_store = RootCertStore::empty(); let mut root_cert_store = RootCertStore::empty();
#[cfg(feature = "rustls-native-certs")] #[cfg(all(
not(feature = "rustls-platform-verifier"),
feature = "rustls-native-certs"
))]
fn load_native_roots(store: &mut RootCertStore) { fn load_native_roots(store: &mut RootCertStore) {
let rustls_native_certs::CertificateResult { certs, errors, .. } = let rustls_native_certs::CertificateResult { certs, errors, .. } =
rustls_native_certs::load_native_certs(); rustls_native_certs::load_native_certs();
@@ -481,11 +487,26 @@ impl TlsParametersBuilder {
store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
} }
#[cfg_attr(not(feature = "rustls-platform-verifier"), allow(unused_mut))]
let mut extra_roots = None::<Vec<CertificateDer<'static>>>;
match self.cert_store { match self.cert_store {
CertificateStore::Default => { CertificateStore::Default => {
#[cfg(feature = "rustls-native-certs")] #[cfg(feature = "rustls-platform-verifier")]
{
extra_roots = Some(Vec::new());
}
#[cfg(all(
not(feature = "rustls-platform-verifier"),
feature = "rustls-native-certs"
))]
load_native_roots(&mut root_cert_store); load_native_roots(&mut root_cert_store);
#[cfg(all(not(feature = "rustls-native-certs"), feature = "webpki-roots"))]
#[cfg(all(
not(feature = "rustls-platform-verifier"),
not(feature = "rustls-native-certs"),
feature = "webpki-roots"
))]
load_webpki_roots(&mut root_cert_store); load_webpki_roots(&mut root_cert_store);
} }
#[cfg(all(feature = "rustls", feature = "webpki-roots"))] #[cfg(all(feature = "rustls", feature = "webpki-roots"))]
@@ -496,11 +517,17 @@ impl TlsParametersBuilder {
} }
for cert in self.root_certs { for cert in self.root_certs {
for rustls_cert in cert.rustls { for rustls_cert in cert.rustls {
#[cfg(feature = "rustls-platform-verifier")]
if let Some(extra_roots) = &mut extra_roots {
extra_roots.push(rustls_cert.clone());
}
root_cert_store.add(rustls_cert).map_err(error::tls)?; root_cert_store.add(rustls_cert).map_err(error::tls)?;
} }
} }
let tls = if self.accept_invalid_certs || self.accept_invalid_hostnames { let tls = if self.accept_invalid_certs
|| (extra_roots.is_none() && self.accept_invalid_hostnames)
{
let verifier = InvalidCertsVerifier { let verifier = InvalidCertsVerifier {
ignore_invalid_hostnames: self.accept_invalid_hostnames, ignore_invalid_hostnames: self.accept_invalid_hostnames,
ignore_invalid_certs: self.accept_invalid_certs, ignore_invalid_certs: self.accept_invalid_certs,
@@ -510,7 +537,23 @@ impl TlsParametersBuilder {
tls.dangerous() tls.dangerous()
.with_custom_certificate_verifier(Arc::new(verifier)) .with_custom_certificate_verifier(Arc::new(verifier))
} else { } else {
tls.with_root_certificates(root_cert_store) #[cfg(feature = "rustls-platform-verifier")]
if let Some(extra_roots) = extra_roots {
tls.dangerous().with_custom_certificate_verifier(Arc::new(
rustls_platform_verifier::Verifier::new_with_extra_roots(
extra_roots,
crypto_provider,
)
.map_err(error::tls)?,
))
} else {
tls.with_root_certificates(root_cert_store)
}
#[cfg(not(feature = "rustls-platform-verifier"))]
{
tls.with_root_certificates(root_cert_store)
}
}; };
let tls = if let Some(identity) = self.identity { let tls = if let Some(identity) = self.identity {

View File

@@ -149,11 +149,7 @@ impl<E: Executor> Pool<E> {
pub(crate) async fn shutdown(&self) { pub(crate) async fn shutdown(&self) {
let connections = { self.connections.lock().await.take() }; let connections = { self.connections.lock().await.take() };
if let Some(connections) = connections { if let Some(connections) = connections {
stream::iter(connections) abort_concurrent(connections.into_iter().map(ParkedConnection::unpark)).await;
.for_each_concurrent(8, |conn| async move {
conn.unpark().abort().await;
})
.await;
} }
if let Some(handle) = self.handle.get() { if let Some(handle) = self.handle.get() {

View File

@@ -43,13 +43,11 @@
use std::{ use std::{
error::Error as StdError, error::Error as StdError,
fmt, fmt,
sync::{Arc, Mutex as StdMutex}, sync::{Arc, Mutex},
}; };
#[cfg(any(feature = "tokio1", feature = "async-std1"))] #[cfg(any(feature = "tokio1", feature = "async-std1"))]
use async_trait::async_trait; use async_trait::async_trait;
#[cfg(any(feature = "tokio1", feature = "async-std1"))]
use futures_util::lock::Mutex as FuturesMutex;
#[cfg(any(feature = "tokio1", feature = "async-std1"))] #[cfg(any(feature = "tokio1", feature = "async-std1"))]
use crate::AsyncTransport; use crate::AsyncTransport;
@@ -72,7 +70,7 @@ impl StdError for Error {}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct StubTransport { pub struct StubTransport {
response: Result<(), Error>, response: Result<(), Error>,
message_log: Arc<StdMutex<Vec<(Envelope, String)>>>, message_log: Arc<Mutex<Vec<(Envelope, String)>>>,
} }
/// This transport logs messages and always returns the given response /// This transport logs messages and always returns the given response
@@ -81,7 +79,7 @@ pub struct StubTransport {
#[cfg_attr(docsrs, doc(cfg(any(feature = "tokio1", feature = "async-std1"))))] #[cfg_attr(docsrs, doc(cfg(any(feature = "tokio1", feature = "async-std1"))))]
pub struct AsyncStubTransport { pub struct AsyncStubTransport {
response: Result<(), Error>, response: Result<(), Error>,
message_log: Arc<FuturesMutex<Vec<(Envelope, String)>>>, message_log: Arc<Mutex<Vec<(Envelope, String)>>>,
} }
impl StubTransport { impl StubTransport {
@@ -89,7 +87,7 @@ impl StubTransport {
pub fn new(response: Result<(), Error>) -> Self { pub fn new(response: Result<(), Error>) -> Self {
Self { Self {
response, response,
message_log: Arc::new(StdMutex::new(vec![])), message_log: Arc::new(Mutex::new(vec![])),
} }
} }
@@ -97,7 +95,7 @@ impl StubTransport {
pub fn new_ok() -> Self { pub fn new_ok() -> Self {
Self { Self {
response: Ok(()), response: Ok(()),
message_log: Arc::new(StdMutex::new(vec![])), message_log: Arc::new(Mutex::new(vec![])),
} }
} }
@@ -105,7 +103,7 @@ impl StubTransport {
pub fn new_error() -> Self { pub fn new_error() -> Self {
Self { Self {
response: Err(Error), response: Err(Error),
message_log: Arc::new(StdMutex::new(vec![])), message_log: Arc::new(Mutex::new(vec![])),
} }
} }
@@ -124,7 +122,7 @@ impl AsyncStubTransport {
pub fn new(response: Result<(), Error>) -> Self { pub fn new(response: Result<(), Error>) -> Self {
Self { Self {
response, response,
message_log: Arc::new(FuturesMutex::new(vec![])), message_log: Arc::new(Mutex::new(vec![])),
} }
} }
@@ -132,7 +130,7 @@ impl AsyncStubTransport {
pub fn new_ok() -> Self { pub fn new_ok() -> Self {
Self { Self {
response: Ok(()), response: Ok(()),
message_log: Arc::new(FuturesMutex::new(vec![])), message_log: Arc::new(Mutex::new(vec![])),
} }
} }
@@ -140,14 +138,14 @@ impl AsyncStubTransport {
pub fn new_error() -> Self { pub fn new_error() -> Self {
Self { Self {
response: Err(Error), response: Err(Error),
message_log: Arc::new(FuturesMutex::new(vec![])), message_log: Arc::new(Mutex::new(vec![])),
} }
} }
/// Return all logged messages sent using [`AsyncTransport::send_raw`] /// Return all logged messages sent using [`AsyncTransport::send_raw`]
#[cfg(any(feature = "tokio1", feature = "async-std1"))] #[cfg(any(feature = "tokio1", feature = "async-std1"))]
pub async fn messages(&self) -> Vec<(Envelope, String)> { pub async fn messages(&self) -> Vec<(Envelope, String)> {
self.message_log.lock().await.clone() self.message_log.lock().unwrap().clone()
} }
} }
@@ -173,7 +171,7 @@ impl AsyncTransport for AsyncStubTransport {
async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> { async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
self.message_log self.message_log
.lock() .lock()
.await .unwrap()
.push((envelope.clone(), String::from_utf8_lossy(email).into())); .push((envelope.clone(), String::from_utf8_lossy(email).into()));
self.response self.response
} }