Merge pull request #218 from mozilla/pb/provider-type
https://github.com/mozilla/fxa-email-service/pull/218 r=vbudhram
This commit is contained in:
Коммит
c9c62459c8
|
@ -4,7 +4,7 @@
|
|||
|
||||
//! Generic abstraction of specific email providers.
|
||||
|
||||
use std::{boxed::Box, collections::HashMap};
|
||||
use std::{boxed::Box, collections::HashMap, convert::TryFrom};
|
||||
|
||||
use emailmessage::{header::ContentType, Message, MessageBuilder, MultiPart, SinglePart};
|
||||
|
||||
|
@ -12,10 +12,11 @@ use self::{
|
|||
mock::MockProvider as Mock, sendgrid::SendgridProvider as Sendgrid, ses::SesProvider as Ses,
|
||||
smtp::SmtpProvider as Smtp, socketlabs::SocketLabsProvider as SocketLabs,
|
||||
};
|
||||
use settings::{DefaultProvider, Settings};
|
||||
use settings::Settings;
|
||||
use types::{
|
||||
error::{AppErrorKind, AppResult},
|
||||
headers::*,
|
||||
provider::Provider as ProviderType,
|
||||
};
|
||||
|
||||
mod mock;
|
||||
|
@ -120,40 +121,38 @@ trait Provider {
|
|||
|
||||
/// Generic provider wrapper.
|
||||
pub struct Providers {
|
||||
default_provider: String,
|
||||
default_provider: ProviderType,
|
||||
force_default_provider: bool,
|
||||
providers: HashMap<String, Box<Provider>>,
|
||||
providers: HashMap<ProviderType, Box<Provider>>,
|
||||
}
|
||||
|
||||
impl Providers {
|
||||
/// Instantiate the provider clients.
|
||||
pub fn new(settings: &Settings) -> Providers {
|
||||
let mut providers: HashMap<String, Box<Provider>> = HashMap::new();
|
||||
let mut providers: HashMap<ProviderType, Box<Provider>> = HashMap::new();
|
||||
|
||||
macro_rules! set_provider {
|
||||
($id:expr, $constructor:expr) => {
|
||||
if !settings.provider.forcedefault
|
||||
|| settings.provider.default == DefaultProvider(String::from($id))
|
||||
{
|
||||
providers.insert(String::from($id), Box::new($constructor));
|
||||
($type:expr, $constructor:expr) => {
|
||||
if !settings.provider.forcedefault || settings.provider.default == $type {
|
||||
providers.insert($type, Box::new($constructor));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
set_provider!("mock", Mock);
|
||||
set_provider!("ses", Ses::new(settings));
|
||||
set_provider!("smtp", Smtp::new(settings));
|
||||
set_provider!(ProviderType::Mock, Mock);
|
||||
set_provider!(ProviderType::Ses, Ses::new(settings));
|
||||
set_provider!(ProviderType::Smtp, Smtp::new(settings));
|
||||
|
||||
if let Some(ref sendgrid) = settings.sendgrid {
|
||||
set_provider!("sendgrid", Sendgrid::new(sendgrid, settings));
|
||||
set_provider!(ProviderType::Sendgrid, Sendgrid::new(sendgrid, settings));
|
||||
}
|
||||
|
||||
if settings.socketlabs.is_some() {
|
||||
set_provider!("socketlabs", SocketLabs::new(settings));
|
||||
set_provider!(ProviderType::SocketLabs, SocketLabs::new(settings));
|
||||
}
|
||||
|
||||
Providers {
|
||||
default_provider: settings.provider.default.to_string(),
|
||||
default_provider: settings.provider.default,
|
||||
force_default_provider: settings.provider.forcedefault,
|
||||
providers,
|
||||
}
|
||||
|
@ -171,13 +170,17 @@ impl Providers {
|
|||
provider_id: Option<&str>,
|
||||
) -> AppResult<String> {
|
||||
let resolved_provider_id = if self.force_default_provider {
|
||||
&self.default_provider
|
||||
self.default_provider
|
||||
} else {
|
||||
provider_id.unwrap_or(&self.default_provider)
|
||||
if let Some(provider_id) = provider_id {
|
||||
ProviderType::try_from(provider_id)?
|
||||
} else {
|
||||
self.default_provider
|
||||
}
|
||||
};
|
||||
|
||||
self.providers
|
||||
.get(resolved_provider_id)
|
||||
.get(&resolved_provider_id)
|
||||
.ok_or_else(|| {
|
||||
AppErrorKind::InvalidPayload(format!(
|
||||
"provider `{}` is not enabled",
|
||||
|
|
|
@ -125,7 +125,7 @@ fn constructor() {
|
|||
fn send() {
|
||||
let mut settings = Settings::new().expect("config error");
|
||||
settings.provider.forcedefault = true;
|
||||
settings.provider.default = DefaultProvider(String::from("mock"));
|
||||
settings.provider.default = ProviderType::Mock;
|
||||
let providers = Providers::new(&settings);
|
||||
let result = providers.send("foo", &vec![], None, "bar", "baz", None, Some("ses"));
|
||||
assert!(result.is_ok(), "Providers::send should not have failed");
|
||||
|
@ -134,7 +134,7 @@ fn send() {
|
|||
}
|
||||
|
||||
settings.provider.forcedefault = false;
|
||||
settings.provider.default = DefaultProvider(String::from("ses"));
|
||||
settings.provider.default = ProviderType::Ses;
|
||||
let providers = Providers::new(&settings);
|
||||
let result = providers.send("foo", &vec![], None, "bar", "baz", None, Some("mock"));
|
||||
assert!(result.is_ok(), "Providers::send should not have failed");
|
||||
|
|
|
@ -21,7 +21,9 @@ use rocket::config::{
|
|||
use serde::de::{Deserialize, Deserializer, Error, Unexpected};
|
||||
|
||||
use logging::MozlogLogger;
|
||||
use types::{duration::Duration, email_address::EmailAddress, validate};
|
||||
use types::{
|
||||
duration::Duration, email_address::EmailAddress, provider::Provider as ProviderType, validate,
|
||||
};
|
||||
|
||||
macro_rules! deserialize_and_validate {
|
||||
($(#[$docs:meta] ($type:ident, $validator:ident, $expected:expr)),+) => ($(
|
||||
|
@ -67,8 +69,6 @@ deserialize_and_validate! {
|
|||
(AwsSecret, aws_secret, "AWS secret key"),
|
||||
/// Base URI type.
|
||||
(BaseUri, base_uri, "base URI"),
|
||||
/// Default email provider.
|
||||
(DefaultProvider, provider, "'ses', 'sendgrid', 'socketlabs' or 'smtp'"),
|
||||
/// Env type.
|
||||
(Env, env, "'dev', 'staging', 'production' or 'test'"),
|
||||
/// Host name or IP address type.
|
||||
|
@ -172,7 +172,7 @@ pub struct Provider {
|
|||
/// Note that this setting can be overridden
|
||||
/// on a per-request basis
|
||||
/// unless `forcedefault` is `true`.
|
||||
pub default: DefaultProvider,
|
||||
pub default: ProviderType,
|
||||
|
||||
/// Flag indicating whether the default provider should be enforced
|
||||
/// in preference to the per-request `provider` param.
|
||||
|
|
|
@ -131,14 +131,11 @@ fn env_vars_take_precedence() {
|
|||
let current_env = Env(String::from("test"));
|
||||
let hmac_key = String::from("something else");
|
||||
let provider = Provider {
|
||||
default: DefaultProvider(
|
||||
if settings.provider.default == DefaultProvider("ses".to_string()) {
|
||||
"sendgrid"
|
||||
} else {
|
||||
"ses"
|
||||
}
|
||||
.to_string(),
|
||||
),
|
||||
default: if settings.provider.default == ProviderType::Ses {
|
||||
ProviderType::Sendgrid
|
||||
} else {
|
||||
ProviderType::Ses
|
||||
},
|
||||
forcedefault: !settings.provider.forcedefault,
|
||||
};
|
||||
let redis_host = format!("{}1", &settings.redis.host);
|
||||
|
@ -216,7 +213,7 @@ fn env_vars_take_precedence() {
|
|||
env::set_var("FXA_EMAIL_ENV", ¤t_env.0);
|
||||
env::set_var("FXA_EMAIL_LOG_LEVEL", &log.level.0);
|
||||
env::set_var("FXA_EMAIL_LOG_FORMAT", &log.format.0);
|
||||
env::set_var("FXA_EMAIL_PROVIDER_DEFAULT", &provider.default.0);
|
||||
env::set_var("FXA_EMAIL_PROVIDER_DEFAULT", provider.default.as_ref());
|
||||
env::set_var(
|
||||
"FXA_EMAIL_PROVIDER_FORCEDEFAULT",
|
||||
provider.forcedefault.to_string(),
|
||||
|
|
|
@ -10,4 +10,5 @@ pub mod duration;
|
|||
pub mod email_address;
|
||||
pub mod error;
|
||||
pub mod headers;
|
||||
pub mod provider;
|
||||
pub mod validate;
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
//! Email provider type.
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
fmt::{self, Display, Formatter},
|
||||
};
|
||||
|
||||
use serde::{
|
||||
de::{Deserialize, Deserializer, Error as SerdeError, Unexpected},
|
||||
ser::{Serialize, Serializer},
|
||||
};
|
||||
|
||||
use types::error::{AppError, AppErrorKind};
|
||||
|
||||
/// Identifies the underlying email provider.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum Provider {
|
||||
Mock,
|
||||
Sendgrid,
|
||||
Ses,
|
||||
Smtp,
|
||||
SocketLabs,
|
||||
}
|
||||
|
||||
impl AsRef<str> for Provider {
|
||||
/// Return the provider as a string slice.
|
||||
fn as_ref(&self) -> &str {
|
||||
match *self {
|
||||
Provider::Mock => "mock",
|
||||
Provider::Sendgrid => "sendgrid",
|
||||
Provider::Ses => "ses",
|
||||
Provider::Smtp => "smtp",
|
||||
Provider::SocketLabs => "socketlabs",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Provider {
|
||||
fn default() -> Self {
|
||||
Provider::Ses
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Provider {
|
||||
/// Format the provider as a `String`.
|
||||
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
|
||||
write!(formatter, "{}", self.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> Deserialize<'d> for Provider {
|
||||
/// Deserialize a provider from its string representation.
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'d>,
|
||||
{
|
||||
let value: String = Deserialize::deserialize(deserializer)?;
|
||||
Provider::try_from(value.as_str())
|
||||
.map_err(|_| D::Error::invalid_value(Unexpected::Str(&value), &"provider"))
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Provider {
|
||||
/// Serialize a provider.
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'v> TryFrom<&'v str> for Provider {
|
||||
type Error = AppError;
|
||||
|
||||
/// Parse a provider from its string representation.
|
||||
fn try_from(value: &str) -> Result<Self, AppError> {
|
||||
match value {
|
||||
"mock" => Ok(Provider::Mock),
|
||||
"sendgrid" => Ok(Provider::Sendgrid),
|
||||
"ses" => Ok(Provider::Ses),
|
||||
"smtp" => Ok(Provider::Smtp),
|
||||
"socketlabs" => Ok(Provider::SocketLabs),
|
||||
_ => Err(AppErrorKind::InvalidPayload(format!(
|
||||
"provider `{}`",
|
||||
value
|
||||
)))?,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn try_from() {
|
||||
match Provider::try_from("mock") {
|
||||
Ok(provider) => assert_eq!(provider, Provider::Mock),
|
||||
Err(error) => assert!(false, error.to_string()),
|
||||
}
|
||||
match Provider::try_from("sendgrid") {
|
||||
Ok(provider) => assert_eq!(provider, Provider::Sendgrid),
|
||||
Err(error) => assert!(false, error.to_string()),
|
||||
}
|
||||
match Provider::try_from("ses") {
|
||||
Ok(provider) => assert_eq!(provider, Provider::Ses),
|
||||
Err(error) => assert!(false, error.to_string()),
|
||||
}
|
||||
match Provider::try_from("smtp") {
|
||||
Ok(provider) => assert_eq!(provider, Provider::Smtp),
|
||||
Err(error) => assert!(false, error.to_string()),
|
||||
}
|
||||
match Provider::try_from("socketlabs") {
|
||||
Ok(provider) => assert_eq!(provider, Provider::SocketLabs),
|
||||
Err(error) => assert!(false, error.to_string()),
|
||||
}
|
||||
match Provider::try_from("wibble") {
|
||||
Ok(_) => assert!(false, "Provider::try_from should have failed"),
|
||||
Err(error) => {
|
||||
assert_eq!(error.to_string(), "Invalid payload: provider `wibble`");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn as_ref() {
|
||||
assert_eq!(Provider::Mock.as_ref(), "mock");
|
||||
assert_eq!(Provider::Sendgrid.as_ref(), "sendgrid");
|
||||
assert_eq!(Provider::Ses.as_ref(), "ses");
|
||||
assert_eq!(Provider::Smtp.as_ref(), "smtp");
|
||||
assert_eq!(Provider::SocketLabs.as_ref(), "socketlabs");
|
||||
}
|
|
@ -32,7 +32,6 @@ lazy_static! {
|
|||
static ref HOST_FORMAT: Regex = Regex::new(r"^[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*$").unwrap();
|
||||
static ref LOGGING_LEVEL: Regex = Regex::new(r"^(?:normal|debug|critical|off)$").unwrap();
|
||||
static ref LOGGING_FORMAT: Regex = Regex::new(r"^(?:mozlog|pretty|null)$").unwrap();
|
||||
static ref PROVIDER_FORMAT: Regex = Regex::new(r"^(?:mock|sendgrid|ses|smtp|socketlabs)$").unwrap();
|
||||
static ref SENDER_NAME_FORMAT: Regex =
|
||||
Regex::new(r"^[A-Za-z0-9-]+(?: [A-Za-z0-9-]+)*$").unwrap();
|
||||
static ref SENDGRID_API_KEY_FORMAT: Regex = Regex::new("^[A-Za-z0-9._-]+$").unwrap();
|
||||
|
@ -87,11 +86,6 @@ pub fn logging_format(value: &str) -> bool {
|
|||
LOGGING_FORMAT.is_match(value)
|
||||
}
|
||||
|
||||
/// Validate an email provider.
|
||||
pub fn provider(value: &str) -> bool {
|
||||
PROVIDER_FORMAT.is_match(value)
|
||||
}
|
||||
|
||||
/// Validate a sender name.
|
||||
pub fn sender_name(value: &str) -> bool {
|
||||
SENDER_NAME_FORMAT.is_match(value)
|
||||
|
|
|
@ -170,23 +170,6 @@ fn invalid_host() {
|
|||
assert_eq!(validate::host("127.0.0.1:25"), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider() {
|
||||
assert!(validate::provider("mock"));
|
||||
assert!(validate::provider("smtp"));
|
||||
assert!(validate::provider("ses"));
|
||||
assert!(validate::provider("sendgrid"));
|
||||
assert!(validate::provider("socketlabs"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_provider() {
|
||||
assert_eq!(validate::provider("sses"), false);
|
||||
assert_eq!(validate::provider("sendgrids"), false);
|
||||
assert_eq!(validate::provider("ses "), false);
|
||||
assert_eq!(validate::provider(" sendgrid"), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sender_name() {
|
||||
assert!(validate::sender_name("foo"));
|
||||
|
|
Загрузка…
Ссылка в новой задаче