use error::{Error, Result};
use std::convert::TryFrom;
use std::fmt;
use std::io::{Read, Write};
use std::path::PathBuf;
use std::time::Duration;
use toml;
#[derive(Clone, Debug, PartialEq)]
pub enum RuntimeEnvironment {
Prod,
Stage,
Test,
Dev,
}
impl fmt::Display for RuntimeEnvironment {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
match *self {
RuntimeEnvironment::Prod => "-prod",
RuntimeEnvironment::Stage => "-stage",
RuntimeEnvironment::Test => "-test",
RuntimeEnvironment::Dev => "-dev",
}
)
}
}
impl<'a> TryFrom<&'a str> for RuntimeEnvironment {
type Error = Error;
fn try_from(env: &str) -> Result<Self> {
match env {
"prod" => Ok(RuntimeEnvironment::Prod),
"stage" => Ok(RuntimeEnvironment::Stage),
"test" => Ok(RuntimeEnvironment::Test),
"dev" => Ok(RuntimeEnvironment::Dev),
_ => Err("invalid runtime enviroment".into()),
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub enum EchoIndex {
MslCheckout,
ProxyApi,
ProxyEcsb,
ProxyBanner,
ProxyDC2,
}
impl Default for EchoIndex {
fn default() -> Self {
EchoIndex::MslCheckout
}
}
impl fmt::Display for EchoIndex {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
match *self {
EchoIndex::MslCheckout => "mobile-service-layer-checkout",
EchoIndex::ProxyApi => "proxy-api",
EchoIndex::ProxyEcsb => "proxy-ecsb",
EchoIndex::ProxyBanner => "proxy-banner",
EchoIndex::ProxyDC2 => "proxy-dc2",
}
)
}
}
#[derive(Clone, Debug, Default, Deserialize, Getters, PartialEq, Serialize, Setters)]
pub struct Config {
#[get = "pub"]
#[set = "pub"]
common: Common,
#[get = "pub"]
#[set = "pub"]
index: Vec<Index>,
#[get = "pub"]
#[set = "pub"]
prod: Environment,
#[get = "pub"]
#[set = "pub"]
stage: Environment,
#[get = "pub"]
#[set = "pub"]
test: Environment,
#[get = "pub"]
#[set = "pub"]
dev: Environment,
}
#[derive(Clone, Debug, Default, Deserialize, Getters, PartialEq, Serialize, Setters)]
pub struct Common {
#[get = "pub"]
#[set = "pub"]
base_db_path: PathBuf,
#[set = "pub"]
search_prefix: Option<String>,
#[set = "pub"]
search_affix: Option<String>,
#[set = "pub"]
search_suffix: Option<String>,
#[set = "pub"]
db_prefix: Option<String>,
#[set = "pub"]
db_suffix: Option<String>,
}
impl Common {
pub fn search_prefix(&self) -> &str {
if let Some(ref prefix) = self.search_prefix {
prefix
} else {
"http://echo"
}
}
pub fn search_affix(&self) -> &str {
if let Some(ref affix) = self.search_affix {
affix
} else {
".kroger.com/elastic"
}
}
pub fn search_suffix(&self) -> &str {
if let Some(ref suffix) = self.search_suffix {
suffix
} else {
"/_search"
}
}
pub fn scroll_affix(&self) -> &str {
"/scroll"
}
pub fn db_prefix(&self) -> &str {
if let Some(ref prefix) = self.search_prefix {
prefix
} else {
"ordmon"
}
}
pub fn db_suffix(&self) -> &str {
if let Some(ref suffix) = self.search_suffix {
suffix
} else {
".db"
}
}
}
#[derive(Clone, Debug, Default, Deserialize, Getters, PartialEq, Serialize, Setters)]
pub struct Index {
#[get = "pub"]
#[set = "pub"]
idx_prefix: EchoIndex,
#[get = "pub"]
#[set = "pub"]
fields: Vec<Field>,
}
#[derive(Clone, Debug, Default, Deserialize, Getters, PartialEq, Serialize, Setters)]
pub struct Field {
#[get = "pub"]
#[set = "pub"]
name: String,
#[get = "pub"]
#[set = "pub"]
data_type: SqliteDataType,
#[get = "pub"]
#[set = "pub"]
primary_key: Option<bool>,
#[get = "pub"]
#[set = "pub"]
not_null: Option<bool>,
#[get = "pub"]
#[set = "pub"]
unique: Option<bool>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub enum SqliteDataType {
Integer,
Text,
}
impl Default for SqliteDataType {
fn default() -> Self {
SqliteDataType::Integer
}
}
impl fmt::Display for SqliteDataType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
match *self {
SqliteDataType::Integer => "INTEGER",
SqliteDataType::Text => "TEXT",
}
)
}
}
#[derive(Clone, Debug, Default, Deserialize, Getters, PartialEq, Serialize, Setters)]
pub struct Environment {
#[get = "pub"]
#[set = "pub"]
echo_env_affix: Option<String>,
#[get = "pub"]
#[set = "pub"]
verbose: bool,
#[get = "pub"]
#[set = "pub"]
duration: Duration,
}
pub fn read_toml<R>(reader: &mut R) -> Result<Config>
where
R: Read,
{
let mut toml_str = String::new();
let bytes_read = reader.read_to_string(&mut toml_str)?;
if bytes_read > 0 {
Ok(toml::from_str(&toml_str)?)
} else {
Err("Unable to read any bytes from the reader".into())
}
}
pub fn write_toml<W>(config: &Config, writer: &mut W) -> Result<()>
where
W: Write,
{
let toml = toml::to_string(&config)?;
writer.write_all(toml.as_bytes())?;
Ok(())
}
pub fn echo_url(
config: &Config,
runtime_env: &RuntimeEnvironment,
echo_idx: &EchoIndex,
scroll: bool,
) -> String {
let common = config.common();
let env = match *runtime_env {
RuntimeEnvironment::Prod => config.prod(),
RuntimeEnvironment::Stage => config.stage(),
RuntimeEnvironment::Test => config.test(),
RuntimeEnvironment::Dev => config.dev(),
};
let echo_affix = if let Some(ref affix) = env.echo_env_affix() {
affix.to_string()
} else {
runtime_env.to_string()
};
let index_str = echo_idx.to_string();
if scroll {
format!(
"{}{}{}/{}{}{}?scroll=1m",
common.search_prefix(),
echo_affix,
common.search_affix(),
index_str,
runtime_env,
common.search_suffix()
)
} else {
format!(
"{}{}{}/{}{}{}",
common.search_prefix(),
echo_affix,
common.search_affix(),
index_str,
runtime_env,
common.search_suffix()
)
}
}
pub fn scroll_url(config: &Config, runtime_env: &RuntimeEnvironment) -> String {
let common = config.common();
let env = match *runtime_env {
RuntimeEnvironment::Prod => config.prod(),
RuntimeEnvironment::Stage => config.stage(),
RuntimeEnvironment::Test => config.test(),
RuntimeEnvironment::Dev => config.dev(),
};
let echo_affix = if let Some(ref affix) = env.echo_env_affix() {
affix.to_string()
} else {
runtime_env.to_string()
};
format!(
"{}{}{}{}{}",
common.search_prefix(),
echo_affix,
common.search_affix(),
common.search_suffix(),
common.scroll_affix()
)
}
pub fn db_path(config: &Config, runtime_env: &RuntimeEnvironment) -> PathBuf {
let common = config.common();
let mut database_path = common.base_db_path().clone();
let filename = format!(
"{}{}{}",
common.db_prefix(),
runtime_env,
common.db_suffix()
);
database_path.push(filename);
database_path
}
#[cfg(test)]
mod test {
use super::{
Common, Config, EchoIndex, Environment, Field, Index, RuntimeEnvironment, SqliteDataType,
};
use error::Result;
use std::convert::TryFrom;
use std::path::PathBuf;
use std::time::Duration;
use toml;
const TEST_TOML: &str = r#"[common]
base_db_path = "/Users/kon8116/projects/kon8116/atters"
[[index]]
idx_prefix = "MslCheckout"
[[index.fields]]
name = "id"
data_type = "Integer"
primary_key = true
[[index.fields]]
name = "correlation_id"
data_type = "Text"
not_null = true
[[index]]
idx_prefix = "ProxyApi"
[[index.fields]]
name = "id"
data_type = "Integer"
primary_key = true
[[index.fields]]
name = "correlation_id"
data_type = "Text"
not_null = true
[prod]
echo_env_affix = "-digital"
verbose = false
[prod.duration]
secs = 900
nanos = 0
[stage]
verbose = false
[stage.duration]
secs = 900
nanos = 0
[test]
verbose = true
[test.duration]
secs = 900
nanos = 0
[dev]
verbose = true
[dev.duration]
secs = 900
nanos = 0
"#;
fn setup_config() -> Config {
let mut config: Config = Default::default();
let mut common: Common = Default::default();
let mut msl_checkout_index: Index = Default::default();
let mut proxy_api_index: Index = Default::default();
let mut prod: Environment = Default::default();
let mut stage: Environment = Default::default();
let mut test: Environment = Default::default();
let mut dev: Environment = Default::default();
common.set_base_db_path(PathBuf::from("/Users/kon8116/projects/kon8116/atters"));
let mut id: Field = Default::default();
id.set_name("id".to_string());
id.set_primary_key(Some(true));
let mut correlation_id: Field = Default::default();
correlation_id.set_name("correlation_id".to_string());
correlation_id.set_data_type(SqliteDataType::Text);
correlation_id.set_not_null(Some(true));
msl_checkout_index.set_fields(vec![id.clone(), correlation_id.clone()]);
proxy_api_index.set_idx_prefix(EchoIndex::ProxyApi);
proxy_api_index.set_fields(vec![id, correlation_id]);
prod.set_echo_env_affix(Some("-digital".to_string()));
prod.set_verbose(false);
prod.set_duration(Duration::from_millis(900000));
stage.set_verbose(false);
stage.set_duration(Duration::from_millis(900000));
test.set_verbose(true);
test.set_duration(Duration::from_millis(900000));
dev.set_verbose(true);
dev.set_duration(Duration::from_millis(900000));
config.set_common(common);
config.set_index(vec![msl_checkout_index, proxy_api_index]);
config.set_prod(prod);
config.set_stage(stage);
config.set_test(test);
config.set_dev(dev);
config
}
#[test]
fn serialize() {
let config = setup_config();
let toml = toml::to_string(&config).expect("Unable to serialize to TOML");
assert_eq!(TEST_TOML, toml);
}
#[test]
fn deserialize() {
let config: Config = toml::from_str(TEST_TOML).expect("Unable to deserialize TOML");
let common = config.common();
let prod = config.prod();
let stage = config.stage();
let test = config.test();
let dev = config.dev();
assert_eq!(
common.base_db_path(),
&PathBuf::from("/Users/kon8116/projects/kon8116/atters")
);
assert_eq!(common.search_prefix(), "http://echo".to_string());
assert_eq!(common.search_affix(), ".kroger.com/elastic".to_string());
assert_eq!(common.search_suffix(), "/_search".to_string());
assert_eq!(common.db_prefix(), "ordmon".to_string());
assert_eq!(common.db_suffix(), ".db".to_string());
assert_eq!(*prod.echo_env_affix(), Some("-digital".to_string()));
assert!(!prod.verbose());
assert_eq!(*prod.duration(), Duration::from_millis(900000));
assert!(!stage.verbose());
assert_eq!(*stage.duration(), Duration::from_millis(900000));
assert!(test.verbose());
assert_eq!(*test.duration(), Duration::from_millis(900000));
assert!(dev.verbose());
assert_eq!(*dev.duration(), Duration::from_millis(900000));
}
#[test]
fn convert() {
let mut runtime_env: RuntimeEnvironment =
TryFrom::try_from("prod").expect("invalid runtime env");
assert_eq!(runtime_env, RuntimeEnvironment::Prod);
runtime_env = TryFrom::try_from("stage").expect("invalid runtime env");
assert_eq!(runtime_env, RuntimeEnvironment::Stage);
runtime_env = TryFrom::try_from("test").expect("invalid runtime env");
assert_eq!(runtime_env, RuntimeEnvironment::Test);
runtime_env = TryFrom::try_from("dev").expect("invalid runtime env");
assert_eq!(runtime_env, RuntimeEnvironment::Dev);
let err_runtime_env: Result<RuntimeEnvironment> = TryFrom::try_from("blah");
match err_runtime_env {
Ok(_) => assert!(false, "This shouldn't be a valid env"),
Err(_) => assert!(true),
}
}
#[test]
fn echo_url() {
let config: Config = toml::from_str(TEST_TOML).expect("Unable to deserialize TOML");
assert_eq!(
super::echo_url(
&config,
&RuntimeEnvironment::Prod,
&EchoIndex::MslCheckout,
false
),
"http://echo-digital.kroger.com/elastic/mobile-service-layer-checkout-prod/_search"
);
assert_eq!(
super::echo_url(&config, &RuntimeEnvironment::Prod, &EchoIndex::MslCheckout, true),
"http://echo-digital.kroger.com/elastic/mobile-service-layer-checkout-prod/_search?scroll=1m"
);
assert_eq!(
super::echo_url(
&config,
&RuntimeEnvironment::Stage,
&EchoIndex::MslCheckout,
false
),
"http://echo-stage.kroger.com/elastic/mobile-service-layer-checkout-stage/_search"
);
assert_eq!(
super::echo_url(
&config,
&RuntimeEnvironment::Test,
&EchoIndex::MslCheckout,
false
),
"http://echo-test.kroger.com/elastic/mobile-service-layer-checkout-test/_search"
);
assert_eq!(
super::echo_url(
&config,
&RuntimeEnvironment::Dev,
&EchoIndex::MslCheckout,
false
),
"http://echo-dev.kroger.com/elastic/mobile-service-layer-checkout-dev/_search"
);
assert_eq!(
super::echo_url(
&config,
&RuntimeEnvironment::Prod,
&EchoIndex::ProxyApi,
false
),
"http://echo-digital.kroger.com/elastic/proxy-api-prod/_search"
);
assert_eq!(
super::echo_url(
&config,
&RuntimeEnvironment::Stage,
&EchoIndex::ProxyApi,
false
),
"http://echo-stage.kroger.com/elastic/proxy-api-stage/_search"
);
assert_eq!(
super::echo_url(
&config,
&RuntimeEnvironment::Test,
&EchoIndex::ProxyApi,
false
),
"http://echo-test.kroger.com/elastic/proxy-api-test/_search"
);
assert_eq!(
super::echo_url(
&config,
&RuntimeEnvironment::Dev,
&EchoIndex::ProxyApi,
false
),
"http://echo-dev.kroger.com/elastic/proxy-api-dev/_search"
);
}
#[test]
fn scroll_url() {
let config: Config = toml::from_str(TEST_TOML).expect("Unable to deserialize TOML");
assert_eq!(
super::scroll_url(&config, &RuntimeEnvironment::Prod),
"http://echo-digital.kroger.com/elastic/_search/scroll"
);
assert_eq!(
super::scroll_url(&config, &RuntimeEnvironment::Stage),
"http://echo-stage.kroger.com/elastic/_search/scroll"
);
assert_eq!(
super::scroll_url(&config, &RuntimeEnvironment::Test),
"http://echo-test.kroger.com/elastic/_search/scroll"
);
assert_eq!(
super::scroll_url(&config, &RuntimeEnvironment::Dev),
"http://echo-dev.kroger.com/elastic/_search/scroll"
);
}
#[test]
fn db_path() {
let config: Config = toml::from_str(TEST_TOML).expect("Unable to deserialize TOML");
assert_eq!(
super::db_path(&config, &RuntimeEnvironment::Prod),
PathBuf::from("/Users/kon8116/projects/kon8116/atters/ordmon-prod.db")
);
assert_eq!(
super::db_path(&config, &RuntimeEnvironment::Stage),
PathBuf::from("/Users/kon8116/projects/kon8116/atters/ordmon-stage.db")
);
assert_eq!(
super::db_path(&config, &RuntimeEnvironment::Test),
PathBuf::from("/Users/kon8116/projects/kon8116/atters/ordmon-test.db")
);
assert_eq!(
super::db_path(&config, &RuntimeEnvironment::Dev),
PathBuf::from("/Users/kon8116/projects/kon8116/atters/ordmon-dev.db")
);
}
}