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:
Phil Booth 2018-11-10 09:12:20 +00:00 коммит произвёл GitHub
Родитель 3a13ef4561 0bd766d431
Коммит c9c62459c8
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
9 изменённых файлов: 176 добавлений и 57 удалений

Просмотреть файл

@ -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"
default: if settings.provider.default == ProviderType::Ses {
ProviderType::Sendgrid
} else {
"ses"
}
.to_string(),
),
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", &current_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;

97
src/types/provider/mod.rs Normal file
Просмотреть файл

@ -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"));