8 releases

0.3.0 Sep 13, 2024
0.2.1 Jul 14, 2022
0.2.0 Jun 17, 2022
0.1.4 Jun 15, 2022
0.1.0 Jun 20, 2019

#31 in Configuration

Download history 683/week @ 2024-08-16 719/week @ 2024-08-23 988/week @ 2024-08-30 1276/week @ 2024-09-06 1208/week @ 2024-09-13 957/week @ 2024-09-20 846/week @ 2024-09-27 768/week @ 2024-10-04 531/week @ 2024-10-11 585/week @ 2024-10-18 560/week @ 2024-10-25 453/week @ 2024-11-01 837/week @ 2024-11-08 845/week @ 2024-11-15 987/week @ 2024-11-22 802/week @ 2024-11-29

3,579 downloads per month
Used in north-config

MIT license

24KB
160 lines

econf

Loads environment variables into your structs in one shot.

Latest version Documentation License Actions Status

econf allows to override struct fields with environment variables easily. This is useful to build up applications that optionally overrides some configuration with environment variables. Here is the basic usage:

use econf::LoadEnv;

#[derive(Debug, LoadEnv)]
struct A {
    x: bool,
    y: u64,
}

let a = A {
    x: true,
    y: 42,
};
println!("Before: {:?}", a);

let a = econf::load(a, "PREFIX");
println!("After:  {:?}", a);
$ ./app
Before: A { x: true, y: 42 }
After:  A { x: true, y: 42 }

$ PREFIX_X=false ./app
Before: A { x: true, y: 42 }
After:  A { x: false, y: 42 }

In this example,

  • PREFIX_X is loaded to x
  • PREFIX_Y is loaded to y

The environment variables are all upper-case with _ separated.

Why econf?

There are some existing crates that provide similar features but econf is unique in the following ways:

  • Supports nesting: Supports nested structs in an intutive manner with a little constraint.
  • Supports containers: Supports Vec, HashMap and various types.
  • Supplemental: Loads into existing variables in the code without changing the original logic.
  • Contributor friendly: Simple code base. Comprehensible with a little study on basic macro usage.

Supported types

  • Boolean: bool
  • Integer: isize, usize, i8, i16,i32,i64,i128, u8,u16,u32,u64,u128
  • String: char, String
  • Float: f32, f64
  • Network: IpAddr,Ipv4Addr,Ipv6Addr,SocketAddr,SocketAddrV4,SocketAddrV6
  • Non-zero types: NonZeroI128,NonZeroI16,NonZeroI32,NonZeroI64,NonZeroI8,NonZeroIsize,NonZeroU128, NonZeroU16,NonZeroU32,NonZeroU64,NonZeroU8, NonZeroUsize
  • File system: PathBuf
  • Containers: Vec, HashSet, HashMap, Option, BTreeMap, BTreeSet, BinaryHeap, LinkedList, VecDeque, tuple
    • Containers are parsed as YAML format. See the tests.

Enums

Since v0.3.0, econf requires enums to implement FromStr trait. Without this implementation, your program will fail to compile. While you can write the FromStr implementation manually, you can alternatively use strum crate to automatically generate it. strum provides several useful features, making it a generally recommended choice. See econf/examples/strum.rs for example code.

use econf::LoadEnv;

#[derive(Debug, strum::EnumString, LoadEnv)]
#[strum(serialize_all = "kebab-case")]
enum AuthMode {
    ApiKey,
    BasicAuth,
    #[strum(ascii_case_insensitive)]
    BearerToken,
    #[strum(serialize = "oauth", serialize = "OAuth")]
    OAuth,
    JWT,
}

Nesting

Nested structs are supported.

#[derive(LoadEnv)]
struct A {
    v1: usize,
    v2: B,
}

#[derive(LoadEnv)]
struct B {
    v1: usize,
    v2: usize,
}

let a = A {
    v1: 1,
    v2: B {
        v1: 2,
        v2: 3,
    },
};

let a = econf::load(a, "PREFIX");

In this example,

  • PREFIX_V1 is loaded to a.v1
  • PREFIX_V2_V1 is loaded to a.v2.v1
  • PREFIX_V2_V2 is loaded to a.v2.v2

Fields in child structs can be specified by chaining the field names with _ as a separator. However, there're cases that names conflict. For example,

#[derive(LoadEnv)]
struct A {
    v2_v1: usize,
    v2: B,
}

#[derive(LoadEnv)]
struct B {
    v1: usize,
    v2: usize,
}

let a = A {
    v2_v1: 1,
    v2: B {
        v1: 2,
        v2: 3,
    },
};

let a = econf::load(a, "PREFIX");

Here PREFIX_V2_V1 corresponds to both a.v2_v1 and a.v2.v1. In this case, econf prints warning through log facade and the value is loaded to both a.v2_v1 and a.v2.v1.

Skipping fields

Fields that do not implement LoadEnv or simply should not be loaded by econf can be skipped by adding the #[econf(skip)] helper attribute:

#[derive(LoadEnv)]
struct A {
    x: bool,
    #[econf(skip)]
    y: u64, // will not be loaded by econf
}

Renaming fields

Load a field with the given name instead of its Rust's field name. This is helpful if the environment variable name and Rust's field name don't match:

#[derive(LoadEnv)]
struct A {
    x: bool,
    #[econf(rename = "ANOTHER_Y")]
    y: u64, // will be loaded from an environment variable `ANOTHER_Y`
}

License: MIT

Dependencies

~2–2.8MB
~60K SLoC