Giter Site home page Giter Site logo

greyblake / envconfig-rs Goto Github PK

View Code? Open in Web Editor NEW
189.0 2.0 16.0 122 KB

Build a config structure from environment variables in Rust without boilerplate

License: MIT License

Rust 98.55% Shell 0.94% Makefile 0.51%
rust configuration environment 12-factor

envconfig-rs's Introduction

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

envconfig-rs's People

Contributors

allevo avatar barskern avatar danrspencer avatar greyblake avatar hobofan avatar nashenas88 avatar technotronicoz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

envconfig-rs's Issues

Add support for durations

It would be great to have support for durations:

use std::time::Duration;

#[derive(Envconfig, Clone, Debug]
struct Config {
   #[envconfig(from="TIMEOUT")]
   pub timeout: Duration;
}

Using as:

TIMEOUT=1ms # one millisecond
TIMEOUT=1s # one second
TIMEOUT=1m # one minute
TIMEOUT=1h # one hour
# TIMEOUT=1 # fail with error: no unit

Provide underlying error in ParseError

I would like to access the reason for the ParseError error, to provide better error message to the user.

If this is a feature you would consider to add, I can prepare a PR.

Support compile-time defaults

It would be beneficial to be able to use environment variables in defaults via the "env!"-macro.

This would allow you to do something like this:

#[derive(Clone, Debug, Envconfig)]
pub struct Config {
    #[envconfig(
        from = "CUSTOM_LOG_DIR",
        default = env!("DEFAULT_LOG_DIR")
    )]
    pub log_location: String,
}

And get a compilation error "error: environment variable DEFAULT_LOG_DIR not defined" if it isn't accessible.

[Question] No code for 0.10.0?

It seems envconfig on crates.io is 0.10.0 but the master branch of this repository still shows 0.9.1. Where can I find the code for 0.10.0?

Redefining `Result` causes issues

This page suggests it should be normal to redefine Result and also use #derive[Foo], however if you do it with envconfig you get an error:

#[macro_use]
extern crate envconfig_derive;

use envconfig::Envconfig;

#[derive(Envconfig)]
pub struct Config {
	#[envconfig(from = "API_KEY", default = "")]
	pub api_key: String,

	#[envconfig(from = "API_SECRET", default = "")]
	pub api_secret: String,
}

type Result<T> = std::result::Result<T, Box<std::error::Error>>;
error[E0107]: wrong number of type arguments: expected 1, found 2
 --> src/main.rs:6:10
  |
6 | #[derive(Envconfig)]
  |          ^^^^^^^^^ unexpected type argument

Could this be because the envconfig source uses Result rather than std::Result? It seems like a bit of a scoping flaw for Rust if you ask me, but anyway it would be nice to fix it.

Automatically detect name for nested structs

I love this lib, simple and straight forward, thanks!

Just one thing that would be nice to have:
In a case like this:

#[derive(Envconfig, Debug)]
struct ProxyAuthConfig {
    enabled: bool,
    headers_username: Option<String>,
    headers_password: Option<String>,
}

#[derive(Envconfig, Debug)]
struct LocalAuthConfig {
    enabled: bool,
}

#[derive(Envconfig, Debug)]
struct AuthConfig {
    #[envconfig(nested = true)]
    proxy: ProxyAuthConfig,
    #[envconfig(nested = true)]
    local: LocalAuthConfig,
}

#[derive(Envconfig, Debug)]
pub struct ConfigurationService {
    jwt_secret: String,

    #[envconfig(nested = true)]
    auth: AuthConfig,
}

the auth.local.enabled would be named AUTH_LOCAL_ENABLED by default :)

Automatically figure out the env var name from the field name

Hi,

when I have something like this:

#[derive(Envconfig)]
pub struct Config {
    pub database_url: String
}

I would expect to be the same as:

#[derive(Envconfig)]
pub struct Config {
    #[envconfig(from = "DATABASE_URL")]
    pub database_url: String
}

Any reason why it doesn't convert that automatically ?

Add support for default attribute

Example:

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

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

Using var_os instead of var?

I have noticed that this crate uses env::var instead of the env::var_os method. For some types, such as PathBuf there is a need to start with OsStrBuf, and given that most of this library uses the Into<T> (or TryInto<T>) it might be possible to do this change without exposing it to the outside world.

Support "prefix" for nested struct environment variable names

Hey, thanks for the great library!

Has there been any consideration for supporting prefixes on nested structs? It would enable reuse of nested structs in a config like the following:

use std::env;
use envconfig::Envconfig;

#[derive(Envconfig)]
pub struct WebHookConfig {
    #[envconfig(from = "HOOK_URL")]
    url: String,

    #[envconfig(from = "HOOK_TIMEOUT")]
    timeout: i32,
}

#[derive(Envconfig)]
pub struct Config {
    #[envconfig(nested = true, prefix = "CREATE_")]
    create_hook: WebHookConfig,

    #[envconfig(nested = true, prefix = "DELETE_")]
    delete_hook: WebHookConfig,
}

fn main() {
    // We assume that those environment variables are set somewhere outside
    env::set_var("CREATE_HOOK_URL", "example-1.com");
    env::set_var("CREATE_HOOK_TIMEOUT", "8000");
    env::set_var("DELETE_HOOK_URL", "example-2.com");
    env::set_var("DELETE_HOOK_TIMEOUT", "8001");

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

    assert_eq!(config.create_hook.url, "example-1.com");
    assert_eq!(config.create_hook.timeout, 8000);

    assert_eq!(config.delete_hook.url, "example-1.com");
    assert_eq!(config.delete_hook.timeout, 8001);
}

This would be most practical if Option values were supported for nested values (but I figured that might be a conversation for a separate issue?), but still be useful even while Option isn't supported. I'd be happy to put together a PR for this if it's something you think would be a valuable addition to the library ๐Ÿ˜„

Possible to use Enums?

I'm pretty used to using something like serde for serializing enums as strings:

#[derive(Serialize, Deserialize)]
pub struct Data {
    pub logging_format: LoggingFormat,
}

#[derive(Serialize, Deserialize)]
pub enum LoggingFormat {
    #[serde(rename = "json")]
    Json,
    #[serde(rename = "plain")]
    Plain,
}

Is it possible to do something similar with Envconfig?

Add support for optinal types

See #3 (comment)

Optional types must be possible:
E.g.

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

    #[from="DB_PORT"]
    pub db_port: Option<u16>
}

Extract an env variable from several possible keys?

This crate fits all my needs up to a feature I'd like to see:

#[derive(Envconfig)]
struct Config {
    #[envconfig(from = "POSTGRESQL_URL", or_from = "POSTGRES_URL")]
    pub postgresql_url: String,
}

Because I may need to run a binary that will run with different environments where the env variables may have slightly different names. The level of priority for each of these names could be determined by their order.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.