feat: add email client
This commit is contained in:
parent
13db7853bd
commit
ac216925ff
7 changed files with 167 additions and 4 deletions
81
Cargo.lock
generated
81
Cargo.lock
generated
|
|
@ -60,6 +60,16 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assert-json-diff"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.68"
|
||||
|
|
@ -374,6 +384,24 @@ dependencies = [
|
|||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deadpool"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb84100978c1c7b37f09ed3ce3e5f843af02c2a2c431bae5b19230dad2c1b490"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"deadpool-runtime",
|
||||
"num_cpus",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deadpool-runtime"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63dfa964fe2a66f3fde91fc70b267fe193d822c7e603e2a675a49a7f46ad3f49"
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.8"
|
||||
|
|
@ -550,6 +578,21 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.28"
|
||||
|
|
@ -594,6 +637,17 @@ version = "0.3.30"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.28"
|
||||
|
|
@ -612,8 +666,10 @@ version = "0.3.28"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
|
|
@ -1076,6 +1132,7 @@ dependencies = [
|
|||
"unicode-segmentation",
|
||||
"uuid",
|
||||
"validator",
|
||||
"wiremock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3007,6 +3064,30 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wiremock"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec874e1eef0df2dcac546057fe5e29186f09c378181cd7b635b4b7bcc98e9d81"
|
||||
dependencies = [
|
||||
"assert-json-diff",
|
||||
"async-trait",
|
||||
"base64",
|
||||
"deadpool",
|
||||
"futures",
|
||||
"http 1.0.0",
|
||||
"http-body-util",
|
||||
"hyper 1.1.0",
|
||||
"hyper-util",
|
||||
"log",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
version = "0.4.5"
|
||||
|
|
|
|||
|
|
@ -59,9 +59,11 @@ default-features = false
|
|||
features = ["json", "rustls-tls"]
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1", features = ["rt", "macros"] }
|
||||
once_cell = "1"
|
||||
claims = "0.7"
|
||||
fake = "2.9.2"
|
||||
quickcheck = "1.0.3"
|
||||
quickcheck_macros = "1.0.0"
|
||||
rand = "0.8.5"
|
||||
wiremock = "0.6.0"
|
||||
|
|
|
|||
|
|
@ -9,3 +9,4 @@ database:
|
|||
email_client:
|
||||
base_url: "localhost"
|
||||
sender_email: "test@fmail.com"
|
||||
authorization_token: "my-secret-token"
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ pub struct Settings {
|
|||
pub struct EmailClientSettings {
|
||||
pub base_url: String,
|
||||
pub sender_email: String,
|
||||
pub authorization_token: Secret<String>,
|
||||
}
|
||||
|
||||
impl EmailClientSettings {
|
||||
|
|
|
|||
|
|
@ -1,19 +1,26 @@
|
|||
use crate::domain::SubscriberEmail;
|
||||
use reqwest::Client;
|
||||
use secrecy::{ExposeSecret, Secret};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EmailClient {
|
||||
http_client: Client,
|
||||
base_url: String,
|
||||
sender: SubscriberEmail,
|
||||
authorization_token: Secret<String>,
|
||||
}
|
||||
|
||||
impl EmailClient {
|
||||
pub fn new(base_url: String, sender: SubscriberEmail) -> Self {
|
||||
pub fn new(
|
||||
base_url: String,
|
||||
sender: SubscriberEmail,
|
||||
authorization_token: Secret<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
http_client: Client::new(),
|
||||
base_url,
|
||||
sender,
|
||||
authorization_token,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -24,6 +31,69 @@ impl EmailClient {
|
|||
html_content: &str,
|
||||
text_content: &str,
|
||||
) -> Result<(), String> {
|
||||
todo!()
|
||||
// TODO: use `reqwest::Url::join` and change `base_url`'s type from `String` to `reqwest::Url`
|
||||
let url = format!("{}/email", self.base_url);
|
||||
let request_body = SendEmailRequest {
|
||||
from: self.sender.as_ref().to_owned(),
|
||||
to: recipient.as_ref().to_owned(),
|
||||
subject: subject.to_owned(),
|
||||
html_body: html_content.to_owned(),
|
||||
text_body: text_content.to_owned(),
|
||||
};
|
||||
let builder = self
|
||||
.http_client
|
||||
.post(&url)
|
||||
.header(
|
||||
"X-Postmark-Server-Token",
|
||||
self.authorization_token.expose_secret(),
|
||||
)
|
||||
.json(&request_body);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
#[derive(serde::Serialize)]
|
||||
struct SendEmailRequest {
|
||||
from: String,
|
||||
to: String,
|
||||
subject: String,
|
||||
html_body: String,
|
||||
text_body: String,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::domain::SubscriberEmail;
|
||||
use crate::email_client::EmailClient;
|
||||
use fake::faker::internet::en::SafeEmail;
|
||||
use fake::faker::lorem::en::{Paragraph, Sentence};
|
||||
use fake::{Fake, Faker};
|
||||
use secrecy::Secret;
|
||||
use wiremock::matchers::any;
|
||||
use wiremock::{Mock, MockServer, ResponseTemplate};
|
||||
|
||||
#[tokio::test]
|
||||
async fn send_email_fires_a_request_to_base_url() {
|
||||
// Arrange
|
||||
let mock_server = MockServer::start().await;
|
||||
let sender = SubscriberEmail::parse(SafeEmail().fake()).unwrap();
|
||||
let email_client = EmailClient::new(mock_server.uri(), sender, Secret::new(Faker.fake()));
|
||||
|
||||
Mock::given(any())
|
||||
.respond_with(ResponseTemplate::new(200))
|
||||
.expect(1)
|
||||
.mount(&mock_server)
|
||||
.await;
|
||||
|
||||
let subscriber_email = SubscriberEmail::parse(SafeEmail().fake()).unwrap();
|
||||
let subject: String = Sentence(1..2).fake();
|
||||
let content: String = Paragraph(1..10).fake();
|
||||
|
||||
// Act
|
||||
let _ = email_client
|
||||
.send_email(subscriber_email, &subject, &content, &content)
|
||||
.await;
|
||||
|
||||
// Assert
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,11 @@ async fn main() {
|
|||
.email_client
|
||||
.sender()
|
||||
.expect("Invalid sender email address.");
|
||||
let email_client = EmailClient::new(configuration.email_client.base_url, sender_email);
|
||||
let email_client = EmailClient::new(
|
||||
configuration.email_client.base_url,
|
||||
sender_email,
|
||||
configuration.email_client.authorization_token,
|
||||
);
|
||||
startup::run(listener, connection_pool, email_client)
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
|
|||
|
|
@ -153,7 +153,11 @@ async fn spawn_app() -> TestApp {
|
|||
.email_client
|
||||
.sender()
|
||||
.expect("Invalid sender email address.");
|
||||
let email_client = EmailClient::new(configuration.email_client.base_url, sender_email);
|
||||
let email_client = EmailClient::new(
|
||||
configuration.email_client.base_url,
|
||||
sender_email,
|
||||
configuration.email_client.authorization_token,
|
||||
);
|
||||
|
||||
let service = learn_axum::startup::app(connection_pool.clone(), email_client);
|
||||
tokio::spawn(async move {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue