# `toml-env`
[](https://crates.io/crates/toml-env) [](https://docs.rs/toml-env/latest/toml_env/) [](https://github.com/kellpossible/toml-env/actions/workflows/rust.yml)
A simple configuration library using `toml`.
This library is designed to load a configuration for an application at startup using the `initialize()` function. The configuration can be loaded (in order of preference):
1. From a dotenv style file `.env.toml` (a file name of your choosing)
2. From an environment variable `CONFIG` (or a variable name of your choosing).
3. From mapped environments (e.g. `MY_VARIABLE => my_variable.child`).
4. From a configuration file.
## Why yet another config library?
Here are some possible alternatives to this library:
- [`config`](https://crates.io/crates/config/) You want maximum flexibility.
- [`figment`](https://crates.io/crates/figment) You want maximum flexibility.
- [`just-config`](https://crates.io/crates/justconfig/) You want maximum flexibility.
- [`dotenvy`](https://crates.io/crates/dotenvy) You just want `.env` support.
- [`env_inventory`](https://crates.io/crates/env-inventory/) You just want environment variable configuration file support.
Why would you use this one?
- Small opinionated feature set.
- Minimal dependencies.
- `.env` using `TOML` which is a more established file format standard.
- Loading config from environment variables using custom mappings into the configuration (`MY_VARIABLE => child.child.config`) in a json pointer style (full syntax is not supported).
- Loading config from environment variables using automatic mappings, which are configurable. (`MY_APP__PARENT__CHILD => parent.child`)
- Loading config from TOML stored in a multiline environment variable.
- For large configurations with nested maps, this could be seen as a bit more legible than `MY_VARIABLE__SOMETHING_ELSE__SOMETHING_SOMETHING_ELSE`.
- You can also just copy text from a TOML file to use in the environment variable instead of translating it into complicated names of variables.
## Config Struct
Firstly you need to define your struct which implements `serde::de::DeserializeOwned` + `serde::Serialize` + `Default`:
```rust
#[derive(serde::Serialize, serde::Deserialize, Default)]
struct Config {
config_value_1: String,
config_value_2: String,
config_child: ConfigChild
}
#[derive(serde::Serialize, serde::Deserialize, Default)]
struct ConfigChild {
config_value_3: String,
}
```
## `.env.toml`
Initally configuration will attempted to be loaded from a file named `.env.toml` by default. You can elect to customize the name of this file. The format of this file is as follows:
```toml
SECRET_ENV_VAR_1="some value"
SECRET_ENV_VAR_2="some other value"
[CONFIG]
config_value_1="some value"
config_value_2="some other value"
[CONFIG.config_child]
config_value_3="some other other value"
```
Environment variables for the application can be set using the top level keys in the file (e.g. `SECRET_ENV_VAR_1`).
The configuration can be loaded from a subset of this file in `CONFIG`. The `CONFIG` key will be the name from the `Args::config_variable_name` which is `CONFIG` by default.
## Environment Variable `CONFIG`
You can specify the configuration by storing it in the variable name as specified using `Args::config_variable_name` (`CONFIG` by default).
```bash
# Store a multiline string into an environment variable in bash shell.
read -r -d '' CONFIG << EOM
config_value_1="some value"
config_value_2="some other value"
[config_child]
config_value_3="some other other value"
EOM
```
## Example
### `CONFIG` Variable
A simple example loading configuration from `CONFIG`, using the default settings.
```rust
use serde::{Deserialize, Serialize};
use toml_env::{initialize, Args};
#[derive(Serialize, Deserialize)]
struct Config {
value_1: String,
value_2: bool,
}
// Normally you may choose set this from a shell script or some
// other source in your environment (docker file or server config file).
std::env::set_var(
"CONFIG",
r#"
value_1="Something from CONFIG environment"
value_2=true
"#,
);
let config: Config = initialize(Args::default())
.unwrap()
.unwrap();
assert_eq!(config.value_1, "Something from CONFIG environment");
assert_eq!(config.value_2, true);
```
### Custom Variable Mappings
A simple demonstration of the custom environment variable mappings:
```rust
use serde::{Deserialize, Serialize};
use toml_env::{Args, initialize, TomlKeyPath};
use std::str::FromStr;
#[derive(Serialize, Deserialize)]
struct Config {
value_1: String,
value_2: bool,
}
// Normally you may choose set this from a shell script or some
// other source in your environment (docker file or server config file).
std::env::set_var("VALUE_1", "Hello World");
std::env::set_var("VALUE_2", "true");
let config: Config = initialize(Args {
map_env: [
("VALUE_1", "value_1"),
("VALUE_2", "value_2"),
]
.into_iter()
.map(|(key, value)| {
(key, TomlKeyPath::from_str(value).unwrap())
}).collect(),
..Args::default()
})
.unwrap()
.unwrap();
assert_eq!(config.value_1, "Hello World");
assert_eq!(config.value_2, true);
```
### Automatic Variable Mappings
A simple demonstration of the automatic environment variable mappings:
```rust
use serde::{Deserialize, Serialize};
use toml_env::{Args, initialize, AutoMapEnvArgs};
// NOTE: the `deny_unknown_fields` can be used to reject
// mappings which don't conform to the current spec.
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
struct Config {
value_1: String,
value_2: bool,
}
// Normally you may choose set this from a shell script or some
// other source in your environment (docker file or server config file).
std::env::set_var("CONFIG__VALUE_1", "Hello World");
std::env::set_var("CONFIG__VALUE_2", "true");
let config: Config = initialize(Args {
auto_map_env: Some(AutoMapEnvArgs::default()),
// The default prefix is CONFIG.
// In practice you would usually use a custom prefix:
// prefix: Some("MY_APP"),
..Args::default()
})
.unwrap()
.unwrap();
assert_eq!(config.value_1, "Hello World");
assert_eq!(config.value_2, true);
```
### `.env.toml` File
A simple example loading configuration and environment variables from `.env.toml`, using the default settings.
```rust
use serde::{Deserialize, Serialize};
use toml_env::{Args, initialize};
#[derive(Serialize, Deserialize)]
struct Config {
value_1: String,
value_2: bool,
}
let dir = tempfile::tempdir().unwrap();
std::env::set_current_dir(&dir).unwrap();
let dotenv_path = dir.path().join(".env.toml");
// Normally you would read this from .env.toml file
std::fs::write(
&dotenv_path,
r#"
OTHER_VARIABLE="hello-world"
[CONFIG]
value_1="Something from .env.toml"
value_2=true
"#,
)
.unwrap();
let config: Config = initialize(Args::default())
.unwrap()
.unwrap();
assert_eq!(config.value_1, "Something from .env.toml");
assert_eq!(config.value_2, true);
let secret = std::env::var("OTHER_VARIABLE").unwrap();
assert_eq!(secret, "hello-world");
```
### All Features
A more complex example demonstrating all the features.
```rust
use serde::{Deserialize, Serialize};
use tempfile::tempdir;
use toml_env::{Args, initialize, Logging, TomlKeyPath, AutoMapEnvArgs};
use std::str::FromStr;
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
struct Config {
value_1: String,
value_2: bool,
child: Child,
array: Vec<String>,
}
#[derive(Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields)]
struct Child {
value_3: i32,
value_4: u8,
value_5: String,
value_6: String,
}
let dir = tempdir().unwrap();
let dotenv_path = dir.path().join(".env.toml");
let config_path = dir.path().join("config.toml");
// Normally you would read this from .env.toml file
std::fs::write(
&dotenv_path,
r#"
SECRET="hello-world"
[MY_CONFIG]
value_1="Something from .env.toml"
[MY_CONFIG.child]
value_3=-5
value_4=16
"#,
)
.unwrap();
// Normally you may choose set this from a shell script or some
// other source in your environment (docker file or server config file).
std::env::set_var(
"MY_CONFIG",
r#"
value_1="Something from MY_CONFIG environment"
value_2=true
"#,
);
std::env::set_var(
"VALUE_1",
"Something from Environment"
);
std::env::set_var(
"VALUE_5",
"Something from Environment"
);
std::env::set_var(
"MY_APP__CHILD__VALUE_6",
"Something from Environment"
);
std::env::set_var(
"MY_APP__ARRAY__1",
"Hello"
);
std::env::set_var(
"MY_APP__ARRAY__0",
"Hello"
);
// Normally you would read this from config.toml
// (or whatever name you want) file.
std::fs::write(
&config_path,
r#"
value_1="Something from config.toml"
value_2=false
[child]
value_4=45
"#,
)
.unwrap();
let config: Config = initialize(Args {
dotenv_path: &dotenv_path,
config_path: Some(&config_path),
config_variable_name: "MY_CONFIG",
logging: Logging::StdOut,
map_env: [
("VALUE_1", "value_1"),
("VALUE_5", "child.value_5"),
("VALUE_99", "does.not.exist"),
]
.into_iter()
.map(|(key, value)| {
(key, TomlKeyPath::from_str(value).unwrap())
}).collect(),
auto_map_env: Some(AutoMapEnvArgs {
divider: "__",
prefix: Some("MY_APP"),
transform: Box::new(|name| name.to_lowercase()),
})
})
.unwrap()
.unwrap();
assert_eq!(config.value_1, "Something from .env.toml");
assert_eq!(config.value_2, true);
assert_eq!(config.array[0], "Hello");
assert_eq!(config.child.value_3, -5);
assert_eq!(config.child.value_4, 16);
assert_eq!(config.child.value_5, "Something from Environment");
let secret = std::env::var("SECRET").unwrap();
assert_eq!(secret, "hello-world");
```
## Changelog
See [CHANGELOG.md](https://github.com/kellpossible/toml-env/blob/master/CHANGELOG.md) for an account of changes to this library.