#[cfg(target_arch = "wasm32")]
mod bindings {
wit_bindgen::generate!({
path: "../../wit",
world: "smtp-client",
exports: {
"wasm-smtp:smtp/smtp-send": super::SmtpSendImpl,
},
});
}
#[cfg(target_arch = "wasm32")]
use bindings::exports::wasm_smtp::smtp::smtp_send::{
Guest, SendError, SendResult, SmtpConfig, SmtpCredentials, SmtpMessage, TlsMode,
};
#[cfg(not(target_arch = "wasm32"))]
mod stubs;
#[cfg(not(target_arch = "wasm32"))]
use stubs::{SendError, SendResult, SmtpConfig, SmtpCredentials, SmtpMessage};
pub struct SmtpSendImpl;
impl SmtpSendImpl {
#[allow(unused_variables)]
pub fn send_impl(
config: SmtpConfig,
credentials: SmtpCredentials,
message: SmtpMessage,
) -> Result<SendResult, SendError> {
use wasm_smtp::SmtpError;
#[cfg(target_arch = "wasm32")]
let client_result = {
let opts = ConnectOptions::default();
let fut = match config.tls_mode {
TlsMode::Implicit => wasm_smtp_wasi::connect_smtps(
&config.host,
config.port,
&config.ehlo_domain,
),
TlsMode::Starttls => wasm_smtp_wasi::connect_smtp_starttls(
&config.host,
config.port,
&config.ehlo_domain,
),
};
wasm_smtp_component_rt::block_on(fut)
};
#[cfg(not(target_arch = "wasm32"))]
let client_result: Result<_, SmtpError> =
Err(SmtpError::Io(wasm_smtp::IoError::new(
"wasm-smtp-component only runs on wasm32-wasip2; \
use cargo test for native unit tests",
)));
let mut _client = client_result.map_err(smtp_error_to_wit)?;
#[cfg(target_arch = "wasm32")]
wasm_smtp_component_rt::block_on(
client.login(&credentials.username, &credentials.password),
)
.map_err(smtp_error_to_wit)?;
let to_refs: Vec<&str> = message.to.iter().map(String::as_str).collect();
#[cfg(target_arch = "wasm32")]
let outcome = wasm_smtp_component_rt::block_on(client.send_mail(
&message.from,
&to_refs,
&message.raw_message,
))
.map_err(smtp_error_to_wit)?;
#[cfg(not(target_arch = "wasm32"))]
#[allow(unreachable_code)]
let _outcome: wasm_smtp::SendOutcome = {
let _ = (&message.from, &to_refs, &message.raw_message);
return Err(SendError::Io("unreachable on native host".into()));
unreachable!()
};
#[cfg(target_arch = "wasm32")]
{ wasm_smtp_component_rt::block_on(client.quit()).ok(); }
#[cfg(target_arch = "wasm32")]
return Ok(SendResult { reply_code: outcome.code });
#[cfg(not(target_arch = "wasm32"))]
Ok(SendResult { reply_code: _outcome.code })
}
}
#[cfg(target_arch = "wasm32")]
impl Guest for SmtpSendImpl {
fn send(
config: SmtpConfig,
credentials: SmtpCredentials,
message: SmtpMessage,
) -> Result<SendResult, SendError> {
Self::send_impl(config, credentials, message)
}
}
fn smtp_error_to_wit(e: wasm_smtp::SmtpError) -> SendError {
match e {
wasm_smtp::SmtpError::Io(io) => SendError::Io(io.to_string()),
wasm_smtp::SmtpError::Protocol(p) => SendError::Protocol(p.to_string()),
wasm_smtp::SmtpError::Auth(_) => SendError::AuthRejected,
wasm_smtp::SmtpError::InvalidInput(i) => SendError::InvalidInput(i.to_string()),
wasm_smtp::SmtpError::Policy(p) => SendError::PolicyRejected(p.to_string()),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn send_error_io_variant_carries_message() {
let e = SendError::Io("connection refused".into());
match e {
SendError::Io(msg) => assert_eq!(msg, "connection refused"),
_ => panic!("wrong variant"),
}
}
#[test]
fn send_error_auth_rejected_has_no_payload() {
let e = SendError::AuthRejected;
assert!(matches!(e, SendError::AuthRejected));
}
#[test]
fn smtp_config_fields_accessible() {
let cfg = SmtpConfig {
host: "smtp.example.com".into(),
port: 465,
ehlo_domain: "client.example.com".into(),
tls_mode: TlsMode::Implicit,
};
assert_eq!(cfg.host, "smtp.example.com");
assert_eq!(cfg.port, 465);
}
#[test]
fn smtp_message_to_is_vec() {
let msg = SmtpMessage {
from: "a@example.com".into(),
to: vec!["b@example.com".into(), "c@example.com".into()],
raw_message: "Subject: hi\r\n\r\nbody\r\n".into(),
};
assert_eq!(msg.to.len(), 2);
}
#[test]
fn credentials_fields_accessible() {
let cred = SmtpCredentials {
username: "user@example.com".into(),
password: "secret".into(),
};
assert_eq!(cred.username, "user@example.com");
assert!(!format!("{cred:?}").contains("secret"),
"credentials must not appear in Debug output");
}
}