tenvy

Crates.iotenvy
lib.rstenvy
version1.0.0
created_at2025-09-22 12:14:50.829629+00
updated_at2025-09-22 12:14:50.829629+00
descriptionParse environment variables into type-safe structures
homepage
repositoryhttps://gitlab.com/SnejUgal/tenvy
max_upload_size
id1849962
size36,641
Artem Starikov (snejugal)

documentation

README

tenvy

Parse environment variables into type-safe structures.

use tenvy::Tenvy;

#[derive(Debug, Tenvy)]
struct Environment {
    database_url: String,
    server: Server,
}

#[derive(Debug, Tenvy)]
struct Server {
    addr: std::net::SocketAddr,
    workers: Option<std::num::NonZeroUsize>,
}

fn main() -> anyhow::Result<()> {
    let env: Environment = tenvy::from_env()?;
    println!("{env:#?}");
    Ok(())
}

How is it different from envy and serde-env?

Both envy and serde-env make use of serde to extract environment variables into types. Although the idea to use serde is rather nice and allows to reuse features from serde, it faces several challenges in practice:

  • Nested structures: envy does not support them at all, while serde-env provides suboptimal error messages if a variable is missing;

  • Flattening: #[serde(flatten)] is hard to support because it uses deserialize_any;

  • Renaming: counterintuitively you have to use lowercase names in #[serde(rename)], and that also means you cannot differentiate between lowercase-named and uppercase-named variables;

  • Customized deserialization: shall you need parse variables in a way that cannot be achieved using serde_derive, you have to resort to very verbose Deserialize implementation.

  • Genericity: empty substructures are not handled well. For example, deserializing Environment<SubsystemStub> successfully requires some variable that starts with SUBSYSTEMS to be present, although its value is never used:

    #[derive(Deserialize)]
    struct Environment<Subsystem> {
      subsystem: Subsystem,
    }
    
    #[derive(Deserialize)]
    struct SubsystemStub {}
    

Many of such issues arise because serde is designed to parse various kinds of data by giving everything it reads to the deserializer. However, when parsing environment variables, we usually want to ask for data we need instead. Moreover, we know that environment variables is a key-value structure with basically random access.

So tenvy takes this other approach when we know that we're dealing with a key-value map and ask it for data we need. This approach should allow for much more pleasant API with less obstacles and surprises.

Commit count: 27

cargo fmt