feat: add result to getenv functions

BREAKING_CHANGES: get env function returns result instead panic
This commit is contained in:
Dmitriy Pleshevskiy 2020-01-19 17:33:46 +03:00
parent ee988af061
commit e1cbcb7696
10 changed files with 195 additions and 65 deletions

View file

@ -1,7 +1,7 @@
# itconfig # itconfig
[![Build Status](https://travis-ci.org/icetemple/itconfig-rs.svg?branch=master)](https://travis-ci.org/icetemple/itconfig-rs) [![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) [![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) [![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. 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::<String>("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 ## Running tests
```bash ```bash

View file

@ -5,7 +5,6 @@ extern crate rocket;
#[macro_use] #[macro_use]
extern crate itconfig; extern crate itconfig;
use rocket::config::{Config, Environment};
config! { config! {
ROCKET { ROCKET {

View file

@ -1,6 +1,6 @@
[package] [package]
name = "itconfig" name = "itconfig"
version = "0.9.0" version = "0.10.0"
authors = ["Dmitriy Pleshevskiy <dmitriy@ideascup.me>"] authors = ["Dmitriy Pleshevskiy <dmitriy@ideascup.me>"]
description = "Easy build a configs from environment variables and use it in globally." description = "Easy build a configs from environment variables and use it in globally."
categories = ["config", "web-programming"] categories = ["config", "web-programming"]
@ -19,6 +19,7 @@ travis-ci = { repository = "icetemple/itconfig-rs" }
maintenance = { status = "actively-developed" } maintenance = { status = "actively-developed" }
[dependencies] [dependencies]
failure = { version = "0.1.6", features = ["derive"]}
[features] [features]
default = ['macro'] default = ['macro']

View file

@ -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::<String>("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 ## Roadmap
* [x] Add namespace for variables * [x] Add namespace for variables

10
itconfig/src/enverr.rs Normal file
View file

@ -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 },
}

View file

@ -1,26 +1,25 @@
use std::env; use std::env;
use crate::envstr::{EnvString, FromEnvString, ToEnvString}; use crate::prelude::*;
#[doc(hidden)] /// This function is similar as `get_env`, but it unwraps result with panic on error.
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 /// Panics
/// ------ /// ------
/// Application will panic if environment variable is missing or cannot parse variable to /// Application will panic if environment variable is missing or cannot parse variable to
/// expected type /// expected type
/// ///
pub fn get_env_or_panic<T>(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 /// Example
/// ------- /// -------
/// ///
@ -32,21 +31,23 @@ macro_rules! env_panic {
/// fn main () { /// fn main () {
/// env::set_var("DEBUG", "true"); /// env::set_var("DEBUG", "true");
/// ///
/// let result: bool = get_env("DEBUG"); /// let result: bool = get_env("DEBUG").unwrap();
/// ///
/// assert_eq!(result, true); /// assert_eq!(result, true);
/// } /// }
/// ``` /// ```
/// ///
pub fn get_env<T>(env_name: &str) -> T pub fn get_env<T>(env_name: &str) -> Result<T, EnvError>
where where
T: FromEnvString 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. /// environment variable with `ToEnvString` trait.
/// ///
/// Panics /// Panics
@ -72,12 +73,13 @@ pub fn get_env_or_default<T, D>(env_name: &str, default: D) -> T
T: FromEnvString, T: FromEnvString,
D: ToEnvString, 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 /// This function is similar as `get_env_or_default`, but the default value will be set to environment
/// default value to environment variable. /// variable, if env variable is missed.
/// ///
/// Panics /// Panics
/// ------ /// ------
@ -105,37 +107,42 @@ pub fn get_env_or_set_default<T, D>(env_name: &str, default: D) -> T
T: FromEnvString, T: FromEnvString,
D: ToEnvString, D: ToEnvString,
{ {
get_env_or(env_name, || { get_env_or(env_name, |_| {
let val = default.to_env_string(); let val = default.to_env_string();
env::set_var(env_name, val.as_str()); 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 /// This function returns env variable as `EnvString` structure. You can pass callback for custom
/// default expression. Callback should return `EnvString` value or `panic!` /// default expression. Callback should return `EnvString` value or `EnvError`
pub fn get_env_or<T, F>(env_name: &str, cb: F) -> T pub fn get_env_or<T, F>(env_name: &str, cb: F) -> Result<T, EnvError>
where where
T: FromEnvString, T: FromEnvString,
F: FnOnce() -> EnvString F: FnOnce(env::VarError) -> Result<EnvString, EnvError>
{ {
let env_str = env::var(env_name) env::var(env_name)
.map(|s| s.to_env_string()) .map(|s| s.to_env_string())
.unwrap_or_else(|_| cb()); .or_else(cb)
.and_then(|env_str| {
parse_env_variable(env_name, env_str) parse_env_variable(env_name, env_str)
})
} }
#[doc(hidden)] #[doc(hidden)]
fn parse_env_variable<T>(env_name: &str, env_str: EnvString) -> T fn parse_env_variable<T>(env_name: &str, env_str: EnvString) -> Result<T, EnvError>
where where
T: FromEnvString T: FromEnvString
{ {
env_str env_str
.parse::<T>() .parse::<T>()
.unwrap_or_else(|_| env_panic!(FailedToParse, env_name)) .map_err(|_| EnvError::FailedToParse { env_name: env_name.to_string() })
} }
#[doc(hidden)]
fn make_panic<T>(e: EnvError) -> T {
panic!("{}", e)
}

View file

@ -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::<String>("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. // Rustc lints.
#![deny(unused_imports)] #![deny(unused_imports)]
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
#[macro_use]
extern crate failure;
mod enverr;
mod getenv; mod getenv;
pub mod envstr; pub mod envstr;
pub use self::getenv::*; pub use self::getenv::*;
pub use self::enverr::*;
pub mod prelude { pub mod prelude {
pub use crate::envstr::*; pub use crate::envstr::*;
pub use crate::enverr::*;
} }

View file

@ -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. /// 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 /// 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 // Find env parameter without default value
($env_name:ident) => ( ($env_name:ident) => (
itconfig::get_env( itconfig::get_env_or_panic(stringify!($env_name).to_uppercase().as_str())
stringify!($env_name).to_uppercase().as_str()
)
); );
// Find string parameter // 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<String> = vec!($($concat),+);
// let value = value_parts.join("");
// __itconfig_variable_helper!(@setenv $env_name, value)
// }
// };
// Add method for env variable // Add method for env variable
( (
meta = [$(#$meta:tt,)*], meta = [$(#$meta:tt,)*],
@ -666,6 +649,7 @@ macro_rules! __itconfig_variable {
} }
}; };
// Concatenate function body
( (
@inner @inner
concat = [$($concat:expr,)+], concat = [$($concat:expr,)+],
@ -678,14 +662,16 @@ macro_rules! __itconfig_variable {
value value
); );
// Env without default
( (
@inner @inner
concat = [], concat = [],
env_name = $env_name:expr, 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 @inner
concat = [], concat = [],

View file

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

View file

@ -5,9 +5,6 @@ use itconfig::*;
fn missing_env_variable() { fn missing_env_variable() {
let flag: bool = get_env_or_default("DEFAULT_TEST_CASE_1", "true"); let flag: bool = get_env_or_default("DEFAULT_TEST_CASE_1", "true");
assert_eq!(flag, true); assert_eq!(flag, true);
// let var: String = env::var("DEFAULT_TEST_CASE_1").unwrap();
// assert_eq!(var, "true");
} }
#[test] #[test]