• Stars
    star
    186
  • Rank 207,316 (Top 5 %)
  • Language
    Rust
  • License
    MIT License
  • Created about 6 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Build a config structure from environment variables in Rust without boilerplate

Envconfig logo

Build Status License Documentation

Initialize config structure from environment variables in Rust without boilerplate.

Stand With Ukraine

Install

[dependencies]
envconfig = "0.10.0"

Usage

Basic example

Let's say you application relies on the following environment variables:

  • DB_HOST
  • DB_PORT

And you want to initialize Config structure like this one:

struct Config {
    db_host: String,
    db_port: u16,
}

You can achieve this with the following code without boilerplate:

use envconfig::Envconfig;

#[derive(Envconfig)]
pub struct Config {
    #[envconfig(from = "DB_HOST")]
    pub db_host: String,

    #[envconfig(from = "DB_PORT", default = "5432")]
    pub db_port: u16,
}

fn main() {
    // Assuming the following environment variables are set
    std::env::set_var("DB_HOST", "127.0.0.1");

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

    assert_eq!(config.db_host, "127.0.0.1");
    assert_eq!(config.db_port, 5432);
}

Nested configs

Configs can be nested. Just add #[envconfig(nested = true)] to nested field.

#[derive(Envconfig)]
pub struct DbConfig {
    #[envconfig(from = "DB_HOST")]
    pub host: String,

    #[envconfig(from = "DB_PORT", default = "5432")]
    pub port: u16,
}

#[derive(Envconfig)]
pub struct Config {
    #[envconfig(nested = true)]     // <---
    db: DbConfig,

    #[envconfig(from = "HOSTNAME")]
    hostname: String,
}

Custom types

Under the hood envconfig relies on FromStr trait. If you want to use a custom type as a field for config, you have to implement FromStr trait for your custom type.

Let's say we want to extend DbConfig with driver field, which is DbDriver enum that represents either Postgresql or Mysql:

pub enum DbDriver {
    Postgresql,
    Mysql,
}

impl std::str::FromStr for DbDriver {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.trim().to_lowercase().as_ref() {
            "postgres" => Ok(DbDriver::Postgresql),
            "mysql" => Ok(DbDriver::Mysql),
            _ => Err(format!("Unknown DB driver: {s}"))
        }
    }
}

#[derive(Envconfig)]
pub struct DbConfig {
    // ...
    #[envconfig(from = "DB_DRIVER")]
    pub driver: DbDriver,
}

If this seems too cumbersome, consider using other crates like strum to derive FromStr automatically.

use strum::EnumString;

#[derive(EnumString)]
pub enum DbDriver {
    Postgresql,
    Mysql,
}

Testing

When writing tests you should avoid using environment variables. Cargo runs Rust tests in parallel by default which means you can end up with race conditions in your tests if two or more are fighting over an environment variable.

To solve this you can initialise your struct from a HashMap<String, String> in your tests. The HashMap should match what you expect the real environment variables to be; for example DB_HOST environment variable becomes a DB_HOST key in your HashMap.

use envconfig::Envconfig;

#[derive(Envconfig)]
pub struct Config {
    #[envconfig(from = "DB_HOST")]
    pub db_host: String,

    #[envconfig(from = "DB_PORT", default = "5432")]
    pub db_port: u16,
}

#[test]
fn test_config_can_be_loaded_from_hashmap() {
    // Create a HashMap that looks like your environment
    let mut hashmap = HashMap::new();
    hashmap.insert("DB_HOST".to_string(), "127.0.0.1".to_string());

    // Initialize config from a HashMap to avoid test race conditions
    let config = Config::init_from_hashmap(&hashmap).unwrap();

    assert_eq!(config.db_host, "127.0.0.1");
    assert_eq!(config.db_port, 5432);
}

Contributing

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

MIT ยฉ Sergey Potapov

Contributors

  • greyblake Potapov Sergey - creator, maintainer.
  • allevo Tommaso Allevi - support nested structures
  • hobofan Maximilian Goisser - update dependencies

More Repositories

1

nutype

Rust newtype with guarantees ๐Ÿ‡บ๐Ÿ‡ฆ ๐Ÿฆ€
Rust
1,068
star
2

whatlang-rs

Natural language detection library for Rust. Try demo online: https://whatlang.org/
Rust
926
star
3

ta-rs

Technical analysis library for Rust language
Rust
584
star
4

vim-preview

Vim plugin for previewing markup files(markdown,rdoc,textile,html)
Vim Script
209
star
5

blogo

Mountable blog engine for Ruby on Rails
Ruby
101
star
6

kinded

Generate Rust enum variants without associated data
Rust
81
star
7

cargo-testify

Watches changes in a rust project, runs test and shows friendly notification
Rust
80
star
8

awesome-programming-books

List of good programming books for beginners and professionals
80
star
9

mago

Magic numbers detector for Ruby source code
Ruby
58
star
10

humble-investing

List of resources that I use for investing research
41
star
11

jsonpath-rs

JSONPath for Rust
Rust
37
star
12

from-typescript-to-rescript

Frontend of https://Inhyped.com written in TypeScript and rewritten in ReScript
TypeScript
29
star
13

telebot

Ruby client for Telegram bot API
Ruby
28
star
14

fast_seeder

Speed up seeding your Rails application using multiple SQL inserts!
Ruby
25
star
15

xplan

Visualizes dependencies between tasks
Rust
22
star
16

dm-rspec

RSpec matchers for DataMapper
Ruby
19
star
17

hellcheck

HTTP health checker implemented in Rust
Rust
19
star
18

vim-esperanto

Vim plugin for typing Esperanto language in any way (Esperanto keyboard, h, x, ^)
Vim Script
15
star
19

crystal-google_translate

Google Translate client for Crystal
Crystal
14
star
20

hail

HTTP load testing tool powered by Rust
Rust
14
star
21

crystal-magma

Crystal interpreter
Crystal
13
star
22

poloniex-rs

Rust client for Poloniex API
Rust
9
star
23

whatlang-ffi

C bindings for whatlang Rust library
C
9
star
24

rustcast

Code for RustCast screencast episodes (https://www.youtube.com/channel/UCZSy_LFJOtOPPcsE64KxDkw)
Rust
8
star
25

crystal-aitk

Artificial Intelligence Tool Kit for Crystal lang
Crystal
8
star
26

tokipona

Ruby library to process constructed language Toki Pona
Ruby
8
star
27

rails3_pg_deferred_constraints

Rails 3 engine which provides a hack to avoid RI_ConstraintTrigger Error bug.
Ruby
7
star
28

dotvim

My .vim
Vim Script
4
star
29

conway-rs

Conway's Game of Life implemented in Rust.
Rust
4
star
30

arbitrary_ext

Provides a way to derive Arbitrary trait but set custom implementation for single fields if necessary.
Rust
4
star
31

crystal-glosbe

Crystal Client for Glosbe API
Crystal
3
star
32

rails_markdown

Allows you to use markdown templates with placeholders in rails application}
Ruby
3
star
33

vim-smartdict

Vim plugin to use translate words (dictionary).
Vim Script
3
star
34

dot-nvim

My nvim config
Vim Script
2
star
35

crystal-cossack

Simple and flexible HTTP client for Crystal with middleware and test support.
2
star
36

enum_param-rs

Rust
2
star
37

beep-alarm

Alarm written in bash and based on beep tool
2
star
38

crystal-delemma

Lemmatization tool for German language.implemented in Crystal
Crystal
2
star
39

crystal-telegram_bot

Crystal
2
star
40

whatlang-website

Website for whatlang (whatlang.org)
JavaScript
2
star
41

crystal-icr

Interactive console for Crystal programming language
2
star
42

envconfig-rs-old

An easy way to build a config structure form environment variables in Rust without boilerplate.
Rust
1
star
43

greyblake.github.com

My blog
HTML
1
star
44

talks

Public talks / presentations
Vue
1
star
45

alis

Tool to create more flexible aliases.
Ruby
1
star
46

rustcast-travis-demo

Rust
1
star
47

gync

Synchronize data of desktop applications with Git
Ruby
1
star
48

dm-enum

Enumerated models for DataMapper
Ruby
1
star
49

deutscher_bot

Telegram Bot that helps to learn German, implemented in Crystal
Crystal
1
star
50

crystal-jwt

JWT implementation in Crystal
1
star