diff --git a/itconfig-macro/src/expand.rs b/itconfig-macro/src/expand.rs index 3789ab3..99db9dc 100644 --- a/itconfig-macro/src/expand.rs +++ b/itconfig-macro/src/expand.rs @@ -1,6 +1,8 @@ use crate::ast::*; use proc_macro2::TokenStream as TokenStream2; use quote::{quote, ToTokens, TokenStreamExt}; +use syn::Path; +use syn::Type; fn vec_to_token_stream_2(input: &Vec) -> Vec where @@ -145,6 +147,8 @@ impl ToTokens for Variable { } else if self.initial.is_some() { let initial = self.initial.as_ref().unwrap(); quote!(::itconfig::get_env_or_set_default(#env_name, #initial)) + } else if is_option_type(&self.ty) { + quote!(::itconfig::maybe_get_env(#env_name)) } else { quote!(::itconfig::get_env_or_panic(#env_name)) }; @@ -170,3 +174,30 @@ impl ToTokens for Variable { } } } + +fn path_ident(path: &Path) -> String { + path.segments + .iter() + .into_iter() + .fold(String::with_capacity(250), |mut acc, v| { + acc.push_str(&v.ident.to_string()); + acc.push('|'); + acc + }) +} + +fn is_option_path_ident(path_ident: String) -> bool { + vec!["Option|", "std|option|Option|", "core|option|Option|"] + .into_iter() + .find(|s| &path_ident == *s) + .is_some() +} + +fn is_option_type(ty: &Type) -> bool { + match ty { + Type::Path(ty_path) => { + ty_path.qself.is_none() && is_option_path_ident(path_ident(&ty_path.path)) + } + _ => false, + } +} diff --git a/itconfig-tests/tests/config_macro.rs b/itconfig-tests/tests/config_macro.rs index b93ae6b..e1ad3ac 100644 --- a/itconfig-tests/tests/config_macro.rs +++ b/itconfig-tests/tests/config_macro.rs @@ -531,3 +531,22 @@ mod test_case_22 { assert_eq!(config::STATIC_CONCAT_VARIABLE(), "static part".to_string()) } } + +mod test_case_23 { + use std::env; + + itconfig::config! { + SOMETHING: Option<&'static str>, + NOTHING: Option<&'static str>, + } + + #[test] + fn optional_variables() { + config::init(); + + env::set_var("SOMETHING", "hello world"); + + assert_eq!(config::SOMETHING(), Some("hello world")); + assert_eq!(config::NOTHING(), None); + } +} diff --git a/itconfig-tests/tests/get_env.rs b/itconfig-tests/tests/get_env.rs deleted file mode 100644 index bf99979..0000000 --- a/itconfig-tests/tests/get_env.rs +++ /dev/null @@ -1,74 +0,0 @@ -use itconfig::EnvError::*; -use itconfig::*; -use std::env; - -#[test] -#[should_panic(expected = "Environment variable \"TEST_CASE_1\" is missing")] -fn get_missing_env() { - get_env_or_panic::("TEST_CASE_1"); -} - -#[test] -#[should_panic(expected = "Failed to parse environment variable \"TEST_CASE_2\"")] -fn get_env_with_invalid_value() { - let env_name = "TEST_CASE_2"; - env::set_var(&env_name, "30r"); - get_env_or_panic::(env_name); -} - -#[test] -fn get_result_of_missing_env() { - let env_name = String::from("TEST_CASE_3"); - let env_val = get_env::(&env_name); - assert_eq!(env_val, Err(MissingVariable(env_name))) -} - -#[test] -fn get_result_of_env_with_invalid_value() { - let env_name = String::from("TEST_CASE_4"); - env::set_var(&env_name, "30r"); - let env_val = get_env::(&env_name); - assert_eq!(env_val, Err(FailedToParse(env_name))) -} - -#[test] -fn get_result_of_env_successfully() { - env::set_var("TEST_CASE_5", "30"); - let env_var = get_env("TEST_CASE_5"); - assert_eq!(env_var, Ok(30)); -} - -#[test] -fn get_missing_env_with_default_value() { - let flag: bool = get_env_or_default("TEST_CASE_6", "true"); - assert_eq!(flag, true); -} - -#[test] -#[should_panic(expected = "Failed to parse environment variable \"TEST_CASE_7\"")] -fn get_invalid_env_with_default_value() { - env::set_var("TEST_CASE_7", "30r"); - get_env_or_default::("TEST_CASE_7", 30); -} - -#[test] -#[should_panic(expected = "Failed to parse environment variable \"TEST_CASE_8\"")] -fn get_env_with_invalid_default_value() { - get_env_or_default::("TEST_CASE_8", "30r"); -} - -#[test] -fn get_env_with_default_successfully() { - env::set_var("TEST_CASE_9", "10"); - let env_val: u32 = get_env_or_default("TEST_CASE_9", 30); - assert_eq!(env_val, 10) -} - -#[test] -fn get_missing_env_with_set_default_value() { - let flag: bool = get_env_or_set_default("TEST_CASE_10", "true"); - assert_eq!(flag, true); - - let env_var = env::var("TEST_CASE_10"); - assert_eq!(env_var, Ok(String::from("true"))) -} diff --git a/itconfig/src/getenv.rs b/itconfig/src/getenv.rs index a3240e5..a396604 100644 --- a/itconfig/src/getenv.rs +++ b/itconfig/src/getenv.rs @@ -1,6 +1,34 @@ use crate::prelude::*; use std::env; +/// Same as get_env but returns Option enum instead Result +/// +/// Example +/// ------- +/// +/// ```rust +/// # extern crate itconfig; +/// # use itconfig::maybe_get_env; +/// use std::env; +/// +/// fn main () { +/// env::set_var("HOST", "https://example.com"); +/// +/// let host: Option<&'static str> = maybe_get_env("HOST"); +/// let not_existence_host: Option<&'static str> = maybe_get_env("NOT_EXISTENCE_HOST"); +/// +/// assert_eq!(host, Some("https://example.com")); +/// assert_eq!(not_existence_host, None); +/// } +/// ``` +/// +pub fn maybe_get_env(env_name: &str) -> Option +where + T: FromEnvString, +{ + get_env(env_name).ok() +} + /// This function is similar as `get_env`, but it unwraps result with panic on error. /// /// Panics @@ -137,3 +165,89 @@ where fn make_panic(e: EnvError) -> T { panic!("{}", e) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[should_panic(expected = "Environment variable \"TEST_CASE_1\" is missing")] + fn get_missing_env() { + get_env_or_panic::("TEST_CASE_1"); + } + + #[test] + #[should_panic(expected = "Failed to parse environment variable \"TEST_CASE_2\"")] + fn get_env_with_invalid_value() { + let env_name = "TEST_CASE_2"; + env::set_var(&env_name, "30r"); + get_env_or_panic::(env_name); + } + + #[test] + fn get_result_of_missing_env() { + let env_name = String::from("TEST_CASE_3"); + let env_val = get_env::(&env_name); + assert_eq!(env_val, Err(EnvError::MissingVariable(env_name))) + } + + #[test] + fn get_result_of_env_with_invalid_value() { + let env_name = String::from("TEST_CASE_4"); + env::set_var(&env_name, "30r"); + let env_val = get_env::(&env_name); + assert_eq!(env_val, Err(EnvError::FailedToParse(env_name))) + } + + #[test] + fn get_result_of_env_successfully() { + env::set_var("TEST_CASE_5", "30"); + let env_var = get_env("TEST_CASE_5"); + assert_eq!(env_var, Ok(30)); + } + + #[test] + fn get_missing_env_with_default_value() { + let flag: bool = get_env_or_default("TEST_CASE_6", "true"); + assert_eq!(flag, true); + } + + #[test] + #[should_panic(expected = "Failed to parse environment variable \"TEST_CASE_7\"")] + fn get_invalid_env_with_default_value() { + env::set_var("TEST_CASE_7", "30r"); + get_env_or_default::("TEST_CASE_7", 30); + } + + #[test] + #[should_panic(expected = "Failed to parse environment variable \"TEST_CASE_8\"")] + fn get_env_with_invalid_default_value() { + get_env_or_default::("TEST_CASE_8", "30r"); + } + + #[test] + fn get_env_with_default_successfully() { + env::set_var("TEST_CASE_9", "10"); + let env_val: u32 = get_env_or_default("TEST_CASE_9", 30); + assert_eq!(env_val, 10) + } + + #[test] + fn get_missing_env_with_set_default_value() { + let flag: bool = get_env_or_set_default("TEST_CASE_10", "true"); + assert_eq!(flag, true); + + let env_var = env::var("TEST_CASE_10"); + assert_eq!(env_var, Ok(String::from("true"))) + } + + #[test] + fn get_optional_env() { + env::set_var("TEST_CASE_11", "something"); + let something: Option<&'static str> = maybe_get_env("TEST_CASE_11"); + assert_eq!(something, Some("something")); + + let nothing: Option<&'static str> = maybe_get_env("TEST_CASE_11_NONE"); + assert_eq!(nothing, None); + } +}