diff --git a/README.md b/README.md index f1c1b2b..212a2d5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # itconfig [![Build Status](https://travis-ci.org/icetemple/itconfig-rs.svg?branch=master)](https://travis-ci.org/icetemple/itconfig-rs) [![Documentation](https://docs.rs/itconfig/badge.svg)](https://docs.rs/itconfig) -[![Crates.io](https://img.shields.io/badge/crates.io-v0.9.0-orange.svg?longCache=true)](https://crates.io/crates/itconfig) +[![Crates.io](https://img.shields.io/badge/crates.io-v0.10.0-orange.svg?longCache=true)](https://crates.io/crates/itconfig) [![Join the chat at https://gitter.im/icetemple/itconfig-rs](https://badges.gitter.im/icetemple/itconfig-rs.svg)](https://gitter.im/icetemple/itconfig-rs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Easy build a configs from environment variables and use it in globally. @@ -75,6 +75,24 @@ fn main () { } ``` + +Macro is an optional feature, enabled by default. You can install itconfig without default +features and use this lib as shown below + +```rust +use itconfig::*; +use std::env; +// use dotenv::dotenv; + +fn main() { + env::set_var("DATABASE_URL", "postgres://127.0.0.1:5432/test"); + + let database_url = get_env::("DATABASE_URL").unwrap(); + let new_profile: bool = get_env_or_default("FEATURE_NEW_PROFILE", false); + let articles_per_page: u32 = get_env_or_set_default("ARTICLES_PER_PAGE", 10); +} +``` + ## Running tests ```bash diff --git a/examples/rocket/src/main.rs b/examples/rocket/src/main.rs index b3d089f..eb5b510 100644 --- a/examples/rocket/src/main.rs +++ b/examples/rocket/src/main.rs @@ -5,7 +5,6 @@ extern crate rocket; #[macro_use] extern crate itconfig; -use rocket::config::{Config, Environment}; config! { ROCKET { diff --git a/itconfig/Cargo.toml b/itconfig/Cargo.toml index bb41512..c35e8a3 100644 --- a/itconfig/Cargo.toml +++ b/itconfig/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "itconfig" -version = "0.9.0" +version = "0.10.0" authors = ["Dmitriy Pleshevskiy "] description = "Easy build a configs from environment variables and use it in globally." categories = ["config", "web-programming"] @@ -19,6 +19,7 @@ travis-ci = { repository = "icetemple/itconfig-rs" } maintenance = { status = "actively-developed" } [dependencies] +failure = { version = "0.1.6", features = ["derive"]} [features] default = ['macro'] diff --git a/itconfig/README.md b/itconfig/README.md index 2b5f561..94445fc 100644 --- a/itconfig/README.md +++ b/itconfig/README.md @@ -63,6 +63,24 @@ fn main () { ``` +Macro is an optional feature, enabled by default. You can install itconfig without default +features and use this lib as shown below + +```rust +use itconfig::*; +use std::env; +// use dotenv::dotenv; + +fn main() { + env::set_var("DATABASE_URL", "postgres://127.0.0.1:5432/test"); + + let database_url = get_env::("DATABASE_URL").unwrap(); + let new_profile: bool = get_env_or_default("FEATURE_NEW_PROFILE", false); + let articles_per_page: u32 = get_env_or_set_default("ARTICLES_PER_PAGE", 10); +} +``` + + ## Roadmap * [x] Add namespace for variables diff --git a/itconfig/src/enverr.rs b/itconfig/src/enverr.rs new file mode 100644 index 0000000..a941e18 --- /dev/null +++ b/itconfig/src/enverr.rs @@ -0,0 +1,10 @@ +use failure::Fail; + + +#[derive(Debug, Fail, PartialEq)] +pub enum EnvError { + #[fail(display = r#"Environment variable "{}" is missing"#, env_name)] + MissingVariable { env_name: String }, + #[fail(display = r#"Failed to parse environment variable "{}""#, env_name)] + FailedToParse { env_name: String }, +} diff --git a/itconfig/src/getenv.rs b/itconfig/src/getenv.rs index cb70ea8..ca08e82 100644 --- a/itconfig/src/getenv.rs +++ b/itconfig/src/getenv.rs @@ -1,26 +1,25 @@ use std::env; -use crate::envstr::{EnvString, FromEnvString, ToEnvString}; +use crate::prelude::*; -#[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. +/// This function is similar as `get_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_env_or_panic(env_name: &str) -> T + where + T: FromEnvString +{ + get_env(env_name).unwrap_or_else(make_panic) +} + + +/// Try to read environment variable and parse to expected type. You may to put to argument +/// any type with `FromEnvString` trait. +/// /// Example /// ------- /// @@ -32,21 +31,23 @@ macro_rules! env_panic { /// fn main () { /// env::set_var("DEBUG", "true"); /// -/// let result: bool = get_env("DEBUG"); +/// let result: bool = get_env("DEBUG").unwrap(); /// /// assert_eq!(result, true); /// } /// ``` /// -pub fn get_env(env_name: &str) -> T +pub fn get_env(env_name: &str) -> Result where T: FromEnvString { - get_env_or(env_name, || env_panic!(MissingVariable, env_name)) + get_env_or(env_name, |_| { + Err(EnvError::MissingVariable { env_name: env_name.to_string() }) + }) } -/// This function is similar as `get_env` but more safely. You can pass default value for +/// This function is similar as `get_env_or_panic`, but you can pass default value for /// environment variable with `ToEnvString` trait. /// /// Panics @@ -72,12 +73,13 @@ pub fn get_env_or_default(env_name: &str, default: D) -> T T: FromEnvString, D: ToEnvString, { - get_env_or(env_name, || default.to_env_string()) + get_env_or(env_name, |_| Ok(default.to_env_string())) + .unwrap_or_else(make_panic) } -/// This function is similar as `get_env_or_default` but if env variable is missed, will set -/// default value to environment variable. +/// This function is similar as `get_env_or_default`, but the default value will be set to environment +/// variable, if env variable is missed. /// /// Panics /// ------ @@ -105,37 +107,42 @@ pub fn get_env_or_set_default(env_name: &str, default: D) -> T T: FromEnvString, D: ToEnvString, { - get_env_or(env_name, || { + get_env_or(env_name, |_| { let val = default.to_env_string(); env::set_var(env_name, val.as_str()); - val - }) + Ok(val) + }).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 `panic!` -pub fn get_env_or(env_name: &str, cb: F) -> T +/// default expression. Callback should return `EnvString` value or `EnvError` +pub fn get_env_or(env_name: &str, cb: F) -> Result where T: FromEnvString, - F: FnOnce() -> EnvString + F: FnOnce(env::VarError) -> Result { - let env_str = env::var(env_name) + env::var(env_name) .map(|s| s.to_env_string()) - .unwrap_or_else(|_| cb()); - - parse_env_variable(env_name, env_str) + .or_else(cb) + .and_then(|env_str| { + parse_env_variable(env_name, env_str) + }) } - #[doc(hidden)] -fn parse_env_variable(env_name: &str, env_str: EnvString) -> T +fn parse_env_variable(env_name: &str, env_str: EnvString) -> Result where T: FromEnvString { env_str .parse::() - .unwrap_or_else(|_| env_panic!(FailedToParse, env_name)) + .map_err(|_| EnvError::FailedToParse { env_name: env_name.to_string() }) } + +#[doc(hidden)] +fn make_panic(e: EnvError) -> T { + panic!("{}", e) +} diff --git a/itconfig/src/lib.rs b/itconfig/src/lib.rs index 3d30ca2..2b98cac 100644 --- a/itconfig/src/lib.rs +++ b/itconfig/src/lib.rs @@ -1,15 +1,109 @@ +//! # 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); +//! } +//! ``` +//! +//! Macro is an optional feature, enabled by default. You can install itconfig without default +//! features and use this lib as shown below +//! +//! ```rust +//! use itconfig::*; +//! use std::env; +//! // use dotenv::dotenv; +//! +//! fn main() { +//! env::set_var("DATABASE_URL", "postgres://127.0.0.1:5432/test"); +//! +//! let database_url = get_env::("DATABASE_URL").unwrap(); +//! let new_profile: bool = get_env_or_default("FEATURE_NEW_PROFILE", false); +//! let articles_per_page: u32 = get_env_or_set_default("ARTICLES_PER_PAGE", 10); +//! } +//! ``` +//! + + // Rustc lints. #![deny(unused_imports)] ///////////////////////////////////////////////////////////////////////////// +#[macro_use] +extern crate failure; + +mod enverr; mod getenv; pub mod envstr; pub use self::getenv::*; +pub use self::enverr::*; pub mod prelude { pub use crate::envstr::*; + pub use crate::enverr::*; } diff --git a/itconfig/src/macro.rs b/itconfig/src/macro.rs index ef426cd..87fa5e0 100644 --- a/itconfig/src/macro.rs +++ b/itconfig/src/macro.rs @@ -1,3 +1,6 @@ + +/// ### _This API requires the following crate features to be activated: `macro`_ +/// /// 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 @@ -589,9 +592,7 @@ macro_rules! __itconfig_concat_param { // Find env parameter without default value ($env_name:ident) => ( - itconfig::get_env( - stringify!($env_name).to_uppercase().as_str() - ) + itconfig::get_env_or_panic(stringify!($env_name).to_uppercase().as_str()) ); // Find string parameter @@ -627,24 +628,6 @@ macro_rules! __itconfig_variable { } }; - // 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 = vec!($($concat),+); -// let value = value_parts.join(""); -// __itconfig_variable_helper!(@setenv $env_name, value) -// } -// }; - // Add method for env variable ( meta = [$(#$meta:tt,)*], @@ -666,6 +649,7 @@ macro_rules! __itconfig_variable { } }; + // Concatenate function body ( @inner concat = [$($concat:expr,)+], @@ -678,14 +662,16 @@ macro_rules! __itconfig_variable { value ); + // Env without default ( @inner concat = [], env_name = $env_name:expr, ) => ( - itconfig::get_env($env_name.to_string().as_str()) + itconfig::get_env_or_panic($env_name.to_string().as_str()) ); + // Env with default ( @inner concat = [], diff --git a/itconfig_tests/tests/get_env.rs b/itconfig_tests/tests/get_env.rs index 4dc4f5b..f8baaa8 100644 --- a/itconfig_tests/tests/get_env.rs +++ b/itconfig_tests/tests/get_env.rs @@ -4,20 +4,20 @@ use itconfig::*; #[test] #[should_panic(expected = "Environment variable \"TEST_CASE_1\" is missing")] fn missing_env_variable() { - get_env::("TEST_CASE_1"); + get_env_or_panic::("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::("TEST_CASE_2"); + get_env_or_panic::("TEST_CASE_2"); } #[test] fn get_env_successfully() { env::set_var("TEST_CASE_3", "30"); - let a: u32 = get_env("TEST_CASE_3"); + let a = get_env::("TEST_CASE_3").unwrap(); assert_eq!(a, 30); } diff --git a/itconfig_tests/tests/get_env_or_default.rs b/itconfig_tests/tests/get_env_or_default.rs index 53e6af9..70a86cf 100644 --- a/itconfig_tests/tests/get_env_or_default.rs +++ b/itconfig_tests/tests/get_env_or_default.rs @@ -5,9 +5,6 @@ use itconfig::*; 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]