Merge pull request #249 from mozilla/pb/env-enum-type

https://github.com/mozilla/fxa-email-service/pull/249
r=vladikoff
This commit is contained in:
Phil Booth 2018-11-26 15:58:01 +00:00 коммит произвёл GitHub
Родитель 592b4d38b2 dba93f0dbf
Коммит abf4456098
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
10 изменённых файлов: 170 добавлений и 135 удалений

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

@ -9,6 +9,7 @@ mod serialize;
mod test;
use std::{
convert::TryFrom,
env,
fmt::{self, Display},
};
@ -22,7 +23,8 @@ use serde::de::{Deserialize, Deserializer, Error, Unexpected};
use logging::MozlogLogger;
use types::{
duration::Duration, email_address::EmailAddress, provider::Provider as ProviderType, validate,
duration::Duration, email_address::EmailAddress, env::Env, provider::Provider as ProviderType,
validate,
};
macro_rules! deserialize_and_validate {
@ -69,8 +71,6 @@ deserialize_and_validate! {
(AwsSecret, aws_secret, "AWS secret key"),
/// Base URI type.
(BaseUri, base_uri, "base URI"),
/// Env type.
(Env, env, "'dev', 'staging', 'production' or 'test'"),
/// Host name or IP address type.
(Host, host, "host name or IP address"),
/// Logging level type.
@ -379,12 +379,13 @@ impl Settings {
config.merge(File::with_name("config/default"))?;
let current_env = match env::var("FXA_EMAIL_ENV") {
Ok(env) => env,
_ => String::from("dev"),
let current_env: Env = match env::var("FXA_EMAIL_ENV") {
Ok(env) => TryFrom::try_from(env.as_str()).unwrap(),
_ => Env::default(),
};
config.merge(File::with_name(&format!("config/{}", current_env)).required(false))?;
config.set_default("env", "dev")?;
config
.merge(File::with_name(&format!("config/{}", current_env.as_ref())).required(false))?;
config.set_default("env", Env::default().as_ref())?;
config.merge(File::with_name("config/local").required(false))?;
let env = Environment::with_prefix("fxa_email").ignore_empty(true);
@ -392,7 +393,7 @@ impl Settings {
match config.try_into::<Settings>() {
Ok(settings) => {
if current_env == "production" {
if current_env == Env::Prod {
if &settings.hmackey == "YOU MUST CHANGE ME" {
panic!("Please set a valid HMAC key")
}
@ -418,9 +419,8 @@ impl Settings {
"critical" => RocketLoggingLevel::Critical,
_ => RocketLoggingLevel::Normal,
};
let rocket_config = match self.env.0.as_str() {
"production" => RocketEnvironment::Production,
"staging" => RocketEnvironment::Staging,
let rocket_config = match self.env {
Env::Prod => RocketEnvironment::Production,
_ => RocketEnvironment::Development,
};
RocketConfig::build(rocket_config)

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

@ -128,7 +128,11 @@ fn env_vars_take_precedence() {
}
};
let bounce_limits_enabled = !settings.bouncelimits.enabled;
let current_env = Env(String::from("test"));
let current_env = if settings.env == Env::Dev {
Env::Prod
} else {
Env::Dev
};
let hmac_key = String::from("something else");
let provider = Provider {
default: if settings.provider.default == ProviderType::Ses {
@ -210,7 +214,7 @@ fn env_vars_take_precedence() {
&bounce_limits_enabled.to_string(),
);
env::set_var("FXA_EMAIL_HMACKEY", &hmac_key.to_string());
env::set_var("FXA_EMAIL_ENV", &current_env.0);
env::set_var("FXA_EMAIL_ENV", current_env.as_ref());
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.as_ref());
@ -317,7 +321,7 @@ fn default_env() {
let _clean_env = CleanEnvironment::new(vec!["FXA_EMAIL_ENV"]);
match Settings::new() {
Ok(settings) => assert_eq!(settings.env, Env("dev".to_string())),
Ok(settings) => assert_eq!(settings.env, Env::Dev),
Err(_error) => assert!(false, "Settings::new shouldn't have failed"),
}
}

24
src/types/env/mod.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,24 @@
// 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/.
//! Environment type.
#[cfg(test)]
mod test;
use serde::de::Error;
use types::error::{AppError, AppErrorKind};
enum_boilerplate!(Env ("env", InvalidEnv) {
Dev => "dev",
Prod => "production",
Test => "test",
});
impl Default for Env {
fn default() -> Self {
Env::Dev
}
}

30
src/types/env/test.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,30 @@
// 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 std::convert::TryFrom;
use super::*;
#[test]
fn try_from() {
let result: Result<Env, AppError> = TryFrom::try_from("dev");
assert!(result.is_ok());
let result: Result<Env, AppError> = TryFrom::try_from("production");
assert!(result.is_ok());
let result: Result<Env, AppError> = TryFrom::try_from("test");
assert!(result.is_ok());
let result: Result<Env, AppError> = TryFrom::try_from("prod");
assert!(result.is_err());
assert_eq!(result.unwrap_err().to_string(), "Invalid environment: prod");
}
#[test]
fn as_ref() {
assert_eq!(Env::Dev.as_ref(), "dev");
assert_eq!(Env::Prod.as_ref(), "production");
assert_eq!(Env::Test.as_ref(), "test");
}

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

@ -188,6 +188,9 @@ pub enum AppErrorKind {
time: u64,
problem: DeliveryProblem,
},
#[fail(display = "Invalid environment: {}", _0)]
InvalidEnv(String),
}
impl AppErrorKind {
@ -213,6 +216,7 @@ impl AppErrorKind {
AppErrorKind::Complaint { .. } => Some(106),
AppErrorKind::SoftBounce { .. } => Some(107),
AppErrorKind::HardBounce { .. } => Some(108),
AppErrorKind::InvalidEnv { .. } => Some(109),
}
}

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

@ -6,8 +6,68 @@
//! for modules that implement
//! miscellaneous generally-used types.
macro_rules! enum_boilerplate {
($name:ident ($description:expr, $error:ident) {
$($variant:ident => $serialization:expr,)+
}) => {
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum $name {
$($variant,
)+
}
impl AsRef<str> for $name {
fn as_ref(&self) -> &str {
match *self {
$($name::$variant => $serialization,
)+
}
}
}
impl std::fmt::Display for $name {
fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(formatter, "{}", self.as_ref())
}
}
impl<'d> serde::de::Deserialize<'d> for $name {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'d>,
{
let value: String = serde::de::Deserialize::deserialize(deserializer)?;
std::convert::TryFrom::try_from(value.as_str())
.map_err(|_| D::Error::invalid_value(serde::de::Unexpected::Str(&value), &$description))
}
}
impl serde::ser::Serialize for $name {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
serializer.serialize_str(self.as_ref())
}
}
impl<'v> std::convert::TryFrom<&'v str> for $name {
type Error = AppError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
$($serialization => Ok($name::$variant),
)+
_ => Err(AppErrorKind::$error(value.to_owned()))?,
}
}
}
}
}
pub mod duration;
pub mod email_address;
pub mod env;
pub mod error;
pub mod headers;
pub mod provider;

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

@ -7,91 +7,20 @@
#[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 serde::de::Error;
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",
}
}
}
enum_boilerplate!(Provider ("env", InvalidPayload) {
Mock => "mock",
Sendgrid => "sendgrid",
Ses => "ses",
Smtp => "smtp",
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
)))?,
}
}
}

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

@ -2,36 +2,35 @@
// 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 std::convert::TryFrom;
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`");
}
}
let result: Result<Provider, AppError> = TryFrom::try_from("mock");
assert!(result.is_ok());
assert_eq!(result.unwrap(), Provider::Mock);
let result: Result<Provider, AppError> = TryFrom::try_from("sendgrid");
assert!(result.is_ok());
assert_eq!(result.unwrap(), Provider::Sendgrid);
let result: Result<Provider, AppError> = TryFrom::try_from("ses");
assert!(result.is_ok());
assert_eq!(result.unwrap(), Provider::Ses);
let result: Result<Provider, AppError> = TryFrom::try_from("smtp");
assert!(result.is_ok());
assert_eq!(result.unwrap(), Provider::Smtp);
let result: Result<Provider, AppError> = TryFrom::try_from("socketlabs");
assert!(result.is_ok());
assert_eq!(result.unwrap(), Provider::SocketLabs);
let result: Result<Provider, AppError> = TryFrom::try_from("wibble");
assert!(result.is_err());
assert_eq!(result.unwrap_err().to_string(), "Invalid payload: wibble");
}
#[test]

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

@ -28,7 +28,6 @@ lazy_static! {
static ref EMAIL_ADDRESS_FORMAT: Regex = Regex::new(
r"^[a-zA-Z0-9.\pL\pN!#$%&*+/=?^_`{|}~-]{1,64}@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?)+$"
).unwrap();
static ref ENV: Regex = Regex::new(r"^(?:dev|staging|production|test)$").unwrap();
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();
@ -66,11 +65,6 @@ pub fn email_address(value: &str) -> bool {
value.len() < 254 && EMAIL_ADDRESS_FORMAT.is_match(value)
}
/// Validate env.
pub fn env(value: &str) -> bool {
ENV.is_match(value)
}
/// Validate a host name or IP address.
pub fn host(value: &str) -> bool {
HOST_FORMAT.is_match(value)

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

@ -127,15 +127,6 @@ fn invalid_email_address() {
)));
}
#[test]
fn env() {
assert!(validate::env("test"));
assert!(validate::env("dev"));
assert!(validate::env("staging"));
assert!(validate::env("production"));
assert_eq!(false, validate::env("something else"));
}
#[test]
fn host() {
assert!(validate::host("foo"));