use reqwest::header::{self, HeaderMap, HeaderValue};
#[cfg(feature = "blocking")]
use reqwest::blocking::Response as BlockingResponse;
use reqwest::Response;
use url::form_urlencoded::Serializer;
use crate::{
error::{RequestNotSuccessful, SendgridResult},
mail::Mail,
};
static API_URL: &str = "https://api.sendgrid.com/api/mail.send.json?";
#[derive(Clone, Debug)]
pub struct SGClient {
api_key: String,
host: String,
client: reqwest::Client,
#[cfg(feature = "blocking")]
blocking_client: reqwest::blocking::Client,
}
fn make_form_key(form: &str, key: &str) -> String {
let mut value = String::with_capacity(form.len() + key.len() + 2);
value.push_str(form);
value.push('[');
value.push_str(key);
value.push(']');
value
}
fn make_post_body(mut mail_info: Mail) -> SendgridResult<String> {
let body = String::new();
let mut encoder = Serializer::new(body);
for to in mail_info.to.iter() {
encoder.append_pair("to[]", to.address);
encoder.append_pair("toname[]", to.name);
}
for cc in mail_info.cc.iter() {
encoder.append_pair("cc[]", cc);
}
for bcc in mail_info.bcc.iter() {
encoder.append_pair("bcc[]", bcc);
}
for (attachment, contents) in &mail_info.attachments {
encoder.append_pair(&make_form_key("files", attachment), contents);
}
for (id, value) in &mail_info.content {
encoder.append_pair(&make_form_key("content", id), value);
}
encoder.append_pair("from", mail_info.from);
encoder.append_pair("subject", mail_info.subject);
encoder.append_pair("html", mail_info.html);
encoder.append_pair("text", mail_info.text);
encoder.append_pair("fromname", mail_info.from_name);
encoder.append_pair("replyto", mail_info.reply_to);
encoder.append_pair("date", mail_info.date);
encoder.append_pair("headers", &mail_info.make_header_string()?);
encoder.append_pair("x-smtpapi", mail_info.x_smtpapi);
Ok(encoder.finish())
}
impl SGClient {
pub fn new<S: Into<String>>(key: S) -> SGClient {
let async_builder = reqwest::ClientBuilder::new();
#[cfg(feature = "rustls")]
let async_builder = async_builder.use_rustls_tls();
let client = async_builder.build().unwrap();
#[cfg(feature = "blocking")]
let blocking_client: reqwest::blocking::Client;
#[cfg(feature = "blocking")]
{
let blocking_builder = reqwest::blocking::ClientBuilder::new();
#[cfg(feature = "rustls")]
let blocking_builder = blocking_builder.use_rustls_tls();
blocking_client = blocking_builder.build().unwrap();
}
SGClient {
api_key: key.into(),
client,
#[cfg(feature = "blocking")]
blocking_client,
host: API_URL.to_string(),
}
}
pub fn set_host<S: Into<String>>(&mut self, host: S) {
self.host = host.into();
}
#[cfg(feature = "blocking")]
pub fn blocking_send(&self, mail_info: Mail) -> SendgridResult<BlockingResponse> {
let post_body = make_post_body(mail_info)?;
let resp = self
.blocking_client
.post(&self.host)
.headers(self.headers()?)
.body(post_body)
.send()?;
if resp.error_for_status_ref().is_err() {
return Err(RequestNotSuccessful::new(resp.status(), resp.text()?).into());
}
Ok(resp)
}
pub async fn send(&self, mail_info: Mail<'_>) -> SendgridResult<Response> {
let post_body = make_post_body(mail_info)?;
let resp = self
.client
.post(&self.host)
.headers(self.headers()?)
.body(post_body)
.send()
.await?;
if resp.error_for_status_ref().is_err() {
return Err(RequestNotSuccessful::new(resp.status(), resp.text().await?).into());
}
Ok(resp)
}
fn headers(&self) -> SendgridResult<HeaderMap> {
let mut headers = HeaderMap::new();
headers.insert(
header::AUTHORIZATION,
HeaderValue::from_str(&format!("Bearer {}", self.api_key.clone()))?,
);
headers.insert(
header::CONTENT_TYPE,
HeaderValue::from_static("application/x-www-form-urlencoded"),
);
headers.insert(header::USER_AGENT, HeaderValue::from_static("sendgrid-rs"));
Ok(headers)
}
}
#[test]
fn basic_message_body() {
use crate::mail::Destination;
let m = Mail::new()
.add_to(Destination {
address: "test@example.com",
name: "Testy mcTestFace",
})
.add_from("me@example.com")
.add_subject("Test")
.add_text("It works");
let body = make_post_body(m);
let want = "to%5B%5D=test%40example.com&toname%5B%5D=Testy+mcTestFace&from=me%40example.com&subject=Test&\
html=&text=It+works&fromname=&replyto=&date=&headers=%7B%7D&x-smtpapi=";
assert_eq!(body.unwrap(), want);
}
#[test]
fn test_proper_key() {
let want = "files[test.jpg]";
let got = make_form_key("files", "test.jpg");
assert_eq!(want, got);
}