Skip to content

Build a config structure from environment variables in Rust without boilerplate

License

Notifications You must be signed in to change notification settings

ANtlord/yasec

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

82 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Yasec

Build Status License

Yet another stupid environment config (YASEC) creates settings from environment variables. (Envconig-rs fork)

Features

  • Nested configuration structure.
  • Inferring of an environment variable name. If a configuration field has name password then it gets value from environment variable PASSWORD. If a configuration field is inside another structure and it has path db.password it gets its value from variable DB_PASSWORD.
  • Custom types.
  • Option type is optional.
  • Prefix of variables.

I implemented everything what I require when I develop an application. Feel free to open an issue of a feature you miss as well as a pull request.

Macro attributes

  • from - name of an environment variable which provides a field value. Name of the field and name of the parent structures are ignored.
  • default - default value of a field if an environment variable doesn't exist. If the environment variable exist but has invalid value an error returns.

Usage

You can achieve this with the following code without boilerplate:

#[macro_use]
extern crate yasec_derive;
extern crate yasec;

use std::error::Error as StdError;
use yasec::Yasec;

#[derive(Yasec)]
pub struct DB {
    pub host: String,
    pub port: u16,
}

#[derive(Yasec)]
pub struct Vendor {
    #[yasec(from = "API_KEY")]
    pub key: String,
    #[yasec(from = "API_SECRET")]
    pub secret: String,
}

#[derive(Yasec)]
pub struct Config {
    db: DB,
    vendor: Vendor,
    #[yasec(default = 8080)]
    listen_port: u16,
    callback_url: Option<String>,
    mode: Mode,
}

pub enum Mode {
    Client,
    Server,
}

impl Yasec for Mode {
    fn parse(s: &str) -> Result<Self, Box<dyn StdError>> {
        match s {
            "CLIENT" => Ok(Self::Client),
            "SERVER" => Ok(Self::Server),
            _ => Err(yasec::ParseError::new(s).into()),
        }
    }
}

fn main() {
    // Assuming the following environment variables are set
    std::env::set_var("DB_HOST", "127.0.0.1");
    std::env::set_var("DB_PORT", "5432");
    std::env::set_var("API_KEY", "0912xn819b8s1029s");
    std::env::set_var("API_SECRET", "zyYWn5pPtLcDSaFWQEu0nf1cf0eYNN8j");
    std::env::set_var("MODE", "SERVER");
    std::env::remove_var("LISTEN_PORT");
    std::env::remove_var("CALLBACK_URL");

    // Initialize config from environment variables or terminate the process.
    let config = Config::init().unwrap();

    assert_eq!(config.db.host, "127.0.0.1");
    assert_eq!(config.db.port, 5432);
    assert_eq!(config.vendor.key, "0912xn819b8s1029s");
    assert_eq!(config.vendor.secret, "zyYWn5pPtLcDSaFWQEu0nf1cf0eYNN8j");
    assert_eq!(config.listen_port, 8080);
    assert_eq!(config.callback_url, None);
    match config.mode {
        Mode::Server => (),
        _ => panic!("Unexpected value of Mode"),
    }
}

Running tests

Tests do some manipulation with environment variables, so to prevent flaky tests they have to be executed in a single thread:

cargo test -- --test-threads=1

License

Licensed under MIT

About

Build a config structure from environment variables in Rust without boilerplate

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Rust 98.9%
  • Shell 1.1%