use crate::{
data_objects::{
MessageObject, RequestObject, Response, ResponseObject, SendEmailParams,
SendResponseObject, SimpleMessage,
},
mailjet_api::ApiUrl,
ApiVersion, ClientError,
};
use reqwest_middleware::ClientWithMiddleware;
use reqwest_tracing::TracingMiddleware;
use secrecy::{ExposeSecret, SecretString};
use serde::Deserialize;
use tracing::{debug, error, info, instrument, trace, warn};
#[derive(Debug)]
pub struct MailjetClient {
http_client: ClientWithMiddleware,
pub email_address: Option<String>,
pub email_name: Option<String>,
api_user: SecretString,
api_key: SecretString,
api_url: String,
api_version: ApiVersion,
sandbox_mode: bool,
}
impl MailjetClient {
#[allow(clippy::too_many_arguments)]
pub fn new(
api_user: SecretString,
api_key: SecretString,
email_address: Option<&str>,
email_name: Option<&str>,
user_agent: Option<&str>,
api_url: Option<&str>,
api_version: Option<&str>,
force_https: Option<bool>,
) -> Result<Self, ClientError> {
let user_agent: &str = user_agent.unwrap_or(concat!(
env!("CARGO_PKG_NAME"),
"/",
env!("CARGO_PKG_VERSION"),
));
let api_url = match api_url {
Some(url) => url.into(),
None => "https://api.mailjet.com".into(),
};
let api_version = match api_version {
Some(version) => version.try_into()?,
None => ApiVersion::V3,
};
let http_client = reqwest::ClientBuilder::new()
.user_agent(user_agent)
.use_native_tls()
.https_only(force_https.unwrap_or(true))
.build()
.map_err(|_| ClientError::HTTPClient)?;
let wrapped_client = reqwest_middleware::ClientBuilder::new(http_client)
.with(TracingMiddleware::default())
.build();
debug!("reqwest client successfully built");
Ok(MailjetClient {
http_client: wrapped_client,
email_address: email_address.map(String::from),
email_name: email_name.map(String::from),
api_key,
api_user,
api_url,
api_version,
sandbox_mode: false,
})
}
pub fn use_api_version(&mut self, version: ApiVersion) {
self.api_version = version;
}
pub fn enable_sandbox_mode(&mut self) {
if self.api_version == ApiVersion::V3 {
warn!("The sandbox mode is only available for API versions >= 3.1");
} else {
self.sandbox_mode = true;
}
}
pub fn disable_sandbox_mode(&mut self) {
if self.sandbox_mode {
info!("Sandbox mode disabled");
self.sandbox_mode = false;
}
}
pub async fn send_email(&self, request: &impl RequestObject) -> Result<Response, ClientError> {
match self.api_version {
ApiVersion::V3 => {
trace!("Sending email to the external API (v3)");
self.send_email_v3(request).await
}
ApiVersion::V3_1 => {
trace!("Sending email to the external API (v3.1)");
self.send_email_v3_1(request).await
}
}
}
#[instrument]
async fn send_email_v3_1(&self, request: &impl RequestObject) -> Result<Response, ClientError> {
debug!("Request parameters: {:?}", request);
let mut request_params: SendEmailParams =
match request.as_any().downcast_ref::<SendEmailParams>() {
Some(r) => r.clone(),
None => return Err(ClientError::UnknownError("Invalid request".to_string())),
};
if !request_params.sandbox_mode.unwrap_or_default() {
debug!("The global sandbox mode is applied to the current message");
request_params.sandbox_mode = Some(self.sandbox_mode);
}
let request = self
.http_client
.post(format!(
"{}/{}",
self.api_url,
ApiUrl::send(&self.api_version)
))
.basic_auth(
self.api_user.expose_secret(),
Some(&self.api_key.expose_secret()),
)
.json(&request_params)
.build()
.unwrap();
debug!("POST request: {:?}", request);
let raw_response = self
.http_client
.execute(request)
.await
.map_err(|e| ClientError::ExternalError(e.to_string()))?;
debug!("Received response: {:?}", raw_response);
let response_code = raw_response.status().as_u16();
let payload = raw_response
.text()
.await
.map_err(|e| ClientError::UnknownError(e.to_string()))?;
debug!("Response's payload: {:?}", payload);
if response_code == 200 {
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
#[allow(dead_code)]
struct TempResponse {
pub messages: Vec<SendResponseObject>,
}
let mut payload: TempResponse = serde_json::from_str(payload.as_str())
.map_err(|e| ClientError::UnknownError(e.to_string()))?;
let response = payload
.messages
.drain(..)
.map(|e: SendResponseObject| Box::<dyn ResponseObject>::from(Box::new(e)))
.collect();
Ok(Response {
status_code: response_code,
payload: Some(response),
})
} else if response_code == 400 {
Err(ClientError::BadRequest(format!(
"status_code: {}, payload: {:?}",
response_code, payload
)))
} else {
Err(ClientError::UnknownError(format!(
"status_code: {}, payload: {:?}",
response_code, payload
)))
}
}
#[instrument]
async fn send_email_v3(&self, request: &impl RequestObject) -> Result<Response, ClientError> {
debug!("Request parameters: {:?}", request);
let request_params: &SimpleMessage = match request.as_any().downcast_ref::<SimpleMessage>()
{
Some(r) => r,
None => {
error!("Received wrong parameters for the selected request");
return Err(ClientError::BadRequest(
"Wrong parameters for the request".into(),
));
}
};
let request = self
.http_client
.post(format!(
"{}/{}",
self.api_url,
ApiUrl::send(&self.api_version)
))
.basic_auth(
self.api_user.expose_secret(),
Some(&self.api_key.expose_secret()),
)
.json(&request_params)
.build()
.unwrap();
trace!("POST request: {:?}", request);
let raw_response = self
.http_client
.execute(request)
.await
.map_err(|e| ClientError::ExternalError(e.to_string()))?;
info!("Send request executed");
debug!("Received response: {:?}", raw_response);
let response_code = raw_response.status().as_u16();
let response_payload = raw_response
.text()
.await
.map_err(|e| ClientError::UnknownError(e.to_string()))?;
debug!("Response's payload: {:?}", response_payload);
if response_code == 200 || response_code == 201 {
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
#[allow(dead_code)]
struct TempResponse {
pub sent: Vec<MessageObject>,
}
let mut payload: TempResponse = serde_json::from_str(response_payload.as_str())
.map_err(|e| ClientError::ParseError(e.to_string()))?;
let response = payload
.sent
.drain(..)
.map(|e: MessageObject| Box::<dyn ResponseObject>::from(Box::new(e)))
.collect();
Ok(Response {
status_code: response_code,
payload: Some(response),
})
} else if response_code == 400 {
Err(ClientError::BadRequest(format!(
"status_code: {}, payload: {:?}",
response_code, response_payload
)))
} else {
Err(ClientError::UnknownError(format!(
"status_code: {}, payload: {:?}",
response_code, response_payload
)))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::MailjetClientBuilder;
use pretty_assertions::assert_eq;
use rstest::*;
use secrecy::{ExposeSecret, SecretString};
use uuid::Uuid;
#[rstest]
fn client_builds() {
let builder = MailjetClientBuilder::default().build();
assert!(builder.is_ok());
let builder = MailjetClientBuilder::default()
.with_api_user(SecretString::from(Uuid::new_v4().to_string()))
.with_api_key(SecretString::from(Uuid::new_v4().to_string()))
.build();
assert!(builder.is_ok());
let api_user = SecretString::from(Uuid::new_v4().to_string());
let api_key = SecretString::from(Uuid::new_v4().to_string());
let builder = MailjetClientBuilder::new(api_user, api_key).build();
assert!(builder.is_ok());
let api_user = SecretString::from(Uuid::new_v4().to_string());
let api_key = SecretString::from(Uuid::new_v4().to_string());
let name = "Test User";
let email = "test_user@mail.com";
let url = "demo.com";
let version = ApiVersion::V3_1;
let builder = MailjetClientBuilder::default()
.with_api_user(api_user.clone())
.with_api_key(api_key.clone())
.with_email_name(name)
.with_email_address(email)
.with_user_agent(name)
.with_api_url(url)
.with_api_version(version.to_string().as_str())
.build();
assert!(builder.is_ok());
let client = builder.unwrap();
assert_eq!(client.email_address.unwrap(), email);
assert_eq!(client.email_name.unwrap(), name);
assert_eq!(client.api_user.expose_secret(), api_user.expose_secret());
assert_eq!(client.api_key.expose_secret(), api_key.expose_secret());
assert_eq!(client.api_url, url);
assert_eq!(client.api_version, version);
}
#[rstest]
fn change_api_version() {
let mut client = MailjetClientBuilder::default().build().unwrap();
assert_eq!(client.api_version, ApiVersion::default());
client.api_version = ApiVersion::V3_1;
assert_eq!(client.api_version, ApiVersion::V3_1);
client.use_api_version(ApiVersion::V3);
assert_eq!(client.api_version, ApiVersion::V3);
}
#[rstest]
fn change_sandbox_mode() {
let mut client = MailjetClientBuilder::default().build().unwrap();
assert_eq!(client.sandbox_mode, false);
client.enable_sandbox_mode();
assert_eq!(client.sandbox_mode, false);
client.use_api_version(ApiVersion::V3_1);
client.enable_sandbox_mode();
assert_eq!(client.sandbox_mode, true);
client.disable_sandbox_mode();
assert_eq!(client.sandbox_mode, false);
}
}