Compare commits

...

5 Commits

22 changed files with 432 additions and 2906 deletions

600
Cargo.lock generated
View File

@ -2,605 +2,15 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bstr"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
dependencies = [
"lazy_static",
"memchr",
"regex-automata",
"serde",
]
[[package]]
name = "bumpalo"
version = "3.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3"
[[package]]
name = "cast"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [
"bitflags",
"textwrap",
"unicode-width",
]
[[package]]
name = "criterion"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f"
dependencies = [
"atty",
"cast",
"clap",
"criterion-plot",
"csv",
"itertools",
"lazy_static",
"num-traits",
"oorandom",
"plotters",
"rayon",
"regex",
"serde",
"serde_cbor",
"serde_derive",
"serde_json",
"tinytemplate",
"walkdir",
]
[[package]]
name = "criterion-plot"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876"
dependencies = [
"cast",
"itertools",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
"once_cell",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
name = "csv"
version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1"
dependencies = [
"bstr",
"csv-core",
"itoa 0.4.8",
"ryu",
"serde",
]
[[package]]
name = "csv-core"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
dependencies = [
"memchr",
]
[[package]]
name = "either"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be"
[[package]]
name = "enve"
version = "1.1.1"
version = "0.1.0"
dependencies = [
"criterion",
"enve_mod",
"lazy_static",
"estring",
]
[[package]]
name = "enve_mod"
version = "1.1.1"
dependencies = [
"enve",
"lazy_static",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "half"
version = "1.8.2"
name = "estring"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "itertools"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "itoa"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
[[package]]
name = "js-sys"
version = "0.3.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "once_cell"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
[[package]]
name = "oorandom"
version = "11.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]]
name = "plotters"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9428003b84df1496fb9d6eeee9c5f8145cb41ca375eb0dad204328888832811f"
dependencies = [
"num-traits",
"plotters-backend",
"plotters-svg",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "plotters-backend"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142"
[[package]]
name = "plotters-svg"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0918736323d1baff32ee0eade54984f6f201ad7e97d5cfb5d6ab4a358529615"
dependencies = [
"plotters-backend",
]
[[package]]
name = "proc-macro2"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rayon"
version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d"
dependencies = [
"autocfg",
"crossbeam-deque",
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils",
"num_cpus",
]
[[package]]
name = "regex"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
dependencies = [
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
[[package]]
name = "regex-syntax"
version = "0.6.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
[[package]]
name = "ryu"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03"
[[package]]
name = "serde_cbor"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5"
dependencies = [
"half",
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f2122636b9fe3b81f1cb25099fcf2d3f542cdb1d45940d56c713158884a05da"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7"
dependencies = [
"itoa 1.0.2",
"ryu",
"serde",
]
[[package]]
name = "syn"
version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "tinytemplate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "unicode-ident"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7"
[[package]]
name = "unicode-width"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "walkdir"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
dependencies = [
"same-file",
"winapi",
"winapi-util",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a"
dependencies = [
"bumpalo",
"lazy_static",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be"
[[package]]
name = "web-sys"
version = "0.3.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
checksum = "72ea7e904ef7cb950eee02f9332b6a5c24c33bebeca6c027651b04f6c20658d9"

View File

@ -1,46 +1,35 @@
[workspace]
members = [
"enve_mod",
]
[package]
name = "enve"
version = "1.1.1"
version = "0.1.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", "configuration", "environment", "macro"]
description = "it helps you work with environment variables and convert it to any type using only type annotations"
categories = ["config"]
keywords = ["env", "environment"]
edition = "2018"
license = "MIT"
repository = "https://github.com/pleshevskiy/enve"
homepage = "https://github.com/pleshevskiy/enve"
documentation = "https://docs.rs/enve"
readme = "../README.md"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = []
number = []
bool = []
vec = []
macro = ["enve_mod"]
[dependencies]
enve_mod = { version = "1.1", path = "./enve_mod", optional = true }
[dev-dependencies]
lazy_static = "1.4.0"
criterion = "0.3.1"
[badges]
maintenance = { status = "actively-developed" }
[package.metadata]
msrv = "1.51.0"
# https://docs.rs/about
[package.metadata.docs.rs]
all-features = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = []
number = ["estring/number"]
bool = ["estring/bool"]
vec = ["estring/vec"]
[dependencies]
estring = "0.1"
[badges]
maintenance = { status = "actively-developed" }
[[example]]
name = "calc"
required-features = ["number", "vec"]

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2019 pleshevskiy
Copyright (c) 2019-2022 pleshevskiy
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

139
README.md
View File

@ -6,24 +6,50 @@
[![Crates.io](https://img.shields.io/crates/v/enve)](https://crates.io/crates/enve)
![Crates.io](https://img.shields.io/crates/l/enve)
Easy build a configs from environment variables and use it in globally.
`enve` helps you work with environment variables and convert it to **any type**
using only **type annotations**.
We recommend you start with the [documentation].
Look at the [examples](https://github.com/pleshevskiy/enve/tree/main/examples)
to see the power!
## Motivation
All standard environment variable types are included, but `enve` under the hood
uses [estring](https://github.com/pleshevskiy/estring), so you can easily create
your own type.
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.
## Getting started
```rust
use enve::SepVec;
type MinusVec<T> = SepVec<T, '-'>;
type PlusVec<T> = SepVec<T, '+'>;
type MulVec<T> = SepVec<T, '*'>;
fn main() -> Result<(), enve::Error> {
enve::sset("E", "10+5*2-3");
let res: f32 = enve::get::<PlusVec<MinusVec<MulVec<f32>>>>("E")
.unwrap()
.iter()
.map(|p| {
p.iter()
.map(|m| m.iter().product::<f32>())
.reduce(|acc, v| acc - v)
.unwrap_or_default()
})
.sum::<f32>();
println!("result: {}", res);
Ok(())
}
```
## Installation
The MSRV is 1.39.0
Add `enve = { version = "1.0", features = ["mod"] }` as a dependency in
Add `enve = { version = "0.1", features = ["prim", "vec"] }` as a dependency in
`Cargo.toml`.
`Cargo.toml` example:
@ -35,102 +61,15 @@ version = "0.1.0"
authors = ["Me <user@rust-lang.org>"]
[dependencies]
enve = { version = "1.0", features = ["mod"] }
enve = { version = "0.1", features = ["prim", "vec"] }
```
## Basic usage
```rust
use std::env;
//use dotenv::dotenv;
enve::mod! {
DEBUG: bool => false,
#[env_name = "APP_HOST"]
HOST: String => "127.0.0.1",
database {
URL < (
"postgres://",
POSTGRES_USERNAME => "user",
":",
POSTGRES_PASSWORD => "pass",
"@",
POSTGRES_HOST => "localhost:5432",
"/",
POSTGRES_DB => "test",
),
pool {
MAX_SIZE: usize => 15,
},
},
sentry {
DSN: Option<&'static str>,
},
feature {
static CORS: bool => false,
static GRAPHQL_PLAYGROUND: bool => false,
},
}
fn main () {
// dotenv().expect("dotenv setup to be successful");
// or
env::set_var("FEATURE_CORS", "true");
config::init();
assert_eq!(config::HOST(), String::from("127.0.0.1"));
assert_eq!(config::database::URL(), String::from("postgres://user:pass@localhost:5432/test"));
assert_eq!(config::database::pool::MAX_SIZE(), 15);
assert_eq!(config::sentry::DSN(), None);
assert_eq!(config::feature::CORS(), true);
}
```
Macro is an optional feature, disabled by default. You can use this library
without macro
```rust
use std::env;
// use dotenv::dotenv;
fn main() {
// dotenv().expect("dotenv setup to be successful");
// or
env::set_var("DATABASE_URL", "postgres://127.0.0.1:5432/test");
let database_url = enve::get::<String>("DATABASE_URL").unwrap();
let new_profile: bool = enve::get("FEATURE_NEW_PROFILE").unwrap_or_default();
let articles_per_page: u32 = enve::get_or_set_default("ARTICLES_PER_PAGE", 10);
}
```
## Running tests
```bash
cargo test --all-features
```
## Available features
- **macro** - Activates `config!` macros for easy configure web application.
- **number** - Group for features: `int`, `uint` and `float`.
- **bool** - impl EnvString for `bool` type `serde_json` package). ⚠
**_DEPRECATED_**
## License
[MIT] © [pleshevskiy](https://github.com/pleshevskiy)
**MIT**. See [LICENSE](https://github.com/pleshevskiy/estring/LICENSE) to see
the full text.
## Contributors
[pleshevskiy](https://github.com/pleshevskiy) (Dmitriy Pleshevskiy) creator,
maintainer.
[documentation]: https://docs.rs/enve
[MIT]: https://github.com/icetemple/enve-rs/blob/master/LICENSE

View File

@ -1,84 +0,0 @@
use criterion::{criterion_group, criterion_main, Criterion, Fun};
use std::env;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate enve;
fn setup_env_var(key: &'static str, initial: String) {
env::set_var(key, initial);
}
fn source_get_env() -> u32 {
enve::get::<u32>("TEST").unwrap()
}
fn lazy_get_env() -> u32 {
lazy_static! {
static ref RES: u32 = source_get_env();
};
return *RES;
}
fn source_vs_lazy(c: &mut Criterion) {
setup_env_var("TEST", "1".to_string());
let source = Fun::new("source", |b, _| {
b.iter(move || assert_eq!(source_get_env(), 1))
});
let lazy = Fun::new("lazy", |b, _| {
b.iter(move || {
assert_eq!(lazy_get_env(), 1);
})
});
c.bench_functions("get_env", vec![source, lazy], 0);
}
fn source_macro_vs_lazy_macro(c: &mut Criterion) {
config! {
TEST: &'static str,
TEST_WITH_DEFAULT: &'static str => "default",
static LAZY_TEST: &'static str,
static LAZY_TEST_WITH_DEFAULT: &'static str => "default",
}
setup_env_var("TEST", "test".to_string());
setup_env_var("LAZY_TEST", "test".to_string());
let source = Fun::new("source", |b, _| {
b.iter(move || {
assert_eq!(config::TEST(), "test");
})
});
let lazy = Fun::new("lazy", |b, _| {
b.iter(move || {
assert_eq!(config::LAZY_TEST(), "test");
})
});
let source_with_default = Fun::new("source_with_default", |b, _| {
b.iter(move || {
assert_eq!(config::TEST_WITH_DEFAULT(), "default");
})
});
let lazy_with_default = Fun::new("lazy_with_default", |b, _| {
b.iter(move || {
assert_eq!(config::LAZY_TEST_WITH_DEFAULT(), "default");
})
});
let funcs = vec![source, lazy, source_with_default, lazy_with_default];
c.bench_functions("macro", funcs, 0);
}
criterion_group! {
benches,
source_vs_lazy,
source_macro_vs_lazy_macro,
}
criterion_main!(benches);

View File

@ -1,29 +0,0 @@
[package]
name = "enve_mod"
version = "1.1.1"
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", "configuration", "environment", "macro"]
edition = "2018"
license = "MIT"
repository = "https://github.com/pleshevskiy/enve"
homepage = "https://github.com/pleshevskiy/enve"
documentation = "https://docs.rs/enve"
readme = "../README.md"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
proc-macro = true
[dependencies]
syn = "1.0.60"
quote = "1.0.9"
proc-macro2 = "1.0.24"
[dev-dependencies]
enve = { path = ".." }
lazy_static = "1.4.0"
[badges]
maintenance = { status = "actively-developed" }

View File

@ -1,29 +0,0 @@
use crate::utils::SupportedBox;
use proc_macro2::TokenStream as TokenStream2;
use syn::{Attribute, Expr, Ident, Type};
pub(crate) struct RootNamespace {
pub name: Option<Ident>,
pub variables: Vec<Variable>,
pub namespaces: Vec<Namespace>,
pub meta: Vec<Attribute>,
}
pub(crate) struct Namespace {
pub name: Ident,
pub variables: Vec<Variable>,
pub namespaces: Vec<Namespace>,
pub env_prefix: Option<String>,
pub meta: Vec<Attribute>,
}
pub(crate) struct Variable {
pub is_static: bool,
pub name: Ident,
pub ty: Type,
pub initial: Option<Expr>,
pub concat_parts: Option<Vec<TokenStream2>>,
pub env_name: Option<String>,
pub meta: Vec<Attribute>,
pub supported_box: Option<SupportedBox>,
}

View File

@ -1,186 +0,0 @@
use crate::ast::*;
use crate::utils::{vec_to_token_stream_2, SupportedBox};
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens, TokenStreamExt};
impl ToTokens for RootNamespace {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let name = &self.name;
let variables = vec_to_token_stream_2(&self.variables);
let namespaces = vec_to_token_stream_2(&self.namespaces);
let init_variables = self
.variables
.iter()
.map(|var| {
let name = &var.name;
let var_meta = vec_to_token_stream_2(&var.meta);
quote!(
#(#var_meta)*
#name();
)
})
.collect::<Vec<TokenStream2>>();
let init_namespaces = self
.namespaces
.iter()
.map(|ns| {
let name = &ns.name;
let ns_meta = vec_to_token_stream_2(&ns.meta);
quote!(
#(#ns_meta)*
#name::init();
)
})
.collect::<Vec<TokenStream2>>();
let inner_meta: Vec<TokenStream2> = if name.is_none() {
vec![]
} else if self.meta.is_empty() {
vec![quote!(#![allow(non_snake_case)])]
} else {
vec_to_token_stream_2(&self.meta)
};
let inner_rules = quote! {
#(#inner_meta)*
#(#namespaces)*
#(#variables)*
pub fn init() {
#(#init_variables)*
#(#init_namespaces)*
}
};
tokens.append_all(match self.name.as_ref() {
None => inner_rules,
Some(name) => quote! {
pub mod #name {
#inner_rules
}
},
});
}
}
impl ToTokens for Namespace {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let name = &self.name;
let variables = vec_to_token_stream_2(&self.variables);
let namespaces = vec_to_token_stream_2(&self.namespaces);
let meta = vec_to_token_stream_2(&self.meta);
let init_variables = self
.variables
.iter()
.map(|var| {
let name = &var.name;
let var_meta = vec_to_token_stream_2(&var.meta);
quote!(
#(#var_meta)*
#name();
)
})
.collect::<Vec<TokenStream2>>();
let init_namespaces = self
.namespaces
.iter()
.map(|ns| {
let name = &ns.name;
let ns_meta = vec_to_token_stream_2(&ns.meta);
quote!(
#(#ns_meta)*
#name::init();
)
})
.collect::<Vec<TokenStream2>>();
tokens.append_all(quote!(
#(#meta)*
pub mod #name {
#(#namespaces)*
#(#variables)*
pub fn init() {
#(#init_variables)*
#(#init_namespaces)*
}
}
))
}
}
impl ToTokens for Variable {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let ty = &self.ty;
let name = &self.name;
let env_name = &self
.env_name
.clone()
.unwrap_or_else(|| name.to_string().to_uppercase());
let meta = vec_to_token_stream_2(&self.meta);
let get_variable: TokenStream2 = if self.concat_parts.is_some() {
let concat_parts = self.concat_parts.as_ref().unwrap();
quote! {{
let value_parts: Vec<String> = vec!(#(#concat_parts),*);
let value = value_parts.join("");
::std::env::set_var(#env_name, value.as_str());
value
}}
} else if let Some(initial) = &self.initial {
match self.supported_box.clone() {
Some(SupportedBox::Vec(params)) => {
let sep = &params.sep();
quote!(::itconfig::get_vec_env_or_set_default(#env_name, #sep, #initial))
}
_ => quote!(::itconfig::get_env_or_set_default(#env_name, #initial)),
}
} else {
match self.supported_box.clone() {
Some(SupportedBox::Option) => {
quote!(::itconfig::maybe_get_env(#env_name))
}
Some(SupportedBox::OptionVec(params)) => {
let sep = &params.sep();
quote!(::itconfig::maybe_get_vec_env(#env_name, #sep))
}
Some(SupportedBox::Vec(params)) => {
let sep = &params.sep();
quote!(::itconfig::get_vec_env_or_panic(#env_name, #sep))
}
None => {
quote!(::itconfig::get_env_or_panic(#env_name))
}
}
};
if self.is_static {
tokens.append_all(quote!(
#(#meta)*
pub fn #name() -> #ty {
::lazy_static::lazy_static! {
static ref #name: #ty = #get_variable;
}
(*#name).clone()
}
));
} else {
tokens.append_all(quote!(
#(#meta)*
pub fn #name() -> #ty {
#get_variable
}
));
}
}
}

View File

@ -1,342 +0,0 @@
#![recursion_limit = "256"]
#![deny(clippy::all)]
#![forbid(unsafe_code)]
#![forbid(non_ascii_idents)]
mod ast;
mod expand;
mod parse;
mod utils;
extern crate proc_macro;
extern crate proc_macro2;
use self::proc_macro::TokenStream;
use ast::RootNamespace;
use quote::ToTokens;
use syn::parse_macro_input;
/// ### _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
/// 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
/// # use itconfig::config;
/// # use std::env;
/// config! {
/// DATABASE_URL: String,
/// }
///
/// # fn main() {
/// # env::set_var("DATABASE_URL", "postgres://u:p@localhost:5432/db");
/// # config::init();
/// # }
/// ```
///
/// Config with default value
///
/// ```rust
/// # use itconfig::config;
/// # use std::env;
/// config! {
/// DATABASE_URL: String,
/// HOST: String => "127.0.0.1",
/// }
/// # fn main() {
/// # env::set_var("DATABASE_URL", "postgres://u:p@localhost:5432/db");
/// # config::init();
/// # }
/// ```
///
/// By default itconfig lib creates module with 'config' name. But you can use simple meta instruction
/// if you want to rename module. In the example below we renamed module to 'configuration'
///
/// ```rust
/// # use itconfig::config;
/// # use std::env;
/// config! {
/// #![config(name = "configuration")]
///
/// DEBUG: bool,
/// }
///
/// fn main() {
/// env::set_var("DEBUG", "t");
///
/// configuration::init();
/// assert_eq!(configuration::DEBUG(), true);
/// }
/// ```
///
/// You also unwrap first config module
///
/// ```rust
/// # use itconfig::config;
/// # use std::env;
///
/// config! {
/// #![config(unwrap)]
///
/// DEBUG: bool,
/// }
///
/// fn main() {
/// env::set_var("DEBUG", "t");
///
/// init();
/// assert_eq!(DEBUG(), true);
/// }
/// ```
///
///
/// Namespaces
/// ----------
///
/// You can use namespaces for env variables
///
/// ```rust
/// # use itconfig::config;
/// # use std::env;
///
/// config! {
/// DEBUG: bool,
/// DATABASE {
/// USERNAME: String,
/// PASSWORD: String,
/// HOST: String,
/// }
/// }
/// fn main() {
/// env::set_var("DEBUG", "t");
/// env::set_var("DATABASE_USERNAME", "user");
/// env::set_var("DATABASE_PASSWORD", "pass");
/// env::set_var("DATABASE_HOST", "localhost");
///
/// config::init();
/// }
/// ```
///
/// Now you can use nested structure in namespaces without limits :)
///
/// ```rust
/// # use itconfig::config;
/// config! {
/// FIRST {
/// SECOND {
/// THIRD {
/// FOO: bool => true,
/// }
/// }
/// }
/// }
/// # fn main() { config::init () }
/// ```
///
/// Namespaces supports custom meta
///
/// ```rust
/// # use itconfig::config;
/// config! {
/// #[cfg(feature = "first")]
/// FIRST {
/// #[cfg(feature = "second")]
/// SECOND {
/// #[cfg(feature = "third")]
/// THIRD {
/// FOO: bool => true,
/// }
/// }
/// }
/// }
/// # fn main() { config::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
/// # use itconfig::config;
/// # use std::env;
///
/// config! {
/// #[env_name = "MY_CUSTOM_NAME"]
/// PER_PAGE: i32,
///
/// APP {
/// #[env_name = "MY_CUSTOM_NAME"]
/// RECIPES_PER_PAGE: i32,
/// }
/// }
///
/// fn main() {
/// env::set_var("MY_CUSTOM_NAME", "95");
///
/// config::init();
/// assert_eq!(config::PER_PAGE(), 95);
/// assert_eq!(config::APP::RECIPES_PER_PAGE(), 95);
/// }
/// ```
///
/// Also you can add custom meta for each variable. For example feature configurations.
///
/// ```rust
/// # use itconfig::config;
/// 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
/// # use itconfig::config;
/// # use std::env;
/// config! {
/// DATABASE_URL < (
/// "postgres://",
/// POSTGRES_USERNAME,
/// ":",
/// POSTGRES_PASSWORD,
/// "@",
/// POSTGRES_HOST => "localhost:5432",
/// "/",
/// POSTGRES_DB => "test",
/// ),
/// }
///
/// fn main() {
/// env::set_var("POSTGRES_USERNAME", "user");
/// env::set_var("POSTGRES_PASSWORD", "pass");
///
/// config::init();
/// assert_eq!(config::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
/// # use itconfig::config;
/// 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",
/// ),
/// }
/// }
/// # fn main() { config::init () }
/// ```
///
/// Static
/// ------
///
/// Starting with 0.11 version you can use lazy static for improve speed of variable. This is very
/// useful, if you use variable more than once.
///
/// ```rust
/// # use itconfig::config;
/// # use std::env;
/// config! {
/// static APP_BASE_URL => "/api",
/// }
///
/// fn main () {
/// env::set_var("APP_BASE_URL", "/api/v1");
///
/// config::init();
/// assert_eq!(config::APP_BASE_URL(), "/api/v1");
/// }
/// ```
///
/// You also can use static with concat variables
///
/// ```rust
/// # use itconfig::config;
/// config! {
/// static CONNECTION_STRING < (
/// "postgres://",
/// NOT_DEFINED_PG_USERNAME => "user",
/// ":",
/// NOT_DEFINED_PG_PASSWORD => "pass",
/// "@",
/// NOT_DEFINED_PG_HOST => "localhost:5432",
/// "/",
/// NOT_DEFINED_PG_DB => "test",
/// ),
/// }
///
/// fn main () {
/// config::init();
/// assert_eq!(config::CONNECTION_STRING(), "postgres://user:pass@localhost:5432/test".to_string());
/// }
/// ```
///
///
/// ---
///
/// 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
/// # use itconfig::config;
/// // use dotenv::dotenv;
///
/// config! {
/// DEBUG: bool => true,
/// HOST: String => "127.0.0.1",
/// }
///
/// fn main () {
/// // dotenv().ok();
/// config::init();
/// assert_eq!(config::HOST(), String::from("127.0.0.1"));
/// }
/// ```
///
#[proc_macro]
pub fn config(input: TokenStream) -> TokenStream {
let namespace = parse_macro_input!(input as RootNamespace);
namespace.into_token_stream().into()
}

View File

@ -1,284 +0,0 @@
use crate::ast::*;
use crate::utils::{maybe_supported_box, SupportedBox, VecBoxParams};
use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
use quote::quote;
use syn::ext::IdentExt;
use syn::parse::{Parse, ParseBuffer, ParseStream, Result};
use syn::token::{Brace, Colon, Comma, FatArrow, Lt};
use syn::{
braced, parenthesized, parse_str, Attribute, Error, Expr, Lit, Meta, MetaList, MetaNameValue,
NestedMeta, Token, Type,
};
fn fill_env_prefix(prefix: String) -> Box<dyn Fn(Namespace) -> Namespace> {
Box::new(move |mut ns| {
let env_prefix = match &ns.env_prefix {
None => {
let env_prefix = format!("{}{}_", prefix, ns.name.clone());
ns.env_prefix = Some(env_prefix.clone());
env_prefix
}
Some(env_prefix) => env_prefix.clone(),
};
if !ns.namespaces.is_empty() {
ns.namespaces = ns
.namespaces
.into_iter()
.map(fill_env_prefix(ns.env_prefix.clone().unwrap()))
.collect()
}
if !ns.variables.is_empty() {
ns.variables = ns
.variables
.into_iter()
.map(|mut var| {
if var.env_name.is_none() {
var.env_name = Some(
format!("{}{}", env_prefix.clone(), &var.name.to_string())
.to_uppercase(),
);
}
var
})
.collect()
}
ns
})
}
fn parse_namespace_content(
input: &ParseBuffer,
variables: &mut Vec<Variable>,
namespaces: &mut Vec<Namespace>,
) -> Result<()> {
let attributes: Vec<Attribute> = input.call(Attribute::parse_outer)?;
if input.peek2(Brace) {
let mut namespace: Namespace = input.parse()?;
for attr in attributes {
if attr.path.is_ident("env_prefix") {
let env_prefix = parse_attribute(attr, "env_prefix", &namespace.env_prefix)?;
namespace.env_prefix = Some(env_prefix);
} else {
namespace.meta.push(attr);
}
}
namespaces.push(namespace);
} else {
let mut variable: Variable = input.parse()?;
for attr in attributes {
if attr.path.is_ident("env_name") {
let env_name = parse_attribute(attr, "env_name", &variable.env_name)?;
variable.env_name = Some(env_name);
} else {
match variable.supported_box {
Some(SupportedBox::Vec(params)) if attr.path.is_ident("sep") => {
let sep = parse_attribute(attr, "sep", &params.sep_opt())?;
variable.supported_box =
Some(SupportedBox::Vec(VecBoxParams::new(Some(sep))));
}
_ => variable.meta.push(attr),
}
}
}
variables.push(variable);
}
Ok(())
}
fn parse_attribute(attr: Attribute, name: &'static str, var: &Option<String>) -> Result<String> {
if var.is_some() {
let message = format!("You cannot use {} meta twice", &name);
return Err(Error::new_spanned(attr, message));
}
match attr.parse_meta()? {
Meta::NameValue(MetaNameValue {
lit: Lit::Str(lit_str),
..
}) => Ok(lit_str.value()),
_ => {
let message = format!("expected #[{} = \"...\"]", &name);
Err(Error::new_spanned(attr, message))
}
}
}
impl Parse for RootNamespace {
fn parse(input: ParseStream) -> Result<Self> {
let mut name: Option<Ident> = None;
let mut with_module = true;
let mut meta: Vec<Attribute> = vec![];
let attributes: Vec<Attribute> = input.call(Attribute::parse_inner)?;
for attr in attributes {
if attr.path.is_ident("config") {
match attr.parse_meta()? {
Meta::List(MetaList { nested, .. }) => {
let message =
"expected #[config(name = \"...\")] or #[config(unwrap)]".to_string();
match nested.first().unwrap() {
NestedMeta::Meta(Meta::NameValue(MetaNameValue {
path,
lit: Lit::Str(lit_str),
..
})) => {
if path.is_ident("name") {
name = Some(Ident::new(&lit_str.value(), Span::call_site()));
} else {
return Err(Error::new_spanned(attr, message));
}
}
NestedMeta::Meta(Meta::Path(path)) => {
if path.is_ident("unwrap") {
name = None;
with_module = false;
} else {
return Err(Error::new_spanned(attr, message));
}
}
_ => {
return Err(Error::new_spanned(attr, message));
}
}
}
_ => {
let message = "expected #[config(...)]".to_string();
return Err(Error::new_spanned(attr, message));
}
}
} else {
meta.push(attr);
}
}
if with_module && name.is_none() {
name = Some(Ident::new("config", Span::call_site()));
}
let mut variables: Vec<Variable> = vec![];
let mut namespaces: Vec<Namespace> = vec![];
while !input.is_empty() {
parse_namespace_content(input, &mut variables, &mut namespaces)?;
}
let prefix = String::new();
let namespaces = namespaces
.into_iter()
.map(fill_env_prefix(prefix))
.collect();
Ok(RootNamespace {
name,
variables,
namespaces,
meta,
})
}
}
impl Parse for Namespace {
fn parse(input: ParseStream) -> Result<Self> {
let name: Ident = input.parse()?;
let mut variables: Vec<Variable> = vec![];
let mut namespaces: Vec<Namespace> = vec![];
let content;
braced!(content in input);
while !content.is_empty() {
parse_namespace_content(&content, &mut variables, &mut namespaces)?;
}
input.parse::<Comma>().ok();
Ok(Namespace {
name,
variables,
namespaces,
env_prefix: None,
meta: vec![],
})
}
}
impl Parse for Variable {
fn parse(input: ParseStream) -> Result<Self> {
let is_static = input.parse::<Token![static]>().ok().is_some();
let name: Ident = input.parse()?;
let is_concat = input.peek(Lt);
let mut concat_parts = None;
let mut initial = None;
let ty: Type = if is_concat {
parse_str("String")?
} else if input.peek(Colon) {
input.parse::<Colon>()?;
input.parse()?
} else {
parse_str("&'static str")?
};
let supported_box = maybe_supported_box(&ty);
if is_concat {
input.parse::<Lt>()?;
let content;
parenthesized!(content in input);
let mut tmp_vec: Vec<TokenStream2> = vec![];
while !content.is_empty() {
if content.peek(Ident::peek_any) {
let concat_var: Variable = content.parse()?;
let name = &concat_var.name;
let env_name = &concat_var
.env_name
.clone()
.unwrap_or_else(|| name.to_string());
let get_variable = if concat_var.initial.is_some() {
let initial = concat_var.initial.as_ref().unwrap();
quote!(::itconfig::get_env_or_set_default(#env_name, #initial))
} else {
quote!(::itconfig::get_env_or_panic(#env_name))
};
tmp_vec.push(get_variable);
} else {
let part: Lit = content.parse()?;
tmp_vec.push(quote!(#part.to_string()));
}
content.parse::<Comma>().ok();
}
concat_parts = Some(tmp_vec);
} else {
initial = input
.parse::<FatArrow>()
.ok()
.and_then(|_| input.parse::<Expr>().ok());
};
input.parse::<Comma>().ok();
Ok(Variable {
supported_box,
is_static,
name,
ty,
initial,
concat_parts,
env_name: None,
meta: vec![],
})
}
}

View File

@ -1,88 +0,0 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::ToTokens;
use syn::{GenericArgument, Path, PathArguments, Type};
const OPTION_PATH_IDENTS: &[&str] = &["Option|", "std|option|Option|", "core|option|Option|"];
const VEC_PATH_IDENTS: &[&str] = &["Vec|", "std|vec|Vec|"];
#[derive(Debug, Clone, Default)]
pub(crate) struct VecBoxParams(Option<String>);
impl VecBoxParams {
#[inline]
pub(crate) fn new(sep: Option<String>) -> Self {
VecBoxParams(sep)
}
#[inline]
pub(crate) fn sep_opt(&self) -> Option<String> {
self.0.clone()
}
#[inline]
pub(crate) fn sep(&self) -> String {
self.0.clone().unwrap_or_else(|| String::from(","))
}
}
#[derive(Debug, Clone)]
pub(crate) enum SupportedBox {
Vec(VecBoxParams),
Option,
OptionVec(VecBoxParams),
}
pub(crate) fn vec_to_token_stream_2<T>(input: &[T]) -> Vec<TokenStream2>
where
T: ToTokens,
{
input.iter().map(|ns| ns.into_token_stream()).collect()
}
fn path_ident(path: &Path) -> String {
path.segments
.iter()
.into_iter()
.fold(String::with_capacity(250), |mut acc, v| {
acc.push_str(&v.ident.to_string());
acc.push('|');
acc
})
}
fn is_option_path_ident(path_ident: &str) -> bool {
OPTION_PATH_IDENTS.iter().any(|s| path_ident == *s)
}
fn is_vec_path_ident(path_ident: &str) -> bool {
VEC_PATH_IDENTS.iter().any(|s| path_ident == *s)
}
pub(crate) fn maybe_supported_box(ty: &Type) -> Option<SupportedBox> {
match ty {
Type::Path(ty_path) if ty_path.qself.is_none() => {
let ty_path_ident = path_ident(&ty_path.path);
if is_option_path_ident(&ty_path_ident) {
if let PathArguments::AngleBracketed(params) =
&ty_path.path.segments.iter().last().unwrap().arguments
{
if let Some(GenericArgument::Type(Type::Path(inner_ty_path))) =
params.args.first()
{
let ty_path_ident = path_ident(&inner_ty_path.path);
if is_vec_path_ident(&ty_path_ident) {
return Some(SupportedBox::OptionVec(Default::default()));
}
}
}
Some(SupportedBox::Option)
} else if is_vec_path_ident(&ty_path_ident) {
Some(SupportedBox::Vec(Default::default()))
} else {
None
}
}
_ => None,
}
}

View File

@ -8,7 +8,7 @@ Fun calculator based on environment variables
E=2*2-1-1+5*3-10 cargo run --example calc --all-features
```
Limits:
Limits (yet):
- Supports `*`, `+`, `-`
- You cannot start from a negative number. `E=-10`. Solution: start from `0`.

View File

@ -1,4 +1,4 @@
use enve::core::SepVec;
use enve::SepVec;
type MinusVec<T> = SepVec<T, '-'>;
type PlusVec<T> = SepVec<T, '+'>;

View File

@ -1,65 +1,320 @@
#[cfg(any(feature = "number", feature = "bool"))]
pub mod prim;
#[cfg(any(feature = "number", feature = "bool"))]
pub use prim::*;
#[cfg(feature = "vec")]
pub mod vec;
#[cfg(feature = "vec")]
pub use vec::*;
use crate::error::Error;
use std::convert::{Infallible, TryFrom};
use estring::EString;
use std::convert::TryFrom;
/// Wrapper under String type.
/// Fetches the environment variable `key` from the current process. It set value `default`
/// if environment variable `key` ins'n set. Then this function tries to parse ``EString`` to
/// expected type by annotations.
///
/// When we read the environment variable, we automatically convert the value
/// to EnvString and then convert it to your expected type.
/// # Errors
///
#[derive(Debug, Default, PartialEq, Eq, Clone)]
pub struct EString(String);
impl EString {
#[inline]
pub fn parse<T: TryFrom<EString>>(self) -> Result<T, Error> {
let orig = self.0.clone();
<T as TryFrom<EString>>::try_from(self).map_err(|_| Error::Parse(orig))
}
}
impl<T> From<T> for EString
/// This function will return an error if ``EString`` cannot parse substring.
///
/// This function may return an error if the environment variable's name contains
/// the equal sign character (`=`) or the NUL character.
///
/// This function will return an error if the environment variable's value is
/// not valid Unicode. If this is not desired, consider using [`var_os`].
///
/// # Examples
///
/// ```
/// let key = "doc_get_or_set";
/// match enve::get_or_set_default::<i32>(key, 10) {
/// Ok(res) => assert_eq!(res, 10),
/// Err(e) => println!("couldn't interpret {key}: {e}"),
/// }
/// ```
#[allow(clippy::needless_pass_by_value)]
pub fn get_or_set_default<R>(env_name: &str, default: R) -> Result<R, Error>
where
T: std::fmt::Display,
R: TryFrom<EString> + std::fmt::Display,
{
#[inline]
fn from(val: T) -> Self {
Self(val.to_string())
}
get::<R>(env_name).or_else(|err| match err {
Error::NotPresent => sset(env_name, &default).parse().map_err(Error::from),
_ => Err(err),
})
}
impl std::ops::Deref for EString {
type Target = String;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
/// Fetches the environment variable `key` from the current process and then tries to parse
/// ``EString`` to expected type by annotations.
///
/// # Errors
///
/// This function will return an error if ``EString`` cannot parse substring.
///
/// This function will return an error if the environment variable isn't set.
///
/// This function may return an error if the environment variable's name contains
/// the equal sign character (`=`) or the NUL character.
///
/// This function will return an error if the environment variable's value is
/// not valid Unicode. If this is not desired, consider using [`var_os`].
///
/// # Examples
///
/// ```
/// let key = "doc_get";
/// enve::sset(key, "10");
/// match enve::get::<i32>(key) {
/// Ok(res) => assert_eq!(res, 10),
/// Err(e) => println!("couldn't interpret {key}: {e}"),
/// }
/// ```
pub fn get<R>(key: &str) -> Result<R, Error>
where
R: TryFrom<EString>,
{
sget(key).and_then(|v| v.parse().map_err(Error::from))
}
impl TryFrom<EString> for String {
type Error = Infallible;
#[inline]
fn try_from(s: EString) -> Result<Self, Self::Error> {
Ok(s.0)
}
/// Fetches the environment variable `key` from the current process and returns value as
/// ``EString``.
///
/// # Errors
///
/// This function will return an error if the environment variable isn't set.
///
/// This function may return an error if the environment variable's name contains
/// the equal sign character (`=`) or the NUL character.
///
/// This function will return an error if the environment variable's value is
/// not valid Unicode. If this is not desired, consider using [`var_os`].
///
/// # Examples
///
/// ```
/// let key = "HOME";
/// match enve::sget(key) {
/// Ok(val) => println!("{key}: {val:?}"),
/// Err(e) => println!("couldn't interpret {key}: {e}"),
/// }
/// ```
pub fn sget(key: &str) -> Result<EString, Error> {
std::env::var(key).map_err(Error::from).map(EString::from)
}
impl TryFrom<EString> for &'static str {
type Error = Infallible;
/// Sets the environment variable `key` to the value `value` for the currently running
/// process and then returns `value` as a ``EString``.
///
/// # Panics
///
/// This function may panic if `key` is empty, contains an ASCII equals sign `'='`
/// or the NUL character `'\0'`, or when `value` contains the NUL character.
///
/// # Examples
///
/// ```
/// let estr = enve::sset("KEY", "10");
/// assert_eq!(estr.to_string(), String::from("10"));
/// ```
pub fn sset<V>(key: &str, value: V) -> EString
where
V: std::fmt::Display,
{
let val = value.to_string();
std::env::set_var(key, &val);
val.into()
}
#[inline]
fn try_from(s: EString) -> Result<Self, Self::Error> {
Ok(Box::leak(s.0.into_boxed_str()))
#[cfg(test)]
mod tests {
use super::*;
struct TestCase<const N: u8>;
impl<const N: u8> std::fmt::Display for TestCase<N> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "test_case_{}", N)
}
}
#[test]
fn should_add_env_variable_to_process() {
let en = TestCase::<0>.to_string();
sset(&en, "hello");
match std::env::var(&en) {
Ok(var) => assert_eq!(&var, "hello"),
_ => unreachable!(),
}
}
#[test]
fn should_return_variable() {
let en = TestCase::<1>.to_string();
std::env::set_var(&en, "hello");
match get::<&str>(&en) {
Ok(res) => assert_eq!(res, "hello"),
_ => unreachable!(),
};
}
#[test]
fn should_throw_no_present_error() {
let en = TestCase::<2>.to_string();
match get::<&str>(&en) {
Err(Error::NotPresent) => {}
_ => unreachable!(),
};
}
#[test]
fn should_set_default_if_var_is_no_present() {
let en = TestCase::<3>.to_string();
let orig = "hello";
match get_or_set_default(&en, orig) {
Ok(res) => {
assert_eq!(res, orig);
assert_eq!(std::env::var(&en).unwrap(), orig);
}
_ => unreachable!(),
};
}
#[cfg(feature = "number")]
mod numbers {
use super::*;
#[test]
fn should_return_parsed_num() {
let en = TestCase::<4>.to_string();
std::env::set_var(&en, "-10");
match get::<i32>(&en) {
Ok(res) => assert_eq!(res, -10),
_ => unreachable!(),
};
}
#[test]
fn should_throw_parse_error() {
let en = TestCase::<5>.to_string();
std::env::set_var(&en, "-10");
match get::<u32>(&en) {
Err(Error::Parse(orig)) => {
assert_eq!(orig, String::from("-10"));
}
_ => unreachable!(),
};
}
#[test]
fn should_set_default_num_if_var_is_no_present() {
let en = TestCase::<6>.to_string();
let orig = 10;
match get_or_set_default(&en, orig) {
Ok(res) => {
assert_eq!(res, orig);
assert_eq!(std::env::var(&en).unwrap(), "10");
}
_ => unreachable!(),
};
}
}
#[cfg(feature = "bool")]
mod boolean {
use super::*;
#[test]
fn should_parse_bool_variable() {
let en = TestCase::<7>.to_string();
let test_cases = [
("1", true),
("y", true),
("yes", true),
("true", true),
("t", true),
("on", true),
("false", false),
("f", false),
("0", false),
];
for (val, expected) in test_cases {
let mut en = en.clone();
en.push_str(val.as_ref());
std::env::set_var(&en, val);
match get::<bool>(&en) {
Ok(res) => assert_eq!(res, expected),
_ => unreachable!(),
};
}
}
}
#[cfg(feature = "vec")]
mod vector {
use super::*;
use crate::estr::{CommaVec, SemiVec, SepVec};
#[test]
fn should_return_var_as_vector() {
let en = TestCase::<8>.to_string();
std::env::set_var(&en, "1,2,3,4,5");
match get::<CommaVec<i32>>(&en) {
Ok(res) => assert_eq!(*res, vec![1, 2, 3, 4, 5]),
_ => unreachable!(),
};
}
#[test]
fn should_trim_identations_before_parsing() {
let en = TestCase::<9>.to_string();
let input = "
1 , 2, 3,
4,5";
std::env::set_var(&en, input);
match get::<CommaVec<i32>>(&en) {
Ok(res) => assert_eq!(*res, vec![1, 2, 3, 4, 5]),
_ => unreachable!(),
};
}
#[test]
fn should_return_vector_of_vectors() {
let en = TestCase::<10>.to_string();
std::env::set_var(&en, "1,2; 3,4,5; 6,7");
match get::<SemiVec<CommaVec<i32>>>(&en) {
Ok(res) => assert_eq!(
res,
SemiVec::from(vec![
CommaVec::from(vec![1, 2]),
CommaVec::from(vec![3, 4, 5]),
CommaVec::from(vec![6, 7])
])
),
_ => unreachable!(),
};
}
#[test]
fn should_throw_parse_vec_error() {
let en = TestCase::<11>.to_string();
std::env::set_var(&en, "1,2,3,4,5");
match get::<SepVec<i32, '+'>>(&en) {
Err(Error::Parse(orig)) => {
assert_eq!(orig, String::from("1,2,3,4,5"));
}
_ => unreachable!(),
};
}
#[test]
fn should_set_default_vector_if_var_is_no_present() {
let en = TestCase::<12>.to_string();
let orig = CommaVec::from(vec![1, 2, 3, 4]);
match get_or_set_default(&en, orig.clone()) {
Ok(res) => {
assert_eq!(res, orig);
assert_eq!(std::env::var(&en).unwrap(), "1,2,3,4");
}
_ => unreachable!(),
};
}
}
}

View File

@ -1,39 +0,0 @@
use crate::core::EString;
use std::convert::{Infallible, TryFrom};
#[doc(hidden)]
macro_rules! from_env_string_numbers_impl {
($($ty:ty),+$(,)?) => {
$(
#[cfg(feature = "number")]
impl TryFrom<EString> for $ty {
type Error = <$ty as std::str::FromStr>::Err;
#[inline]
fn try_from(s: EString) -> Result<Self, Self::Error> {
s.0.parse::<Self>()
}
}
)+
};
}
#[rustfmt::skip]
from_env_string_numbers_impl![
i8, i16, i32, i64, i128, isize,
u8, u16, u32, u64, u128, usize,
f32, f64
];
#[cfg(feature = "bool")]
impl TryFrom<EString> for bool {
type Error = Infallible;
#[inline]
fn try_from(s: EString) -> Result<Self, Self::Error> {
Ok(matches!(
s.to_lowercase().as_str(),
"true" | "t" | "yes" | "y" | "on" | "1"
))
}
}

View File

@ -1,60 +0,0 @@
use crate::core::EString;
use std::convert::TryFrom;
use std::fmt::Write;
pub const COMMA: char = ',';
pub const SEMI: char = ';';
pub type CommaVec<T> = SepVec<T, COMMA>;
pub type SemiVec<T> = SepVec<T, SEMI>;
#[derive(Debug, PartialEq, Clone)]
pub struct SepVec<T, const SEP: char>(pub Vec<T>);
impl<T, const SEP: char> std::ops::Deref for SepVec<T, SEP> {
type Target = Vec<T>;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T, const SEP: char> From<Vec<T>> for SepVec<T, SEP> {
#[inline]
fn from(vec: Vec<T>) -> Self {
Self(vec)
}
}
impl<T, const SEP: char> std::fmt::Display for SepVec<T, SEP>
where
T: std::fmt::Display,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.iter().enumerate().try_for_each(|(i, part)| {
if i != 0 {
f.write_char(SEP)?;
}
f.write_str(&part.to_string())
})
}
}
impl<T, const SEP: char> TryFrom<EString> for SepVec<T, SEP>
where
T: TryFrom<EString> + std::fmt::Display,
{
type Error = T::Error;
fn try_from(value: EString) -> Result<Self, Self::Error> {
let inner = value
.split(SEP)
.map(|p| p.trim())
.map(EString::from)
.map(T::try_from)
.collect::<Result<Vec<_>, _>>()?;
Ok(Self(inner))
}
}

View File

@ -4,7 +4,7 @@ use std::ffi::OsString;
use std::fmt;
/// The error type for operations interacting with environment variables
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub enum Error {
/// The specified environment variable was not present in the current process's environment.
NotPresent,
@ -20,15 +20,14 @@ pub enum Error {
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use Error::*;
match &self {
NotPresent => f.write_str("The specified env variable was not present"),
Invalid(inner) => write!(
Error::NotPresent => f.write_str("The specified env variable was not present"),
Error::Invalid(inner) => write!(
f,
"The specified env variable was found, but it did not valid: '{:?}'",
inner,
),
Parse(env_name) => {
Error::Parse(env_name) => {
write!(f, r#"Failed to parse environment variable "{}""#, env_name)
}
}
@ -45,3 +44,9 @@ impl From<VarError> for Error {
}
}
}
impl From<estring::ParseError> for Error {
fn from(err: estring::ParseError) -> Self {
Error::Parse(err.clone())
}
}

6
src/estr.rs Normal file
View File

@ -0,0 +1,6 @@
#[cfg(feature = "vec")]
mod vec;
#[cfg(feature = "vec")]
pub use vec::{CommaVec, SemiVec};
pub use estring::core::*;

10
src/estr/vec.rs Normal file
View File

@ -0,0 +1,10 @@
use estring::SepVec;
const COMMA: char = ',';
const SEMI: char = ';';
/// Splits substring by comma character and returns ``SepVec``
pub type CommaVec<T> = SepVec<T, COMMA>;
/// Splits substring by semicolon character and returns ``SepVec``
pub type SemiVec<T> = SepVec<T, SEMI>;

View File

@ -1,162 +1,64 @@
//! # enve
//!
//! Simple configuration with macro for rust application.
//! `enve` helps you work with environment variables and convert it to **any type**
//! using only **type annotations**.
//!
//! Look at the [examples](https://github.com/pleshevskiy/enve/tree/main/examples)
//! to see the power!
//!
//! ## Motivation
//! All standard environment variable types are included, but `enve` under the hood
//! uses [estring](https://github.com/pleshevskiy/estring), so you can easily create
//! your own type.
//!
//! 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.
//!
//!
//! ## Installation
//!
//! These macros require a Rust compiler version 1.31 or newer.
//!
//! Add `enve = { version = "1.0", features = ["macro"] }` as a dependency in `Cargo.toml`.
//!
//!
//! `Cargo.toml` example:
//!
//! ```toml
//! [package]
//! name = "my-crate"
//! version = "0.1.0"
//! authors = ["Me <user@rust-lang.org>"]
//!
//! [dependencies]
//! enve = { version = "1.0", features = ["macro"] }
//! ```
//!
//!
//! ## Basic usage
//! ## Getting started
//!
//! ```rust
//! use enve::config;
//! use std::env;
//! //use dotenv::dotenv;
//! use enve::SepVec;
//!
//! config! {
//! DEBUG: bool => false,
//! type MinusVec<T> = SepVec<T, '-'>;
//! type PlusVec<T> = SepVec<T, '+'>;
//! type MulVec<T> = SepVec<T, '*'>;
//!
//! #[env_name = "APP_HOST"]
//! HOST: String => "127.0.0.1",
//! fn main() -> Result<(), enve::Error> {
//! enve::sset("E", "10+5*2-3");
//!
//! database {
//! URL < (
//! "postgres://",
//! POSTGRES_USERNAME => "user",
//! ":",
//! POSTGRES_PASSWORD => "pass",
//! "@",
//! POSTGRES_HOST => "localhost:5432",
//! "/",
//! POSTGRES_DB => "test",
//! ),
//! let res: f32 = enve::get::<PlusVec<MinusVec<MulVec<f32>>>>("E")
//! .unwrap()
//! .iter()
//! .map(|p| {
//! p.iter()
//! .map(|m| m.iter().product::<f32>())
//! .reduce(|acc, v| acc - v)
//! .unwrap_or_default()
//! })
//! .sum::<f32>();
//!
//! pool {
//! MAX_SIZE: usize => 15,
//! },
//! },
//! println!("result: {}", res);
//!
//! sentry {
//! DSN: Option<&'static str>,
//! },
//!
//! feature {
//! static CORS: bool => false,
//!
//! static GRAPHQL_PLAYGROUND: bool => false,
//! },
//! }
//!
//! fn main () {
//! // dotenv().expect("dotenv setup to be successful");
//! // or
//! env::set_var("FEATURE_CORS", "true");
//!
//! config::init();
//! assert_eq!(config::HOST(), String::from("127.0.0.1"));
//! assert_eq!(config::database::URL(), String::from("postgres://user:pass@localhost:5432/test"));
//! assert_eq!(config::database::pool::MAX_SIZE(), 15);
//! assert_eq!(config::sentry::DSN(), None);
//! assert_eq!(config::feature::CORS(), true);
//! Ok(())
//! }
//! ```
//!
//! Macro is an optional feature, disabled by default. You can use this library without macro.
//!
//! ```rust
//! use enve::*;
//! use std::env;
//! // use dotenv::dotenv;
//!
//! fn main() {
//! // dotenv().expect("dotenv setup to be successful");
//! // or
//! 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);
//! }
//! ```
//!
//! ## Available features
//!
//! * **default** - ["primitives"]
//! * **macro** - Activates `config!` macros for easy configure web application.
//! * **primitives** - Group for features: `numbers` and `bool`.
//! * **numbers** - Group for features: `int`, `uint` and `float`.
//! * **int** - Group for features: `i8`, `i16`, `i32`, `i64`, `i128` and `isize`.
//! * **uint** - Group for features: `u8`, `u16`, `u32`, `u64`, `u128` and `usize`.
//! * **float** - Group for features: `f32` and `f64`
//! * **i8** - impl EnvString for `i8` type
//! * **i16** - impl EnvString for `i16` type
//! * **i32** - impl EnvString for `i32` type
//! * **i64** - impl EnvString for `i64` type
//! * **i128** - impl EnvString for `i128` type
//! * **isize** - impl EnvString for `isize` type
//! * **u8** - impl EnvString for `u8` type
//! * **u16** - impl EnvString for `u16` type
//! * **u32** - impl EnvString for `u32` type
//! * **u64** - impl EnvString for `u64` type
//! * **u128** - impl EnvString for `u128` type
//! * **usize** - impl EnvString for `usize` type
//! * **f32** - impl EnvString for `f32` type
//! * **f64** - impl EnvString for `f64` type
//! * **bool** - impl EnvString for `bool` type
//! * **json_array** - Add EnvString impl for vector type (uses optional `serde_json` package). ⚠ **_DEPRECATED_**
//!
// Rustc lints.
#![forbid(unsafe_code)]
#![forbid(non_ascii_idents)]
#![deny(
missing_debug_implementations,
// missing_docs,
missing_docs,
unstable_features,
unused_imports,
unused_qualifications
)]
// Clippy lints
#![deny(clippy::all)]
#![allow(clippy::needless_doctest_main)]
#![deny(clippy::pedantic)]
#![allow(clippy::module_name_repetitions)]
/////////////////////////////////////////////////////////////////////////////
pub mod core;
mod core;
mod error;
mod utils;
mod estr;
pub use self::core::*;
pub use self::error::*;
pub use self::utils::*;
#[cfg(feature = "macro")]
extern crate enve_mod;
#[cfg(feature = "macro")]
pub use enve_mod::*;
pub use crate::core::{get, get_or_set_default, sget, sset};
pub use error::Error;
pub use estr::*;

View File

@ -1,236 +0,0 @@
use crate::core::EString;
use crate::error::Error;
use std::convert::TryFrom;
use std::env;
pub fn get_or_set_default<R>(env_name: &str, default: R) -> Result<R, Error>
where
R: TryFrom<EString> + std::fmt::Display,
{
get::<R>(env_name).or_else(|err| match err {
Error::NotPresent => set(env_name, &default).parse(),
_ => Err(err),
})
}
pub fn get<R>(env_name: &str) -> Result<R, Error>
where
R: TryFrom<EString>,
{
env::var(env_name)
.map_err(From::from)
.map(EString::from)
.and_then(EString::parse)
}
pub fn set<V>(env_name: &str, value: V) -> EString
where
V: std::fmt::Display,
{
let val = value.to_string();
env::set_var(env_name, &val);
val.into()
}
#[cfg(test)]
mod tests {
use super::*;
struct TestCase<const N: u8>;
impl<const N: u8> std::fmt::Display for TestCase<N> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "test_case_{}", N)
}
}
#[test]
fn should_add_env_variable_to_process() {
let en = TestCase::<0>.to_string();
set(&en, "hello");
match env::var(&en) {
Ok(var) => assert_eq!(&var, "hello"),
_ => unreachable!(),
}
}
#[test]
fn should_return_variable() {
let en = TestCase::<1>.to_string();
env::set_var(&en, "hello");
match get::<&str>(&en) {
Ok(res) => assert_eq!(res, "hello"),
_ => unreachable!(),
};
}
#[test]
fn should_throw_no_present_error() {
let en = TestCase::<2>.to_string();
match get::<&str>(&en) {
Err(Error::NotPresent) => {}
_ => unreachable!(),
};
}
#[test]
fn should_set_default_if_var_is_no_present() {
let en = TestCase::<3>.to_string();
let orig = "hello";
match get_or_set_default(&en, orig) {
Ok(res) => {
assert_eq!(res, orig);
assert_eq!(env::var(&en).unwrap(), orig);
}
_ => unreachable!(),
};
}
#[cfg(feature = "number")]
mod numbers {
use super::*;
#[test]
fn should_return_parsed_num() {
let en = TestCase::<4>.to_string();
env::set_var(&en, "-10");
match get::<i32>(&en) {
Ok(res) => assert_eq!(res, -10),
_ => unreachable!(),
};
}
#[test]
fn should_throw_parse_error() {
let en = TestCase::<5>.to_string();
env::set_var(&en, "-10");
match get::<u32>(&en) {
Err(Error::Parse(orig)) => {
assert_eq!(orig, String::from("-10"))
}
_ => unreachable!(),
};
}
#[test]
fn should_set_default_num_if_var_is_no_present() {
let en = TestCase::<6>.to_string();
let orig = 10;
match get_or_set_default(&en, orig) {
Ok(res) => {
assert_eq!(res, orig);
assert_eq!(env::var(&en).unwrap(), "10");
}
_ => unreachable!(),
};
}
}
#[cfg(feature = "bool")]
mod boolean {
use super::*;
#[test]
fn should_parse_bool_variable() {
let en = TestCase::<7>.to_string();
[
("1", true),
("y", true),
("yes", true),
("true", true),
("t", true),
("on", true),
("false", false),
("f", false),
("0", false),
]
.iter()
.for_each(|(val, expected)| {
let mut en = en.clone();
en.push_str(val.as_ref());
env::set_var(&en, val);
match get::<bool>(&en) {
Ok(res) => assert_eq!(res, *expected),
_ => unreachable!(),
};
})
}
}
#[cfg(feature = "vec")]
mod vector {
use super::*;
use crate::core::vec::{CommaVec, SemiVec, SepVec};
#[test]
fn should_return_var_as_vector() {
let en = TestCase::<8>.to_string();
env::set_var(&en, "1,2,3,4,5");
match get::<CommaVec<i32>>(&en) {
Ok(res) => assert_eq!(*res, vec![1, 2, 3, 4, 5]),
_ => unreachable!(),
};
}
#[test]
fn should_trim_identations_before_parsing() {
let en = TestCase::<9>.to_string();
let input = "
1 , 2, 3,
4,5";
env::set_var(&en, input);
match get::<CommaVec<i32>>(&en) {
Ok(res) => assert_eq!(*res, vec![1, 2, 3, 4, 5]),
_ => unreachable!(),
};
}
#[test]
fn should_return_vector_of_vectors() {
let en = TestCase::<10>.to_string();
env::set_var(&en, "1,2; 3,4,5; 6,7");
match get::<SemiVec<CommaVec<i32>>>(&en) {
Ok(res) => assert_eq!(
res,
SemiVec::from(vec![
CommaVec::from(vec![1, 2]),
CommaVec::from(vec![3, 4, 5]),
CommaVec::from(vec![6, 7])
])
),
_ => unreachable!(),
};
}
#[test]
fn should_throw_parse_vec_error() {
let en = TestCase::<11>.to_string();
env::set_var(&en, "1,2,3,4,5");
match get::<SepVec<i32, '+'>>(&en) {
Err(Error::Parse(orig)) => {
assert_eq!(orig, String::from("1,2,3,4,5"))
}
_ => unreachable!(),
};
}
#[test]
fn should_set_default_vector_if_var_is_no_present() {
let en = TestCase::<12>.to_string();
let orig = CommaVec::from(vec![1, 2, 3, 4]);
match get_or_set_default(&en, orig.clone()) {
Ok(res) => {
assert_eq!(res, orig);
assert_eq!(env::var(&en).unwrap(), "1,2,3,4");
}
_ => unreachable!(),
};
}
}
}

View File

@ -1,613 +0,0 @@
mod test_case_1 {
itconfig::config! {
MISS_VARIABLE: bool,
}
#[test]
#[should_panic(expected = "Environment variable \"MISS_VARIABLE\" is missing")]
fn should_panic_if_miss_env_variable() {
config::init();
}
}
mod test_case_2 {
use std::env;
itconfig::config! {
DEBUG: bool,
}
#[test]
fn one_variable() {
env::set_var("DEBUG", "t");
config::init();
assert_eq!(config::DEBUG(), true);
env::remove_var("DEBUG");
}
}
mod test_case_3 {
itconfig::config! {
DEBUG: bool => true,
}
#[test]
fn one_variable_with_default_value() {
config::init();
assert_eq!(config::DEBUG(), true);
}
}
mod test_case_4 {
itconfig::config! {
FOO: bool => true,
BAR: bool => false,
}
#[test]
fn few_variables_with_default_value() {
config::init();
assert_eq!(config::FOO(), true);
assert_eq!(config::BAR(), false);
}
}
mod test_case_5 {
itconfig::config! {
NUMBER: i32 => 30,
BOOL: bool => true,
STR: String => "str",
STRING: String => "string".to_string(),
}
#[test]
fn different_types_with_default_value() {
config::init();
assert_eq!(config::NUMBER(), 30);
assert_eq!(config::BOOL(), true);
assert_eq!(config::STR(), "str".to_string());
assert_eq!(config::STRING(), "string".to_string());
}
}
mod test_case_6 {
use std::env;
itconfig::config! {
T_BOOL: bool,
TRUE_BOOL: bool,
NUM_BOOL: bool,
ON_BOOL: bool,
CAMEL_CASE: bool,
FALSE_BOOL: bool,
}
#[test]
fn convert_bool_type_value_from_env() {
env::set_var("T_BOOL", "t");
env::set_var("TRUE_BOOL", "true");
env::set_var("NUM_BOOL", "1");
env::set_var("ON_BOOL", "on");
env::set_var("CAMEL_CASE", "True");
env::set_var("FALSE_BOOL", "false");
config::init();
assert_eq!(config::T_BOOL(), true);
assert_eq!(config::TRUE_BOOL(), true);
assert_eq!(config::NUM_BOOL(), true);
assert_eq!(config::ON_BOOL(), true);
assert_eq!(config::CAMEL_CASE(), true);
assert_eq!(config::FALSE_BOOL(), false);
}
}
mod test_case_7 {
use std::env;
itconfig::config! {
I8: i8,
I16: i16,
I32: i32,
I64: i64,
I128: i128,
ISIZE: isize,
U8: u8,
U16: u16,
U32: u32,
U64: u64,
U128: u128,
USIZE: usize,
F32: f32,
F64: f64,
}
#[test]
fn convert_number_type_value_from_env() {
env::set_var("I8", "10");
env::set_var("I16", "10");
env::set_var("I32", "10");
env::set_var("I64", "10");
env::set_var("I128", "10");
env::set_var("ISIZE", "10");
env::set_var("U8", "10");
env::set_var("U16", "10");
env::set_var("U32", "10");
env::set_var("U64", "10");
env::set_var("U128", "10");
env::set_var("USIZE", "10");
env::set_var("F32", "10");
env::set_var("F64", "10");
config::init();
assert_eq!(config::I8(), 10);
assert_eq!(config::I16(), 10);
assert_eq!(config::I32(), 10);
assert_eq!(config::I64(), 10);
assert_eq!(config::ISIZE(), 10);
assert_eq!(config::U8(), 10);
assert_eq!(config::U16(), 10);
assert_eq!(config::U32(), 10);
assert_eq!(config::U64(), 10);
assert_eq!(config::USIZE(), 10);
assert_eq!(config::F32(), 10.0);
assert_eq!(config::F64(), 10.0);
}
}
mod test_case_8 {
itconfig::config! {
#![config(name = "custom_config_name")]
DEBUG: bool => true,
}
#[test]
fn change_configuration_module_name() {
custom_config_name::init();
assert_eq!(custom_config_name::DEBUG(), true);
}
}
mod test_case_9 {
use std::env;
itconfig::config! {
DEBUG: bool => true,
DB {
HOST: bool,
PORT: bool => true,
USERNAME: bool => true,
}
APP {}
}
#[test]
fn configuration_with_namespace() {
env::set_var("DB_HOST", "t");
config::init();
assert_eq!(config::DEBUG(), true);
assert_eq!(config::DB::HOST(), true);
}
}
mod test_case_10 {
itconfig::config! {
FIRST {
SECOND {
THIRD {
FOO: u32 => 50,
}
}
}
}
#[test]
fn configuration_with_nested_namespaces() {
config::init();
assert_eq!(config::FIRST::SECOND::THIRD::FOO(), 50);
}
}
mod test_case_11 {
itconfig::config! {
FIRST {
#[cfg(feature = "meta_namespace")]
SECOND {
THIRD {
FOO: u32 => 50,
}
}
}
}
#[cfg(feature = "meta_namespace")]
#[test]
fn configuration_namespaces_with_custom_meta() {
config::init();
assert_eq!(config::FIRST::SECOND::THIRD::FOO(), 50);
}
}
mod test_case_12 {
use std::env;
itconfig::config! {
testing: bool,
namespace {
foo: bool,
}
}
#[test]
fn configuration_variables_and_namespace_in_lowercase() {
env::set_var("TESTING", "t");
env::set_var("NAMESPACE_FOO", "t");
config::init();
assert_eq!(config::testing(), true);
assert_eq!(config::namespace::foo(), true);
}
}
mod test_case_13 {
use std::env;
itconfig::config! {
#[env_name = "MY_CUSTOM_NAME"]
PER_PAGE: i32,
APP {
#[env_name = "MY_CUSTOM_NAME"]
RECIPES_PER_PAGE: i32,
}
}
#[test]
fn custom_environment_name_for_variable() {
env::set_var("MY_CUSTOM_NAME", "95");
config::init();
assert_eq!(config::PER_PAGE(), 95);
assert_eq!(config::APP::RECIPES_PER_PAGE(), 95);
}
}
mod test_case_14 {
use std::env;
itconfig::config! {
#[cfg(feature = "postgres")]
#[env_name = "MY_CUSTOM_NAME"]
DATABASE_URL: String,
#[cfg(not(feature = "postgres"))]
#[env_name = "MY_CUSTOM_NAME"]
DATABASE_URL: i32,
}
#[test]
fn stranger_meta_data() {
env::set_var("MY_CUSTOM_NAME", "95");
config::init();
#[cfg(not(feature = "postgres"))]
assert_eq!(config::DATABASE_URL(), 95);
#[cfg(feature = "postgres")]
assert_eq!(config::DATABASE_URL(), "95");
}
}
mod test_case_15 {
use std::env;
itconfig::config! {
DEFAULT_ENV_STRING: String => "localhost",
DEFAULT_ENV_BOOLEAN: bool => true,
DEFAULT_ENV_UINT: u32 => 40,
DEFAULT_ENV_FLOAT: f64 => 40.9,
}
#[test]
fn setting_default_env_variable() {
config::init();
assert_eq!(env::var("DEFAULT_ENV_STRING"), Ok("localhost".to_string()));
assert_eq!(env::var("DEFAULT_ENV_BOOLEAN"), Ok("true".to_string()));
assert_eq!(env::var("DEFAULT_ENV_UINT"), Ok("40".to_string()));
assert_eq!(env::var("DEFAULT_ENV_FLOAT"), Ok("40.9".to_string()));
}
}
mod test_case_16 {
use std::env;
itconfig::config! {
DATABASE_URL < (
"postgres://",
POSTGRES_USERNAME,
":",
POSTGRES_PASSWORD,
"@",
POSTGRES_HOST,
"/",
POSTGRES_DB,
),
}
#[test]
fn concatenate_environment_variables() {
env::set_var("POSTGRES_USERNAME", "user");
env::set_var("POSTGRES_PASSWORD", "pass");
env::set_var("POSTGRES_HOST", "localhost");
env::set_var("POSTGRES_DB", "test");
config::init();
assert_eq!(
config::DATABASE_URL(),
String::from("postgres://user:pass@localhost/test")
);
}
}
mod test_case_17 {
use std::env;
itconfig::config! {
DEFAULT_CONCAT_ENV < (
"string",
"/",
SETTING_DEFAULT_CONCAT_ENV_VARIABLE,
),
}
#[test]
fn setting_default_concat_env_variable() {
env::set_var("SETTING_DEFAULT_CONCAT_ENV_VARIABLE", "custom");
config::init();
assert_eq!(
env::var("DEFAULT_CONCAT_ENV"),
Ok("string/custom".to_string())
);
}
}
mod test_case_18 {
itconfig::config! {
DATABASE_URL < (
"postgres://",
PG_USERNAME,
":",
PG_PASSWORD,
"@",
PG_HOST,
"/",
PG_DB,
),
}
#[test]
#[should_panic(expected = "Environment variable \"PG_USERNAME\" is missing")]
fn concatenate_not_defined_environment_variables() {
config::init();
}
}
mod test_case_19 {
use std::env;
itconfig::config! {
CONCATENATED_DATABASE_URL < (
"postgres://",
NOT_DEFINED_PG_USERNAME => "user",
":",
NOT_DEFINED_PG_PASSWORD => "pass",
"@",
NOT_DEFINED_PG_HOST => "localhost:5432",
"/",
NOT_DEFINED_PG_DB => "test",
),
}
#[test]
fn default_value_for_concatenate_env_parameter() {
config::init();
assert_eq!(
env::var("CONCATENATED_DATABASE_URL"),
Ok("postgres://user:pass@localhost:5432/test".to_string())
);
}
}
mod test_case_20 {
use std::env;
use std::env::VarError;
itconfig::config! {
#[env_name = "CUSTOM_CONCAT_ENVNAME"]
CONCAT_ENVVAR < (
"postgres://",
NOT_DEFINED_PG_USERNAME => "user",
":",
NOT_DEFINED_PG_PASSWORD => "pass",
"@",
NOT_DEFINED_PG_HOST => "localhost:5432",
"/",
NOT_DEFINED_PG_DB => "test",
),
}
#[test]
fn envname_meta_for_concatenated_env_variable() {
config::init();
assert_eq!(
env::var("CUSTOM_CONCAT_ENVNAME"),
Ok("postgres://user:pass@localhost:5432/test".to_string())
);
assert_eq!(env::var("CONCAT_ENVVAR"), Err(VarError::NotPresent));
}
}
mod test_case_21 {
use std::env;
use std::env::VarError;
itconfig::config! {
CONCATED_NAMESPACE {
CONCAT_ENVVAR < (
"postgres://",
NOT_DEFINED_PG_USERNAME => "user",
":",
NOT_DEFINED_PG_PASSWORD => "pass",
"@",
NOT_DEFINED_PG_HOST => "localhost:5432",
"/",
NOT_DEFINED_PG_DB => "test",
),
}
}
#[test]
fn concatenated_environment_variable_in_namespace() {
config::init();
assert_eq!(
env::var("CONCATED_NAMESPACE_CONCAT_ENVVAR"),
Ok("postgres://user:pass@localhost:5432/test".to_string())
);
assert_eq!(env::var("CONCAT_ENVVAR"), Err(VarError::NotPresent));
}
}
mod test_case_22 {
itconfig::config! {
static STATIC_STR => "test",
static STATIC_STRING: String => "test",
static STATIC_I8: i8 => 1,
static STATIC_I16: i16 => 1,
static STATIC_I32: i32 => 1,
static STATIC_I64: i64 => 1,
static STATIC_I128: i128 => 1,
static STATIC_ISIZE: isize => 1,
static STATIC_U8: u8 => 1,
static STATIC_U16: u16 => 1,
static STATIC_U32: u32 => 1,
static STATIC_U64: u64 => 1,
static STATIC_U128: u128 => 1,
static STATIC_USIZE: usize => 1,
static STATIC_F32: f32 => 1,
static STATIC_F64: f64 => 1,
static STATIC_CONCAT_VARIABLE < (
"static ",
STATIC_CONCAT_PART => "part",
),
static STATIC_VEC: Vec<u32> => vec![1],
}
#[test]
fn static_variables() {
config::init();
assert_eq!(config::STATIC_STR(), "test");
assert_eq!(config::STATIC_STRING(), "test".to_string());
assert_eq!(config::STATIC_I8(), 1);
assert_eq!(config::STATIC_I16(), 1);
assert_eq!(config::STATIC_I32(), 1);
assert_eq!(config::STATIC_I64(), 1);
assert_eq!(config::STATIC_I128(), 1);
assert_eq!(config::STATIC_ISIZE(), 1);
assert_eq!(config::STATIC_U8(), 1);
assert_eq!(config::STATIC_U16(), 1);
assert_eq!(config::STATIC_U32(), 1);
assert_eq!(config::STATIC_U64(), 1);
assert_eq!(config::STATIC_U128(), 1);
assert_eq!(config::STATIC_USIZE(), 1);
assert_eq!(config::STATIC_F32(), 1.0);
assert_eq!(config::STATIC_F64(), 1.0);
assert_eq!(config::STATIC_CONCAT_VARIABLE(), "static part".to_string());
assert_eq!(config::STATIC_VEC(), vec![1]);
}
}
mod test_case_23 {
use std::env;
itconfig::config! {
SOMETHING: Option<&'static str>,
#[env_name = "SOMETHING"]
STD_SOMETHING: std::option::Option<&'static str>,
#[env_name = "SOMETHING"]
CORE_SOMETHING: core::option::Option<&'static str>,
NOTHING: Option<&'static str>,
}
#[test]
fn optional_variables() {
env::set_var("SOMETHING", "hello world");
assert_eq!(config::SOMETHING(), Some("hello world"));
assert_eq!(config::STD_SOMETHING(), Some("hello world"));
assert_eq!(config::CORE_SOMETHING(), Some("hello world"));
assert_eq!(config::NOTHING(), None);
}
}
mod test_case_24 {
use std::env;
itconfig::config! {
MY_VEC: Vec<&'static str>,
#[env_name = "MY_VEC"]
STD_VEC: std::vec::Vec<&'static str>,
}
#[test]
fn vector_of_values() {
env::set_var("MY_VEC", "paypal,stripe");
assert_eq!(config::MY_VEC(), vec!["paypal", "stripe"]);
assert_eq!(config::STD_VEC(), vec!["paypal", "stripe"]);
}
}
mod test_case_25 {
use std::env;
itconfig::config! {
#[sep = ";"]
CUSTOM_SEP_MY_VEC: Vec<&'static str>,
#[env_name = "CUSTOM_SEP_MY_VEC"]
#[sep = ";"]
CUSTOM_SEP_STD_VEC: std::vec::Vec<&'static str>,
}
#[test]
fn custom_separator_for_vector() {
env::set_var("CUSTOM_SEP_MY_VEC", "paypal;stripe");
assert_eq!(config::CUSTOM_SEP_MY_VEC(), vec!["paypal", "stripe"]);
assert_eq!(config::CUSTOM_SEP_STD_VEC(), vec!["paypal", "stripe"]);
}
}
mod test_case_26 {
use std::env;
itconfig::config! {
OPTION_VEC: Option<Vec<&'static str>>,
}
#[test]
fn optional_vec() {
env::set_var("OPTION_VEC", "paypal,stripe");
assert_eq!(config::OPTION_VEC(), Some(vec!["paypal", "stripe"]));
}
}