12 releases (3 stable)

1.2.0 Feb 18, 2024
1.1.1 Sep 25, 2023
0.3.5 Sep 23, 2023
0.2.0 Sep 23, 2023
0.1.1 Sep 23, 2023

#45 in Configuration

Download history 120/week @ 2024-02-16 44/week @ 2024-02-23 39/week @ 2024-03-01 107/week @ 2024-03-08 40/week @ 2024-03-15 25/week @ 2024-03-22 22/week @ 2024-03-29 2/week @ 2024-04-05

103 downloads per month

MIT license

48KB
832 lines

toml-env

crates.io docs.rs GitHub Workflow Status (with event)

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 You want maximum flexibility.
  • figment You want maximum flexibility.
  • just-config You want maximum flexibility.
  • dotenvy You just want .env support.
  • 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:

#[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:

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).

# 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.

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:

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:

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.

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.

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 for an account of changes to this library.

Dependencies

~0.6–1.3MB
~28K SLoC