1use std::future::Future;
2use std::pin::Pin;
3
4use crate::error::AuthError;
5
6pub struct EmailMessage<'a> {
8 pub to: &'a str,
9 pub subject: &'a str,
10 pub body: &'a str,
11 pub html: Option<&'a str>,
12}
13
14pub trait EmailSender: Send + Sync {
23 fn send<'a>(
24 &'a self,
25 message: EmailMessage<'a>,
26 ) -> Pin<Box<dyn Future<Output = Result<(), AuthError>> + Send + 'a>>;
27}
28
29pub struct LogEmailSender;
34
35impl EmailSender for LogEmailSender {
36 fn send<'a>(
37 &'a self,
38 message: EmailMessage<'a>,
39 ) -> Pin<Box<dyn Future<Output = Result<(), AuthError>> + Send + 'a>> {
40 tracing::info!(
41 to = message.to,
42 subject = message.subject,
43 body = message.body,
44 html = message.html,
45 "dev email (not delivered)"
46 );
47 Box::pin(std::future::ready(Ok(())))
48 }
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54
55 fn _assert_object_safe(_: &dyn EmailSender) {}
57
58 #[tokio::test]
59 async fn log_sender_succeeds() {
60 let sender = LogEmailSender;
61 let msg = EmailMessage {
62 to: "user@example.com",
63 subject: "Test",
64 body: "Hello",
65 html: None,
66 };
67 let result = sender.send(msg).await;
68 assert!(result.is_ok());
69 }
70
71 #[tokio::test]
72 async fn log_sender_succeeds_with_html() {
73 let sender = LogEmailSender;
74 let msg = EmailMessage {
75 to: "user@example.com",
76 subject: "Test",
77 body: "Hello",
78 html: Some("<p>Hello</p>"),
79 };
80 let result = sender.send(msg).await;
81 assert!(result.is_ok());
82 }
83
84 #[tokio::test]
85 async fn trait_object_dispatch_works() {
86 let sender: Box<dyn EmailSender> = Box::new(LogEmailSender);
87 let msg = EmailMessage {
88 to: "user@example.com",
89 subject: "Subject",
90 body: "Body",
91 html: None,
92 };
93 let result = sender.send(msg).await;
94 assert!(result.is_ok());
95 }
96}