#environment #configuration #env #macro #env-var

macro yasec_derive

Yet another stupid environment config (YASEC) derive crate

1 stable release

Uses old Rust 2015

1.0.0 Jun 26, 2020

#140 in #environment-variables


Used in yasec

MIT license

11KB
162 lines

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

Dependencies

~2MB
~45K SLoC