refactor(settings): promote Env to an enum from a wrapped string
This commit is contained in:
Родитель
592b4d38b2
Коммит
dba93f0dbf
|
@ -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", ¤t_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"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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"));
|
||||
|
|
Загрузка…
Ссылка в новой задаче