feat: add result to getenv functions
BREAKING_CHANGES: get env function returns result instead panic
This commit is contained in:
parent
ee988af061
commit
e1cbcb7696
10 changed files with 195 additions and 65 deletions
20
README.md
20
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::<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
|
||||
|
|
|
@ -5,7 +5,6 @@ extern crate rocket;
|
|||
#[macro_use]
|
||||
extern crate itconfig;
|
||||
|
||||
use rocket::config::{Config, Environment};
|
||||
|
||||
config! {
|
||||
ROCKET {
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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
10
itconfig/src/enverr.rs
Normal 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 },
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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::*;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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 = [],
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
Reference in a new issue