Merge pull request #9 from icetemple/rewrite

refac: change structure
This commit is contained in:
Dmitriy Pleshevskiy 2020-01-17 00:17:37 +03:00 committed by GitHub
commit 0aa258cf17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1022 additions and 863 deletions

View File

@ -1,10 +1,10 @@
[package]
name = "itconfig"
version = "0.8.0"
version = "0.9.0"
authors = ["Dmitriy Pleshevskiy <dmitriy@ideascup.me>"]
description = "Easy build a configs from environment variables and use it in globally."
categories = ["config", "web-programming"]
keywords = ["config", "env", "macro", "configuration", "environment"]
keywords = ["config", "env", "configuration", "environment", "macro"]
edition = "2018"
license = "MIT"
repository = "https://github.com/icetemple/itconfig-rs"
@ -19,3 +19,8 @@ travis-ci = { repository = "icetemple/itconfig-rs" }
maintenance = { status = "actively-developed" }
[dependencies]
[features]
default = ['macro']
macro = []

92
itconfig/src/envstr.rs Normal file
View File

@ -0,0 +1,92 @@
use std::ops::Deref;
use std::str::FromStr;
#[doc(hidden)]
pub trait ToEnvString {
fn to_env_string(&self) -> EnvString;
}
#[doc(hidden)]
pub trait FromEnvString: Sized {
type Err;
fn from_env_string(s: &EnvString) -> Result<Self, Self::Err>;
}
impl<T> ToEnvString for T
where
T: ToString
{
#[inline]
fn to_env_string(&self) -> EnvString {
EnvString(self.to_string())
}
}
#[doc(hidden)]
macro_rules! from_env_string_numbers_impl {
($($ty:ty),+) => {
$(
impl FromEnvString for $ty {
type Err = <$ty as FromStr>::Err;
#[inline]
fn from_env_string(s: &EnvString) -> Result<Self, Self::Err> {
s.0.parse::<Self>()
}
}
)+
};
}
from_env_string_numbers_impl![
i8, i16, i32, i64, i128, isize,
u8, u16, u32, u64, u128, usize,
f32, f64
];
impl FromEnvString for bool {
type Err = ();
fn from_env_string(s: &EnvString) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"true" | "t" | "yes" | "y" | "on" | "1" => Ok(true),
_ => Ok(false)
}
}
}
impl FromEnvString for String {
type Err = ();
fn from_env_string(s: &EnvString) -> Result<Self, Self::Err> {
Ok(s.0.clone())
}
}
#[doc(hidden)]
#[derive(Debug, PartialEq, Clone)]
pub struct EnvString(String);
impl EnvString {
pub fn parse<T: FromEnvString>(&self) -> Result<T, T::Err> {
FromEnvString::from_env_string(self)
}
}
impl Deref for EnvString {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.0
}
}

141
itconfig/src/getenv.rs Normal file
View File

@ -0,0 +1,141 @@
use std::env;
use crate::envstr::{EnvString, FromEnvString, ToEnvString};
#[doc(hidden)]
macro_rules! env_panic {
(MissingVariable, $env_name:expr) => {
panic!(r#"Environment variable "{}" is missing"#, $env_name)
};
(FailedToParse, $env_name:expr) => {
panic!(r#"Failed to parse environment variable "{}""#, $env_name)
};
}
/// Try to read environment variable and parse to expected type. You may to put to argument
/// any type with `FromEnvString` trait.
///
/// Panics
/// ------
/// Application will panic if environment variable is missing or cannot parse variable to
/// expected type
///
/// Example
/// -------
///
/// ```rust
/// # extern crate itconfig;
/// # use itconfig::get_env;
/// use std::env;
///
/// fn main () {
/// env::set_var("DEBUG", "true");
///
/// let result: bool = get_env("DEBUG");
///
/// assert_eq!(result, true);
/// }
/// ```
///
pub fn get_env<T>(env_name: &str) -> T
where
T: FromEnvString
{
get_env_or(env_name, || env_panic!(MissingVariable, env_name))
}
/// This function is similar as `get_env` but more safely. 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_env_or_default;
/// use std::env;
///
/// fn main () {
/// let result: bool = get_env_or_default("TESTING", "true");
/// assert_eq!(result, true);
/// }
/// ```
///
pub fn get_env_or_default<T, D>(env_name: &str, default: D) -> T
where
T: FromEnvString,
D: ToEnvString,
{
get_env_or(env_name, || default.to_env_string())
}
/// This function is similar as `get_env_or_default` but if env variable is missed, will set
/// default value to environment variable.
///
/// Panics
/// ------
/// Application will panic if cannot parse variable to expected type
///
/// Example
/// -------
///
/// ```rust
/// # extern crate itconfig;
/// # use itconfig::get_env_or_set_default;
/// use std::env;
///
/// fn main () {
/// let result: bool = get_env_or_set_default("TESTING", "true");
/// assert_eq!(result, true);
///
/// let var = env::var("TESTING").unwrap();
/// assert_eq!(var, "true");
/// }
/// ```
///
pub fn get_env_or_set_default<T, D>(env_name: &str, default: D) -> T
where
T: FromEnvString,
D: ToEnvString,
{
get_env_or(env_name, || {
let val = default.to_env_string();
env::set_var(env_name, val.as_str());
val
})
}
/// This function returns env variable as `EnvString` structure. You can pass callback for custom
/// default expression. Callback should return `EnvString` value or `panic!`
pub fn get_env_or<T, F>(env_name: &str, cb: F) -> T
where
T: FromEnvString,
F: FnOnce() -> EnvString
{
let env_str = env::var(env_name)
.map(|s| s.to_env_string())
.unwrap_or_else(|_| cb());
parse_env_variable(env_name, env_str)
}
#[doc(hidden)]
fn parse_env_variable<T>(env_name: &str, env_str: EnvString) -> T
where
T: FromEnvString
{
env_str
.parse::<T>()
.unwrap_or_else(|_| env_panic!(FailedToParse, env_name))
}

View File

@ -1,861 +1,22 @@
//! # itconfig
//!
//! Simple configuration with macro for rust application.
//!
//!
//! ## Motivation
//!
//! I began to use rust with web programming experience where environment variables are widely used
//! and often there are more then 50 of them. First I looked at already created libraries.
//! But there it's necessary to initialise structure that needs to be moved to each function
//! where you need variable. It uses little bit memory, but configuration lifetime is as long
//! as application lifetime. Because of it I decided to create my own library.
//!
//!
//! ## Example usage
//!
//! ```rust
//! #[macro_use] extern crate itconfig;
//! use std::env;
//! // use dotenv::dotenv;
//!
//! config! {
//! DEBUG: bool => true,
//! HOST: String => "127.0.0.1",
//!
//! DATABASE_URL < (
//! "postgres://",
//! POSTGRES_USERNAME => "user",
//! ":",
//! POSTGRES_PASSWORD => "pass",
//! "@",
//! POSTGRES_HOST => "localhost:5432",
//! "/",
//! POSTGRES_DB => "test",
//! ),
//!
//! APP {
//! ARTICLE {
//! PER_PAGE: u32 => 15,
//! }
//!
//! #[cfg(feature = "companies")]
//! COMPANY {
//! #[env_name = "INSTITUTIONS_PER_PAGE"]
//! PER_PAGE: u32 => 15,
//! }
//! }
//!
//! FEATURE {
//! NEW_MENU: bool => false,
//!
//! COMPANY {
//! PROFILE: bool => false,
//! }
//! }
//! }
//!
//! fn main () {
//! // dotenv().ok();
//! env::set_var("FEATURE_NEW_MENU", "t");
//!
//! cfg::init();
//! assert_eq!(cfg::HOST(), String::from("127.0.0.1"));
//! assert_eq!(cfg::DATABASE_URL(), String::from("postgres://user:pass@localhost:5432/test"));
//! assert_eq!(cfg::APP::ARTICLE::PER_PAGE(), 15);
//! assert_eq!(cfg::FEATURE::NEW_MENU(), true);
//! }
//! ```
// Rustc lints.
#![deny(unused_imports)]
/////////////////////////////////////////////////////////////////////////////
mod getenv;
pub mod envstr;
pub use self::getenv::*;
pub mod prelude {
pub use crate::envstr::*;
}
#[cfg(feature = "macro")]
#[allow(unused_imports)]
#[macro_use]
mod r#macro;
#[cfg(feature = "macro")]
#[doc(hidden)]
macro_rules! __impl_from_for_numbers {
($($ty:ty),+) => {
$(
impl From<EnvValue> for $ty {
fn from(env: EnvValue) -> Self {
env.0.parse::<Self>().unwrap()
}
}
)*
}
}
#[derive(Debug)]
#[doc(hidden)]
pub struct EnvValue(String);
__impl_from_for_numbers![
i8, i16, i32, i64, i128, isize,
u8, u16, u32, u64, u128, usize,
f32, f64
];
impl From<EnvValue> for bool {
fn from(env: EnvValue) -> Self {
match env.0.to_lowercase().as_str() {
"true" | "1" | "t" | "on" => true,
_ => false,
}
}
}
impl From<String> for EnvValue {
fn from(val: String) -> Self {
Self(val)
}
}
impl From<EnvValue> for String {
fn from(env: EnvValue) -> Self {
env.0
}
}
/// Creates new public mod with function fo get each environment variable of mapping.
///
/// All variables are required and program will panic if some variables haven't value, but you
/// can add default value for specific variable.
///
/// Starts with v0.6.0 if you don't have an optional variable, the variable is set automatically.
///
/// Example usage
/// -------------
///
/// ```rust
/// # #[macro_use] extern crate itconfig;
/// # use std::env;
/// # env::set_var("DATABASE_URL", "postgres://u:p@localhost:5432/db");
/// config! {
/// DATABASE_URL: String,
/// }
/// # cfg::init()
/// ```
///
/// Config with default value
///
/// ```rust
/// # #[macro_use] extern crate itconfig;
/// # use std::env;
/// # env::set_var("DATABASE_URL", "postgres://u:p@localhost:5432/db");
/// config! {
/// DATABASE_URL: String,
/// HOST: String => "127.0.0.1",
/// }
/// # cfg::init()
/// ```
///
/// By default itconfig lib creates module with 'cfg' name. But you can use simple meta instruction
/// if you want to rename module. In the example below we renamed module to 'configuration'
///
/// ```rust
/// # #[macro_use] extern crate itconfig;
/// # use std::env;
/// env::set_var("DEBUG", "t");
///
/// config! {
/// #![mod_name = configuration]
///
/// DEBUG: bool,
/// }
///
/// configuration::init();
/// assert_eq!(configuration::DEBUG(), true);
/// ```
///
/// Namespaces
/// ----------
///
/// You can use namespaces for env variables
///
/// ```rust
/// # #[macro_use] extern crate itconfig;
/// # use std::env;
/// env::set_var("DEBUG", "t");
/// env::set_var("DATABASE_USERNAME", "user");
/// env::set_var("DATABASE_PASSWORD", "pass");
/// env::set_var("DATABASE_HOST", "localhost");
///
/// config! {
/// DEBUG: bool,
/// DATABASE {
/// USERNAME: String,
/// PASSWORD: String,
/// HOST: String,
/// }
/// }
/// # cfg::init()
/// ```
///
/// Now you can use nested structure in namespaces without limits :)
///
/// ```rust
/// # #[macro_use] extern crate itconfig;
/// config! {
/// FIRST {
/// SECOND {
/// THIRD {
/// FOO: bool => true,
/// }
/// }
/// }
/// }
/// # cfg::init();
/// ```
///
/// Namespaces supports custom meta
///
/// ```rust
/// # #[macro_use] extern crate itconfig;
/// config! {
/// #[cfg(feature = "first")]
/// FIRST {
/// #[cfg(feature = "second")]
/// SECOND {
/// #[cfg(feature = "third")]
/// THIRD {
/// FOO: bool => true,
/// }
/// }
/// }
/// }
/// # cfg::init();
/// ```
///
/// Meta
/// ----
///
/// If you want to read custom env name for variable you can change it manually.
///
/// **A variable in the nameespace will lose environment prefix**
///
/// ```rust
/// # #[macro_use] extern crate itconfig;
/// # use std::env;
/// env::set_var("MY_CUSTOM_NAME", "95");
///
/// config! {
/// #[env_name = "MY_CUSTOM_NAME"]
/// PER_PAGE: i32,
///
/// APP {
/// #[env_name = "MY_CUSTOM_NAME"]
/// RECIPES_PER_PAGE: i32,
/// }
/// }
///
/// cfg::init();
/// assert_eq!(cfg::PER_PAGE(), 95);
/// assert_eq!(cfg::APP::RECIPES_PER_PAGE(), 95);
/// ```
///
/// Also you can add custom meta for each variable. For example feature configurations.
///
/// ```rust
/// # #[macro_use] extern crate itconfig;
/// config! {
/// #[cfg(feature = "postgres")]
/// DATABASE_URL: String,
///
/// #[cfg(not(feature = "postgres"))]
/// DATABASE_URL: String,
/// }
/// # fn main() {}
/// ```
///
/// Concatenate
/// -----------
///
/// Try to concatenate env variable or strings or both to you env variable. It's easy!
///
/// ```rust
/// # #[macro_use] extern crate itconfig;
/// # use std::env;
/// env::set_var("POSTGRES_USERNAME", "user");
/// env::set_var("POSTGRES_PASSWORD", "pass");
///
/// config! {
/// DATABASE_URL < (
/// "postgres://",
/// POSTGRES_USERNAME,
/// ":",
/// POSTGRES_PASSWORD,
/// "@",
/// POSTGRES_HOST => "localhost:5432",
/// "/",
/// POSTGRES_DB => "test",
/// ),
/// }
///
/// cfg::init();
/// assert_eq!(cfg::DATABASE_URL(), "postgres://user:pass@localhost:5432/test".to_string())
/// ```
///
/// Concatinated variables can be only strings and support all features like namespaces and meta.
///
/// ```rust
/// # #[macro_use] extern crate itconfig;
/// config! {
/// CONCATED_NAMESPACE {
/// #[env_name = "DATABASE_URL"]
/// CONCAT_ENVVAR < (
/// "postgres://",
/// NOT_DEFINED_PG_USERNAME => "user",
/// ":",
/// NOT_DEFINED_PG_PASSWORD => "pass",
/// "@",
/// NOT_DEFINED_PG_HOST => "localhost:5432",
/// "/",
/// NOT_DEFINED_PG_DB => "test",
/// ),
/// }
/// }
///
/// cfg::init();
/// ```
///
/// ---
///
/// This module will also contain helper method:
/// --------------------------------------------
///
/// ```rust
/// pub fn init() {}
/// ```
///
/// Run this at the main function for check all required variables without default value.
///
/// Panics
/// ------
///
/// If you miss some required variables your application will panic at startup.
///
/// Examples
/// --------
///
/// ```rust
/// #[macro_use] extern crate itconfig;
/// // use dotenv::dotenv;
///
/// config! {
/// DEBUG: bool => true,
/// HOST: String => "127.0.0.1",
/// }
///
/// fn main () {
/// // dotenv().ok();
/// cfg::init();
/// assert_eq!(cfg::HOST(), String::from("127.0.0.1"));
/// }
/// ```
///
#[macro_export(local_inner_macros)]
macro_rules! config {
($($tokens:tt)*) => {
__itconfig_parse_module! {
tokens = [$($tokens)*],
name = cfg,
}
}
}
#[macro_export]
#[doc(hidden)]
macro_rules! __itconfig_invalid_syntax {
() => {
compile_error!(
"Invalid `config!` syntax. Please see the `config!` macro docs for more info.\
`https://docs.rs/itconfig/latest/itconfig/macro.config.html`"
);
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __itconfig_parse_module {
// Find module name
(
tokens = [
#![mod_name = $mod_name:ident]
$($rest:tt)*
],
name = $ignore:tt,
) => {
__itconfig_parse_module! {
tokens = [$($rest)*],
name = $mod_name,
}
};
// Done parsing module
(
tokens = $tokens:tt,
name = $name:tt,
) => {
__itconfig_parse_variables! {
tokens = $tokens,
variables = [],
namespaces = [],
module = {
env_prefix = "",
name = $name,
meta = [],
},
}
};
// Invalid syntax
($($tokens:tt)*) => {
__itconfig_invalid_syntax!();
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __itconfig_parse_variables {
// Find namespace
(
tokens = [
$(#$meta:tt)*
$ns_name:ident { $($ns_tokens:tt)* }
$($rest:tt)*
],
$($args:tt)*
) => {
__itconfig_parse_variables! {
tokens = [$($ns_tokens)*],
variables = [],
namespaces = [],
module = {
env_prefix = concat!(stringify!($ns_name), "_"),
name = $ns_name,
meta = [$(#$meta)*],
},
callback = {
tokens = [$($rest)*],
$($args)*
},
}
};
// Find concatenated variable
(
tokens = [
$(#$meta:tt)*
$name:ident < ($($inner:tt)+),
$($rest:tt)*
],
$($args:tt)*
) => {
__itconfig_parse_variables! {
current_variable = {
unparsed_meta = [$(#$meta)*],
meta = [],
unparsed_concat = [$($inner)+],
concat = [],
name = $name,
ty = String,
},
tokens = [$($rest)*],
$($args)*
}
};
// Find variable
(
tokens = [
$(#$meta:tt)*
$name:ident : $ty:ty$( => $default:expr)?,
$($rest:tt)*
],
$($args:tt)*
) => {
__itconfig_parse_variables! {
current_variable = {
unparsed_meta = [$(#$meta)*],
meta = [],
unparsed_concat = [],
concat = [],
name = $name,
ty = $ty,
$(default = $default,)?
},
tokens = [$($rest)*],
$($args)*
}
};
// Find meta with custom env name
(
current_variable = {
unparsed_meta = [
#[env_name = $env_name:expr]
$($rest:tt)*
],
meta = $meta:tt,
unparsed_concat = $unparsed_concat:tt,
concat = $concat:tt,
name = $name:ident,
$($current_variable:tt)*
},
$($args:tt)*
) => {
__itconfig_parse_variables! {
current_variable = {
unparsed_meta = [$($rest)*],
meta = $meta,
unparsed_concat = $unparsed_concat,
concat = $concat,
name = $name,
env_name = $env_name,
$($current_variable)*
},
$($args)*
}
};
// Find stranger meta
(
current_variable = {
unparsed_meta = [
#$stranger_meta:tt
$($rest:tt)*
],
meta = [$(#$meta:tt,)*],
$($current_variable:tt)*
},
$($args:tt)*
) => {
__itconfig_parse_variables! {
current_variable = {
unparsed_meta = [$($rest)*],
meta = [$(#$meta,)* #$stranger_meta,],
$($current_variable)*
},
$($args)*
}
};
// Parse concat params
(
current_variable = {
unparsed_meta = $unparsed_meta:tt,
meta = $meta:tt,
unparsed_concat = [
$concat_param:tt$( => $default:expr)?,
$($rest:tt)*
],
concat = [$($concat:expr,)*],
$($current_variable:tt)*
},
$($args:tt)*
) => {
__itconfig_parse_variables! {
current_variable = {
unparsed_meta = $unparsed_meta,
meta = $meta,
unparsed_concat = [$($rest)*],
concat = [$($concat,)* __itconfig_concat_param!($concat_param$( => $default)?),],
$($current_variable)*
},
$($args)*
}
};
// Done parsing variable
(
current_variable = {
unparsed_meta = [],
meta = $meta:tt,
unparsed_concat = [],
$($current_variable:tt)*
},
tokens = $tokens:tt,
variables = [$($variables:tt,)*],
$($args:tt)*
) => {
__itconfig_parse_variables! {
tokens = $tokens,
variables = [$($variables,)* { meta = $meta, $($current_variable)* },],
$($args)*
}
};
// Done parsing all variables of namespace
(
tokens = [],
variables = $ns_variables:tt,
namespaces = $ns_namespaces:tt,
module = $ns_module:tt,
callback = {
tokens = $tokens:tt,
variables = $variables:tt,
namespaces = [$($namespaces:tt,)*],
$($args:tt)*
},
) => {
__itconfig_parse_variables! {
tokens = $tokens,
variables = $variables,
namespaces = [
$($namespaces,)*
{
variables = $ns_variables,
namespaces = $ns_namespaces,
module = $ns_module,
},
],
$($args)*
}
};
// Done parsing all variables
(
tokens = [],
$($args:tt)*
) => {
__itconfig_impl_namespace!($($args)*);
};
// Invalid syntax
($($tokens:tt)*) => {
__itconfig_invalid_syntax!();
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __itconfig_impl_namespace {
(
variables = [$({
meta = $var_meta:tt,
concat = $var_concat:tt,
name = $var_name:ident,
$($variable:tt)*
},)*],
namespaces = [$({
variables = $ns_variable:tt,
namespaces = $ns_namespaces:tt,
module = {
env_prefix = $ns_env_prefix:expr,
name = $ns_mod_name:ident,
meta = [$(#$ns_meta:tt)*],
},
},)*],
module = {
env_prefix = $env_prefix:expr,
name = $mod_name:ident,
meta = [$(#$meta:tt)*],
},
) => {
$(#$meta)*
pub mod $mod_name {
#![allow(non_snake_case)]
use std::env;
use itconfig::EnvValue;
$(__itconfig_impl_namespace! {
variables = $ns_variable,
namespaces = $ns_namespaces,
module = {
env_prefix = $ns_env_prefix,
name = $ns_mod_name,
meta = [$(#$ns_meta)*],
},
})*
pub fn init() {
$($var_name();)*
$(
$(#$ns_meta)*
$ns_mod_name::init();
)*
}
$(__itconfig_variable! {
meta = $var_meta,
concat = $var_concat,
name = $var_name,
env_prefix = $env_prefix,
$($variable)*
})*
}
};
// Invalid syntax
($($tokens:tt)*) => {
__itconfig_invalid_syntax!();
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __itconfig_concat_param {
// Find env parameter with default value
($env_name:ident => $default:expr) => {
__itconfig_variable_helper!(stringify!($env_name).to_uppercase(), $default, default)
};
// Find env parameter without default value
($env_name:ident) => {
env_or!(stringify!($env_name).to_uppercase())
};
// Find string parameter
($str:expr) => ( $str.to_string() );
// Invalid syntax
($($tokens:tt)*) => {
__itconfig_invalid_syntax!();
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __itconfig_variable {
// Set default env name
(
meta = $meta:tt,
concat = $concat:tt,
name = $name:ident,
env_prefix = $env_prefix:expr,
ty = $ty:ty,
$($args:tt)*
) => {
__itconfig_variable! {
meta = $meta,
concat = $concat,
name = $name,
env_prefix = $env_prefix,
env_name = concat!($env_prefix, stringify!($name)).to_uppercase(),
ty = $ty,
$($args)*
}
};
// Add method for concatenated variable
(
meta = [$(#$meta:tt,)*],
concat = [$($concat:expr,)+],
name = $name:ident,
env_prefix = $env_prefix:expr,
env_name = $env_name:expr,
ty = $ty:ty,
$($args:tt)*
) => {
$(#$meta)*
pub fn $name() -> $ty {
let value_parts: Vec<String> = vec!($($concat),+);
let value = value_parts.join("");
__itconfig_variable_helper!(@setenv $env_name, value)
}
};
// Add method for env variable
(
meta = [$(#$meta:tt,)*],
concat = [],
name = $name:ident,
env_prefix = $env_prefix:expr,
env_name = $env_name:expr,
ty = $ty:ty,
$(default = $default:expr,)?
) => {
$(#$meta)*
pub fn $name() -> $ty {
env_or!($env_name$(, $default)?)
}
};
// Invalid syntax
($($tokens:tt)*) => {
__itconfig_invalid_syntax!();
};
}
/// This macro returns environment variable by name and converts variable to desired type
/// or returns default value.
///
/// Panics
/// ------
/// If you don't pass default value, macro will panic
///
/// Examples
/// --------
///
/// ```rust
/// # #[macro_use] extern crate itconfig;
/// let url: String = env_or!("DATABASE_URL", "127.0.0.1".to_string());
/// assert_eq!(url, "127.0.0.1".to_string());
/// ```
#[macro_export(local_inner_macro)]
macro_rules! env_or {
// Env without default value
($env_name:expr) => {
__itconfig_variable_helper!($env_name, format!(r#"Cannot read "{}" environment variable"#, $env_name), panic);
};
// Env with default value
($env_name:expr, $default:expr) => {
__itconfig_variable_helper!($env_name, $default, setenv);
};
// Invalid syntax
($($tokens:tt)*) => {
__itconfig_env_or_invalid_syntax!();
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __itconfig_env_or_invalid_syntax {
() => {
compile_error!(
"Invalid `env_or!` syntax. Please see the `env_or!` macro docs for more info.\
`https://docs.rs/itconfig/latest/itconfig/macro.env_or.html`"
);
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __itconfig_variable_helper {
// Get env variable
($env_name:expr, $default:expr, $token:tt) => {{
use std::env;
use itconfig::EnvValue;
env::var($env_name)
.map(|val| __itconfig_variable_helper!(val))
.unwrap_or_else(|_| __itconfig_variable_helper!(@$token $env_name, $default))
}};
// Returns converted env variable
($(@default $env_name:expr,)? $default:expr) => {{
EnvValue::from($default.to_string()).into()
}};
// Set default value for env variable and returns default
(@setenv $env_name:expr, $default:expr) => {{
env::set_var($env_name, $default.to_string());
__itconfig_variable_helper!($default)
}};
// Make panic for env variable
(@panic $env_name:expr, $default:expr) => {
panic!($default);
};
// Invalid syntax
($($tokens:tt)*) => {
__itconfig_env_or_invalid_syntax!();
};
}
pub use r#macro::*;

706
itconfig/src/macro.rs Normal file
View File

@ -0,0 +1,706 @@
/// Creates new public mod with function fo get each environment variable of mapping.
///
/// All variables are required and program will panic if some variables haven't value, but you
/// can add default value for specific variable.
///
/// Starts with v0.6.0 if you don't have an optional variable, the variable is set automatically.
///
/// Example usage
/// -------------
///
/// ```rust
/// # #[macro_use] extern crate itconfig;
/// # use std::env;
/// # env::set_var("DATABASE_URL", "postgres://u:p@localhost:5432/db");
/// config! {
/// DATABASE_URL: String,
/// }
/// # cfg::init()
/// ```
///
/// Config with default value
///
/// ```rust
/// # #[macro_use] extern crate itconfig;
/// # use std::env;
/// # env::set_var("DATABASE_URL", "postgres://u:p@localhost:5432/db");
/// config! {
/// DATABASE_URL: String,
/// HOST: String => "127.0.0.1",
/// }
/// # cfg::init()
/// ```
///
/// By default itconfig lib creates module with 'cfg' name. But you can use simple meta instruction
/// if you want to rename module. In the example below we renamed module to 'configuration'
///
/// ```rust
/// # #[macro_use] extern crate itconfig;
/// # use std::env;
/// env::set_var("DEBUG", "t");
///
/// config! {
/// #![mod_name = configuration]
///
/// DEBUG: bool,
/// }
///
/// configuration::init();
/// assert_eq!(configuration::DEBUG(), true);
/// ```
///
/// Namespaces
/// ----------
///
/// You can use namespaces for env variables
///
/// ```rust
/// # #[macro_use] extern crate itconfig;
/// # use std::env;
/// env::set_var("DEBUG", "t");
/// env::set_var("DATABASE_USERNAME", "user");
/// env::set_var("DATABASE_PASSWORD", "pass");
/// env::set_var("DATABASE_HOST", "localhost");
///
/// config! {
/// DEBUG: bool,
/// DATABASE {
/// USERNAME: String,
/// PASSWORD: String,
/// HOST: String,
/// }
/// }
/// # cfg::init()
/// ```
///
/// Now you can use nested structure in namespaces without limits :)
///
/// ```rust
/// # #[macro_use] extern crate itconfig;
/// config! {
/// FIRST {
/// SECOND {
/// THIRD {
/// FOO: bool => true,
/// }
/// }
/// }
/// }
/// # cfg::init();
/// ```
///
/// Namespaces supports custom meta
///
/// ```rust
/// # #[macro_use] extern crate itconfig;
/// config! {
/// #[cfg(feature = "first")]
/// FIRST {
/// #[cfg(feature = "second")]
/// SECOND {
/// #[cfg(feature = "third")]
/// THIRD {
/// FOO: bool => true,
/// }
/// }
/// }
/// }
/// # cfg::init();
/// ```
///
/// Meta
/// ----
///
/// If you want to read custom env name for variable you can change it manually.
///
/// **A variable in the nameespace will lose environment prefix**
///
/// ```rust
/// # #[macro_use] extern crate itconfig;
/// # use std::env;
/// env::set_var("MY_CUSTOM_NAME", "95");
///
/// config! {
/// #[env_name = "MY_CUSTOM_NAME"]
/// PER_PAGE: i32,
///
/// APP {
/// #[env_name = "MY_CUSTOM_NAME"]
/// RECIPES_PER_PAGE: i32,
/// }
/// }
///
/// cfg::init();
/// assert_eq!(cfg::PER_PAGE(), 95);
/// assert_eq!(cfg::APP::RECIPES_PER_PAGE(), 95);
/// ```
///
/// Also you can add custom meta for each variable. For example feature configurations.
///
/// ```rust
/// # #[macro_use] extern crate itconfig;
/// config! {
/// #[cfg(feature = "postgres")]
/// DATABASE_URL: String,
///
/// #[cfg(not(feature = "postgres"))]
/// DATABASE_URL: String,
/// }
/// # fn main() {}
/// ```
///
/// Concatenate
/// -----------
///
/// Try to concatenate env variable or strings or both to you env variable. It's easy!
///
/// ```rust
/// # #[macro_use] extern crate itconfig;
/// # use std::env;
/// env::set_var("POSTGRES_USERNAME", "user");
/// env::set_var("POSTGRES_PASSWORD", "pass");
///
/// config! {
/// DATABASE_URL < (
/// "postgres://",
/// POSTGRES_USERNAME,
/// ":",
/// POSTGRES_PASSWORD,
/// "@",
/// POSTGRES_HOST => "localhost:5432",
/// "/",
/// POSTGRES_DB => "test",
/// ),
/// }
///
/// cfg::init();
/// assert_eq!(cfg::DATABASE_URL(), "postgres://user:pass@localhost:5432/test".to_string())
/// ```
///
/// Concatinated variables can be only strings and support all features like namespaces and meta.
///
/// ```rust
/// # #[macro_use] extern crate itconfig;
/// config! {
/// CONCATED_NAMESPACE {
/// #[env_name = "DATABASE_URL"]
/// CONCAT_ENVVAR < (
/// "postgres://",
/// NOT_DEFINED_PG_USERNAME => "user",
/// ":",
/// NOT_DEFINED_PG_PASSWORD => "pass",
/// "@",
/// NOT_DEFINED_PG_HOST => "localhost:5432",
/// "/",
/// NOT_DEFINED_PG_DB => "test",
/// ),
/// }
/// }
///
/// cfg::init();
/// ```
///
/// ---
///
/// This module will also contain helper method:
/// --------------------------------------------
///
/// ```rust
/// pub fn init() {}
/// ```
///
/// Run this at the main function for check all required variables without default value.
///
/// Panics
/// ------
///
/// If you miss some required variables your application will panic at startup.
///
/// Examples
/// --------
///
/// ```rust
/// #[macro_use] extern crate itconfig;
/// // use dotenv::dotenv;
///
/// config! {
/// DEBUG: bool => true,
/// HOST: String => "127.0.0.1",
/// }
///
/// fn main () {
/// // dotenv().ok();
/// cfg::init();
/// assert_eq!(cfg::HOST(), String::from("127.0.0.1"));
/// }
/// ```
///
#[macro_export(local_inner_macros)]
macro_rules! config {
($($tokens:tt)*) => {
__itconfig_parse_module! {
tokens = [$($tokens)*],
name = cfg,
}
}
}
#[macro_export]
#[doc(hidden)]
macro_rules! __itconfig_invalid_syntax {
() => {
compile_error!(
"Invalid `config!` syntax. Please see the `config!` macro docs for more info.\
`https://docs.rs/itconfig/latest/itconfig/macro.config.html`"
);
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __itconfig_parse_module {
// Find module name
(
tokens = [
#![mod_name = $mod_name:ident]
$($rest:tt)*
],
name = $ignore:tt,
) => {
__itconfig_parse_module! {
tokens = [$($rest)*],
name = $mod_name,
}
};
// Done parsing module
(
tokens = $tokens:tt,
name = $name:tt,
) => {
__itconfig_parse_variables! {
tokens = $tokens,
variables = [],
namespaces = [],
module = {
env_prefix = "",
name = $name,
meta = [],
},
}
};
// Invalid syntax
($($tokens:tt)*) => {
__itconfig_invalid_syntax!();
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __itconfig_parse_variables {
// Find namespace
(
tokens = [
$(#$meta:tt)*
$ns_name:ident { $($ns_tokens:tt)* }
$($rest:tt)*
],
$($args:tt)*
) => {
__itconfig_parse_variables! {
tokens = [$($ns_tokens)*],
variables = [],
namespaces = [],
module = {
env_prefix = concat!(stringify!($ns_name), "_"),
name = $ns_name,
meta = [$(#$meta)*],
},
callback = {
tokens = [$($rest)*],
$($args)*
},
}
};
// Find concatenated variable
(
tokens = [
$(#$meta:tt)*
$name:ident < ($($inner:tt)+),
$($rest:tt)*
],
$($args:tt)*
) => {
__itconfig_parse_variables! {
current_variable = {
unparsed_meta = [$(#$meta)*],
meta = [],
unparsed_concat = [$($inner)+],
concat = [],
name = $name,
ty = String,
},
tokens = [$($rest)*],
$($args)*
}
};
// Find variable
(
tokens = [
$(#$meta:tt)*
$name:ident : $ty:ty$( => $default:expr)?,
$($rest:tt)*
],
$($args:tt)*
) => {
__itconfig_parse_variables! {
current_variable = {
unparsed_meta = [$(#$meta)*],
meta = [],
unparsed_concat = [],
concat = [],
name = $name,
ty = $ty,
$(default = $default,)?
},
tokens = [$($rest)*],
$($args)*
}
};
// Find meta with custom env name
(
current_variable = {
unparsed_meta = [
#[env_name = $env_name:expr]
$($rest:tt)*
],
meta = $meta:tt,
unparsed_concat = $unparsed_concat:tt,
concat = $concat:tt,
name = $name:ident,
$($current_variable:tt)*
},
$($args:tt)*
) => {
__itconfig_parse_variables! {
current_variable = {
unparsed_meta = [$($rest)*],
meta = $meta,
unparsed_concat = $unparsed_concat,
concat = $concat,
name = $name,
env_name = $env_name,
$($current_variable)*
},
$($args)*
}
};
// Find stranger meta
(
current_variable = {
unparsed_meta = [
#$stranger_meta:tt
$($rest:tt)*
],
meta = [$(#$meta:tt,)*],
$($current_variable:tt)*
},
$($args:tt)*
) => {
__itconfig_parse_variables! {
current_variable = {
unparsed_meta = [$($rest)*],
meta = [$(#$meta,)* #$stranger_meta,],
$($current_variable)*
},
$($args)*
}
};
// Parse concat params
(
current_variable = {
unparsed_meta = $unparsed_meta:tt,
meta = $meta:tt,
unparsed_concat = [
$concat_param:tt$( => $default:expr)?,
$($rest:tt)*
],
concat = [$($concat:expr,)*],
$($current_variable:tt)*
},
$($args:tt)*
) => {
__itconfig_parse_variables! {
current_variable = {
unparsed_meta = $unparsed_meta,
meta = $meta,
unparsed_concat = [$($rest)*],
concat = [$($concat,)* __itconfig_concat_param!($concat_param$( => $default)?),],
$($current_variable)*
},
$($args)*
}
};
// Done parsing variable
(
current_variable = {
unparsed_meta = [],
meta = $meta:tt,
unparsed_concat = [],
$($current_variable:tt)*
},
tokens = $tokens:tt,
variables = [$($variables:tt,)*],
$($args:tt)*
) => {
__itconfig_parse_variables! {
tokens = $tokens,
variables = [$($variables,)* { meta = $meta, $($current_variable)* },],
$($args)*
}
};
// Done parsing all variables of namespace
(
tokens = [],
variables = $ns_variables:tt,
namespaces = $ns_namespaces:tt,
module = $ns_module:tt,
callback = {
tokens = $tokens:tt,
variables = $variables:tt,
namespaces = [$($namespaces:tt,)*],
$($args:tt)*
},
) => {
__itconfig_parse_variables! {
tokens = $tokens,
variables = $variables,
namespaces = [
$($namespaces,)*
{
variables = $ns_variables,
namespaces = $ns_namespaces,
module = $ns_module,
},
],
$($args)*
}
};
// Done parsing all variables
(
tokens = [],
$($args:tt)*
) => {
__itconfig_impl_namespace!($($args)*);
};
// Invalid syntax
($($tokens:tt)*) => {
__itconfig_invalid_syntax!();
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __itconfig_impl_namespace {
(
variables = [$({
meta = $var_meta:tt,
concat = $var_concat:tt,
name = $var_name:ident,
$($variable:tt)*
},)*],
namespaces = [$({
variables = $ns_variable:tt,
namespaces = $ns_namespaces:tt,
module = {
env_prefix = $ns_env_prefix:expr,
name = $ns_mod_name:ident,
meta = [$(#$ns_meta:tt)*],
},
},)*],
module = {
env_prefix = $env_prefix:expr,
name = $mod_name:ident,
meta = [$(#$meta:tt)*],
},
) => {
$(#$meta)*
pub mod $mod_name {
#![allow(non_snake_case)]
$(__itconfig_impl_namespace! {
variables = $ns_variable,
namespaces = $ns_namespaces,
module = {
env_prefix = $ns_env_prefix,
name = $ns_mod_name,
meta = [$(#$ns_meta)*],
},
})*
pub fn init() {
$($var_name();)*
$(
$(#$ns_meta)*
$ns_mod_name::init();
)*
}
$(__itconfig_variable! {
meta = $var_meta,
concat = $var_concat,
name = $var_name,
env_prefix = $env_prefix,
$($variable)*
})*
}
};
// Invalid syntax
($($tokens:tt)*) => {
__itconfig_invalid_syntax!();
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __itconfig_concat_param {
// Find env parameter with default value
($env_name:ident => $default:expr) => (
itconfig::get_env_or_default(
stringify!($env_name).to_uppercase().as_str(),
$default
)
);
// Find env parameter without default value
($env_name:ident) => (
itconfig::get_env(
stringify!($env_name).to_uppercase().as_str()
)
);
// Find string parameter
($str:expr) => ( $str.to_string() );
// Invalid syntax
($($tokens:tt)*) => {
__itconfig_invalid_syntax!();
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __itconfig_variable {
// Set default env name
(
meta = $meta:tt,
concat = $concat:tt,
name = $name:ident,
env_prefix = $env_prefix:expr,
ty = $ty:ty,
$($args:tt)*
) => {
__itconfig_variable! {
meta = $meta,
concat = $concat,
name = $name,
env_prefix = $env_prefix,
env_name = concat!($env_prefix, stringify!($name)).to_uppercase(),
ty = $ty,
$($args)*
}
};
// Add method for concatenated variable
// (
// meta = [$(#$meta:tt,)*],
// concat = [$($concat:expr,)+],
// name = $name:ident,
// env_prefix = $env_prefix:expr,
// env_name = $env_name:expr,
// ty = $ty:ty,
// $($args:tt)*
// ) => {
// $(#$meta)*
// pub fn $name() -> $ty {
// let value_parts: Vec<String> = vec!($($concat),+);
// let value = value_parts.join("");
// __itconfig_variable_helper!(@setenv $env_name, value)
// }
// };
// Add method for env variable
(
meta = [$(#$meta:tt,)*],
concat = $concat:tt,
name = $name:ident,
env_prefix = $env_prefix:expr,
env_name = $env_name:expr,
ty = $ty:ty,
$(default = $default:expr,)?
) => {
$(#$meta)*
pub fn $name() -> $ty {
__itconfig_variable! {
@inner
concat = $concat,
env_name = $env_name,
$(default = $default,)?
}
}
};
(
@inner
concat = [$($concat:expr,)+],
env_name = $env_name:expr,
$($args:tt)*
) => (
let value_parts: Vec<String> = vec!($($concat),+);
let value = value_parts.join("");
std::env::set_var($env_name, value.as_str());
value
);
(
@inner
concat = [],
env_name = $env_name:expr,
) => (
itconfig::get_env($env_name.to_string().as_str())
);
(
@inner
concat = [],
env_name = $env_name:expr,
default = $default:expr,
) => (
itconfig::get_env_or_set_default(
$env_name.to_string().as_str(),
$default
)
);
// Invalid syntax
($($tokens:tt)*) => {
__itconfig_invalid_syntax!();
};
}

View File

@ -6,7 +6,7 @@ extern crate itconfig;
#[test]
#[should_panic(expected = "Cannot read \"MISS_VARIABLE\" environment variable")]
#[should_panic(expected = "Environment variable \"MISS_VARIABLE\" is missing")]
fn should_panic_if_miss_env_variable() {
config! {
MISS_VARIABLE: bool,
@ -334,7 +334,7 @@ fn setting_default_concat_env_variable() {
#[test]
#[should_panic(expected = "Cannot read \"PG_USERNAME\" environment variable")]
#[should_panic(expected = "Environment variable \"PG_USERNAME\" is missing")]
fn concatenate_not_defined_environment_variables() {
config! {
DATABASE_URL < (

View File

@ -0,0 +1,23 @@
use std::env;
use itconfig::*;
#[test]
#[should_panic(expected = "Environment variable \"TEST_CASE_1\" is missing")]
fn missing_env_variable() {
get_env::<String>("TEST_CASE_1");
}
#[test]
#[should_panic(expected = "Failed to parse environment variable \"TEST_CASE_2\"")]
fn cannot_parse_env_variable() {
env::set_var("TEST_CASE_2", "30r");
get_env::<u32>("TEST_CASE_2");
}
#[test]
fn get_env_successfully() {
env::set_var("TEST_CASE_3", "30");
let a: u32 = get_env("TEST_CASE_3");
assert_eq!(a, 30);
}

View File

@ -0,0 +1,31 @@
use std::env;
use itconfig::*;
#[test]
fn missing_env_variable() {
let flag: bool = get_env_or_default("DEFAULT_TEST_CASE_1", "true");
assert_eq!(flag, true);
// let var: String = env::var("DEFAULT_TEST_CASE_1").unwrap();
// assert_eq!(var, "true");
}
#[test]
#[should_panic(expected = "Failed to parse environment variable \"DEFAULT_TEST_CASE_2\"")]
fn cannot_parse_env_variable() {
env::set_var("DEFAULT_TEST_CASE_2", "30r");
let _: u32 = get_env_or_default("DEFAULT_TEST_CASE_2", 30);
}
#[test]
#[should_panic(expected = "Failed to parse environment variable \"DEFAULT_TEST_CASE_2\"")]
fn cannot_parse_default_value() {
let _: u32 = get_env_or_default("DEFAULT_TEST_CASE_2", "30r");
}
#[test]
fn get_env_successfully() {
let a: u32 = get_env_or_default("DEFAULT_TEST_CASE_3", 30);
assert_eq!(a, 30);
}