Crate toml_env

Source
Expand description

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

Structs§

Args
Args as input to initialize().
AutoMapEnvArgs
Automatically map environment variables into config.
Error
An error that occurs while initializing configuration.
TomlKeyPath
A path to a key into a toml::Value. In the format of key.0.key (0 for indexing into an array) when parsed using FromStr.

Enums§

ConfigSource
A source of configuration.
Logging
What method of logging for this library to use.

Constants§

DEFAULT_CONFIG_VARIABLE_NAME
Default environment variable name to use for loading configuration from. Also the same name used for the table of the configuration within the .env.toml.
DEFAULT_DOTENV_PATH
Default name for attempting to load the configuration (and environment variables) from a file.
DEFAULT_MAP_ENV_DIVIDER
The default divider between different levels of parent.child in environment variable names. This will be replaced with a . for the TomlKeyPath.

Functions§

initialize
Initialize configuration from available sources specified in Args.

Type Aliases§

Result
Convenience type shorthand for Result<T, Error>.