feat: add vec supporting

This commit is contained in:
Dmitriy Pleshevskiy 2021-04-22 02:11:04 +03:00
parent fbd37930a7
commit 5d6b335a7a
9 changed files with 405 additions and 30 deletions

View file

@ -1,5 +1,5 @@
use crate::ast::*;
use crate::utils::{is_option_type, vec_to_token_stream_2};
use crate::utils::{maybe_supported_box, vec_to_token_stream_2, SupportedBox};
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens, TokenStreamExt};
@ -128,6 +128,8 @@ impl ToTokens for Variable {
.unwrap_or_else(|| name.to_string().to_uppercase());
let meta = vec_to_token_stream_2(&self.meta);
let supported_box = maybe_supported_box(&ty);
let get_variable: TokenStream2 = if self.concat_parts.is_some() {
let concat_parts = self.concat_parts.as_ref().unwrap();
quote! {{
@ -136,13 +138,25 @@ impl ToTokens for Variable {
::std::env::set_var(#env_name, value.as_str());
value
}}
} 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 if let Some(initial) = &self.initial {
match supported_box {
Some(SupportedBox::Vec) => {
quote!(::itconfig::get_vec_env_or_set_default(#env_name, ",", #initial))
}
_ => quote!(::itconfig::get_env_or_set_default(#env_name, #initial)),
}
} else {
quote!(::itconfig::get_env_or_panic(#env_name))
match supported_box {
Some(SupportedBox::Option) => {
quote!(::itconfig::maybe_get_env(#env_name))
}
Some(SupportedBox::Vec) => {
quote!(::itconfig::get_vec_env_or_panic(#env_name, ","))
}
_ => {
quote!(::itconfig::get_env_or_panic(#env_name))
}
}
};
if self.is_static {

View file

@ -3,6 +3,13 @@ use quote::ToTokens;
use syn::{Path, Type};
const OPTION_PATH_IDENTS: &[&str] = &["Option|", "std|option|Option|", "core|option|Option|"];
const VEC_PATH_IDENTS: &[&str] = &["Vec|", "std|vec|Vec|"];
#[derive(Debug)]
pub enum SupportedBox {
Vec,
Option,
}
pub fn vec_to_token_stream_2<T>(input: &[T]) -> Vec<TokenStream2>
where
@ -22,15 +29,26 @@ fn path_ident(path: &Path) -> String {
})
}
fn is_option_path_ident(path_ident: String) -> bool {
fn is_option_path_ident(path_ident: &str) -> bool {
OPTION_PATH_IDENTS.iter().any(|s| path_ident == *s)
}
pub fn is_option_type(ty: &Type) -> bool {
fn is_vec_path_ident(path_ident: &str) -> bool {
VEC_PATH_IDENTS.iter().any(|s| path_ident == *s)
}
pub fn maybe_supported_box(ty: &Type) -> Option<SupportedBox> {
match ty {
Type::Path(ty_path) => {
ty_path.qself.is_none() && is_option_path_ident(path_ident(&ty_path.path))
Type::Path(ty_path) if ty_path.qself.is_none() => {
let path_ident = path_ident(&ty_path.path);
if is_option_path_ident(&path_ident) {
Some(SupportedBox::Option)
} else if is_vec_path_ident(&path_ident) {
Some(SupportedBox::Vec)
} else {
None
}
}
_ => false,
_ => None,
}
}

View file

@ -1,6 +1,6 @@
[package]
name = "itconfig_tests"
version = "0.1.0"
version = "0.1.1"
authors = ["Dmitriy Pleshevskiy <dmitriy@ideascup.me>"]
edition = "2018"
license = "MIT"

View file

@ -547,8 +547,6 @@ mod test_case_23 {
#[test]
fn optional_variables() {
config::init();
env::set_var("SOMETHING", "hello world");
assert_eq!(config::SOMETHING(), Some("hello world"));
@ -557,3 +555,21 @@ mod test_case_23 {
assert_eq!(config::NOTHING(), None);
}
}
mod test_case_24 {
use std::env;
itconfig::config! {
MY_VEC: Vec<&'static str>,
#[env_name = "MY_VEC"]
STD_VEC: std::vec::Vec<&'static str>,
}
#[test]
fn vector_of_values() {
env::set_var("MY_VEC", "paypal,stripe");
assert_eq!(config::MY_VEC(), vec!["paypal", "stripe"]);
assert_eq!(config::STD_VEC(), vec!["paypal", "stripe"]);
}
}

View file

@ -5,7 +5,7 @@ use std::ops::Deref;
/// When we read the environment variable, we automatically convert the value
/// to EnvString and then convert it to your expected type.
///
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, Default, PartialEq, Eq, Clone)]
pub struct EnvString(String);
impl<T> From<T> for EnvString

View file

@ -1,5 +1,6 @@
use crate::envstr::*;
use crate::error::*;
use crate::utils::*;
use std::env;
/// Same as get_env but returns Option enum instead Result
@ -152,18 +153,6 @@ where
.and_then(|env_str| parse_env_variable(env_name, env_str))
}
fn parse_env_variable<T>(env_name: &str, env_str: EnvString) -> Result<T, EnvError>
where
T: FromEnvString,
{
FromEnvString::from_env_string(&env_str)
.map_err(|_| EnvError::FailedToParse(env_name.to_string()))
}
fn make_panic<T>(e: EnvError) -> T {
panic!("{}", e)
}
#[cfg(test)]
mod tests {
use super::*;

322
itconfig/src/get_vec_env.rs Normal file
View file

@ -0,0 +1,322 @@
use crate::envstr::*;
use crate::error::*;
use crate::utils::*;
use std::env;
/// Same as get_vec_env but returns Option enum instead Result
///
/// Example
/// -------
///
/// ```rust
/// # extern crate itconfig;
/// # use itconfig::*;
/// use std::env;
///
/// #[derive(Debug, PartialEq, Eq)]
/// enum PaymentPlatform {
/// PayPal,
/// Stripe,
/// SomethingElse,
/// }
///
/// impl FromEnvString for PaymentPlatform {
/// type Err = &'static str;
///
/// fn from_env_string(envstr: &EnvString) -> Result<Self, Self::Err> {
/// match envstr.to_lowercase().as_str() {
/// "paypal" => Ok(Self::PayPal),
/// "stripe" => Ok(Self::Stripe),
/// "smth" => Ok(Self::SomethingElse),
/// _ => Err("Unsupported payment platform"),
/// }
/// }
/// }
///
///
/// fn main () {
/// env::set_var("PAYMENT_PLATFORMS", "paypal,stripe");
///
/// let payment_platforms: Option<Vec<PaymentPlatform>> = maybe_get_vec_env("PAYMENT_PLATFORMS", ",");
/// assert_eq!(
/// payment_platforms,
/// Some(vec![PaymentPlatform::PayPal, PaymentPlatform::Stripe])
/// );
/// }
/// ```
///
pub fn maybe_get_vec_env<T>(env_name: &str, sep: &'static str) -> Option<Vec<T>>
where
T: FromEnvString,
{
get_vec_env(env_name, sep).ok()
}
/// This function is similar as `get_vec_env`, but it unwraps result with panic on error.
///
/// Panics
/// ------
/// Application will panic if environment variable is missing or cannot parse variable to
/// expected type
///
pub fn get_vec_env_or_panic<T>(env_name: &str, sep: &'static str) -> Vec<T>
where
T: FromEnvString,
{
get_vec_env(env_name, sep).unwrap_or_else(make_panic)
}
/// Try to read environment variable, split by separator and parse each item to expected
/// type.
///
/// Example
/// -------
///
/// ```rust
/// # extern crate itconfig;
/// # use itconfig::get_vec_env;
/// use std::env;
///
/// fn main () {
/// env::set_var("DEBUG", "true");
///
/// let result: Vec<bool> = get_vec_env("DEBUG", ",").unwrap();
///
/// assert_eq!(result, vec![true]);
/// }
/// ```
///
pub fn get_vec_env<T>(env_name: &str, sep: &'static str) -> Result<Vec<T>, EnvError>
where
T: FromEnvString,
{
get_vec_env_or(env_name, sep, |_| {
Err(EnvError::MissingVariable(env_name.to_string()))
})
}
/// This function is similar as `get_vec_env_or_panic`, but you can pass default value for
/// environment variable with `ToEnvString` trait.
///
/// Panics
/// ------
/// Application will panic if cannot parse variable to expected type
///
/// Example
/// -------
///
/// ```rust
/// # extern crate itconfig;
/// # use itconfig::get_vec_env_or_default;
/// use std::env;
///
/// fn main () {
/// let result: Vec<bool> = get_vec_env_or_default("TESTING", ",", vec!["true"]);
/// assert_eq!(result, vec![true]);
/// }
/// ```
///
pub fn get_vec_env_or_default<T, D>(env_name: &str, sep: &'static str, default: Vec<D>) -> Vec<T>
where
T: FromEnvString,
D: ToEnvString,
{
get_vec_env_or(env_name, sep, |_| {
Ok(default.into_iter().map(EnvString::from).collect())
})
.unwrap_or_else(make_panic)
}
/// This function is similar as `get_vec_env_or_default`, but the default value will be set to environment
/// variable, if env variable is missed.
///
/// Panics
/// ------
/// Application will panic if cannot parse variable to expected type
///
/// Example
/// -------
///
/// ```rust
/// # extern crate itconfig;
/// # use itconfig::get_vec_env_or_set_default;
/// use std::env;
///
/// fn main () {
/// let result: Vec<bool> = get_vec_env_or_set_default("TESTING", ",", vec!["true"]);
/// assert_eq!(result, vec![true]);
///
/// let var = env::var("TESTING").unwrap();
/// assert_eq!(var, "true");
/// }
/// ```
///
pub fn get_vec_env_or_set_default<T, D>(
env_name: &str,
sep: &'static str,
default: Vec<D>,
) -> Vec<T>
where
T: FromEnvString,
D: ToEnvString,
{
get_vec_env_or(env_name, sep, |_| {
let default_env_strings: Vec<EnvString> =
default.into_iter().map(EnvString::from).collect();
let env_val =
default_env_strings
.iter()
.enumerate()
.fold(String::new(), |mut res, (i, item)| {
if i > 0 {
res.push_str(sep);
}
res.push_str(&item);
res
});
env::set_var(env_name, env_val.as_str());
Ok(default_env_strings)
})
.unwrap_or_else(make_panic)
}
/// This function returns env variable as `EnvString` structure. You can pass callback for custom
/// default expression. Callback should return `EnvString` value or `EnvError`
pub fn get_vec_env_or<T, F>(env_name: &str, sep: &'static str, cb: F) -> Result<Vec<T>, EnvError>
where
T: FromEnvString,
F: FnOnce(env::VarError) -> Result<Vec<EnvString>, EnvError>,
{
env::var(env_name)
.map(|s| {
s.split(sep)
.into_iter()
.map(|item| item.to_env_string())
.collect()
})
.or_else(cb)
.and_then(|items| {
items
.into_iter()
.map(|env_str| parse_env_variable(env_name, env_str))
.collect()
})
}
#[cfg(test)]
mod tests {
use super::*;
const SEP: &str = ",";
#[test]
#[should_panic(expected = "Environment variable \"TEST_CASE_VEC_1\" is missing")]
fn get_missing_vec_env() {
let _: Vec<&'static str> = get_vec_env_or_panic("TEST_CASE_VEC_1", SEP);
}
#[test]
#[should_panic(expected = "Failed to parse environment variable \"TEST_CASE_VEC_2\"")]
fn get_vec_env_with_invalid_value() {
let env_name = "TEST_CASE_VEC_2";
env::set_var(&env_name, "30r");
let _: Vec<u32> = get_vec_env_or_panic(env_name, SEP);
}
#[test]
fn get_result_of_missing_vec_env() {
let env_name = String::from("TEST_CASE_VEC_3");
let env_val = get_vec_env::<String>(&env_name, SEP);
assert_eq!(env_val, Err(EnvError::MissingVariable(env_name)))
}
#[test]
fn get_result_of_vec_env_with_invalid_value() {
let env_name = String::from("TEST_CASE_VEC_4");
env::set_var(&env_name, "30r");
let env_val = get_vec_env::<u32>(&env_name, SEP);
assert_eq!(env_val, Err(EnvError::FailedToParse(env_name)))
}
#[test]
fn get_result_of_vec_env_successfully() {
env::set_var("TEST_CASE_VEC_5", "30");
let env_var = get_vec_env("TEST_CASE_VEC_5", SEP);
assert_eq!(env_var, Ok(vec![30]));
}
#[test]
fn get_missing_vec_env_with_default_value() {
let flag: Vec<bool> = get_vec_env_or_default("TEST_CASE_VEC_6", SEP, vec!["true"]);
assert_eq!(flag, vec![true]);
}
#[test]
#[should_panic(expected = "Failed to parse environment variable \"TEST_CASE_VEC_7\"")]
fn get_invalid_vec_env_with_default_value() {
env::set_var("TEST_CASE_VEC_7", "30r");
get_vec_env_or_default::<u32, _>("TEST_CASE_VEC_7", SEP, vec![30]);
}
#[test]
#[should_panic(expected = "Failed to parse environment variable \"TEST_CASE_VEC_8\"")]
fn get_vec_env_with_invalid_default_value() {
get_vec_env_or_default::<u32, _>("TEST_CASE_VEC_8", SEP, vec!["30r"]);
}
#[test]
fn get_vec_env_with_default_successfully() {
env::set_var("TEST_CASE_VEC_9", "10");
let env_val: Vec<u32> = get_vec_env_or_default("TEST_CASE_VEC_9", SEP, vec![30]);
assert_eq!(env_val, vec![10])
}
#[test]
fn get_missing_vec_env_with_set_default_value() {
let flag: Vec<bool> = get_vec_env_or_set_default("TEST_CASE_VEC_10", SEP, vec!["true"]);
assert_eq!(flag, vec![true]);
let env_var = env::var("TEST_CASE_VEC_10");
assert_eq!(env_var, Ok(String::from("true")))
}
#[test]
fn get_optional_vec_env() {
env::set_var("TEST_CASE_VEC_11", "something");
let something: Option<Vec<&'static str>> = maybe_get_vec_env("TEST_CASE_VEC_11", SEP);
assert_eq!(something, Some(vec!["something"]));
let nothing: Option<Vec<&'static str>> = maybe_get_vec_env("TEST_CASE_VEC_11_NONE", SEP);
assert_eq!(nothing, None);
}
#[test]
fn get_custom_type_from_vec_env() {
#[derive(Debug, PartialEq, Eq)]
enum PaymentPlatform {
PayPal,
Stripe,
SomethingElse,
}
impl FromEnvString for PaymentPlatform {
type Err = &'static str;
fn from_env_string(envstr: &EnvString) -> Result<Self, Self::Err> {
match envstr.to_lowercase().as_str() {
"paypal" => Ok(Self::PayPal),
"stripe" => Ok(Self::Stripe),
"smth" => Ok(Self::SomethingElse),
_ => Err("Unsupported payment platform"),
}
}
}
env::set_var("TEST_CASE_VEC_12", "paypal,stripe");
let something: Option<Vec<PaymentPlatform>> = maybe_get_vec_env("TEST_CASE_VEC_12", SEP);
assert_eq!(
something,
Some(vec![PaymentPlatform::PayPal, PaymentPlatform::Stripe])
);
}
}

View file

@ -149,11 +149,14 @@
mod envstr;
mod error;
mod getenv;
mod get_env;
mod get_vec_env;
pub(crate) mod utils;
pub use self::envstr::*;
pub use self::error::*;
pub use self::getenv::*;
pub use self::get_env::*;
pub use self::get_vec_env::*;
#[cfg(feature = "macro")]
extern crate itconfig_macro;

13
itconfig/src/utils.rs Normal file
View file

@ -0,0 +1,13 @@
use crate::{EnvError, EnvString, FromEnvString};
pub(crate) fn parse_env_variable<T>(env_name: &str, env_str: EnvString) -> Result<T, EnvError>
where
T: FromEnvString,
{
FromEnvString::from_env_string(&env_str)
.map_err(|_| EnvError::FailedToParse(env_name.to_string()))
}
pub(crate) fn make_panic<T>(e: EnvError) -> T {
panic!("{}", e)
}