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
[![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::<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
```bash

View file

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

View file

@ -1,6 +1,6 @@
[package]
name = "itconfig"
version = "0.9.0"
version = "0.10.0"
authors = ["Dmitriy Pleshevskiy <dmitriy@ideascup.me>"]
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']

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
* [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 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<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
/// -------
///
@ -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<T>(env_name: &str) -> T
pub fn get_env<T>(env_name: &str) -> Result<T, EnvError>
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<T, D>(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<T, D>(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<T, F>(env_name: &str, cb: F) -> T
/// default expression. Callback should return `EnvString` value or `EnvError`
pub fn get_env_or<T, F>(env_name: &str, cb: F) -> Result<T, EnvError>
where
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())
.unwrap_or_else(|_| cb());
.or_else(cb)
.and_then(|env_str| {
parse_env_variable(env_name, env_str)
})
}
#[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
T: FromEnvString
{
env_str
.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.
#![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::*;
}

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.
///
/// 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<String> = 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 = [],

View file

@ -4,20 +4,20 @@ use itconfig::*;
#[test]
#[should_panic(expected = "Environment variable \"TEST_CASE_1\" is missing")]
fn missing_env_variable() {
get_env::<String>("TEST_CASE_1");
get_env_or_panic::<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");
get_env_or_panic::<u32>("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::<u32>("TEST_CASE_3").unwrap();
assert_eq!(a, 30);
}

View file

@ -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]