envy

Deserialize environment variables directly into typesafe Rust structs using Serde.

Library
Cargo
v0.4.2
978stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
33/100Needs Attention
Development Activity0
Maintenance0
Community44
Maturity60
Momentum28

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
76/100Good
Architecture80
Code Quality78
Innovation62
Learning Curve85

envy is a small Rust crate that lets you deserialize a process’s environment variables straight into a typesafe struct via Serde’s Deserialize trait, instead of hand-rolling env::var calls and manual parsing for every config field. It maps struct field names to uppercase env var names automatically, and leans on Serde’s derive machinery to hand you a fully-typed configuration object or a descriptive error in one call.

Beyond the basic from_env path, envy supports Option fields for optional vars, comma-separated values for Vec fields, unit-variant enums, serde defaults and renames, and a prefixed() helper for namespacing all the variables a specific app or component reads (e.g. APP_FOO, APP_BAR). It has no runtime dependencies beyond serde itself, making it a lightweight addition to any Rust binary that reads its configuration from the environment.

What You Get

  • from_env::<T>() to deserialize any Deserialize-able struct directly from the current process environment
  • from_iter::<Iter, T>() to deserialize from an arbitrary iterator of (String, String) pairs, useful for testing without touching real env vars
  • prefixed("APP_") for scoping deserialization to only the env vars carrying a given prefix, with the prefix stripped before matching
  • keep_names() for case-sensitive/exact field-name matching instead of the default uppercase convention
  • Automatic support for Option<T> fields (absent var = None), Vec<T> fields (comma-separated values), and unit-variant enums
  • Descriptive Error::MissingValue and Error::Custom error types that name the offending env var

Common Use Cases

  • Loading a 12-factor app’s configuration (ports, feature flags, credentials, timeouts) into one typed struct at startup
  • Providing sensible defaults for optional configuration via #[serde(default = "...")] while still failing fast on missing required values
  • Namespacing configuration for a library embedded in a larger app using envy::prefixed("MYLIB_") so it doesn’t collide with the host app’s own env vars
  • Unit-testing configuration-loading logic by constructing env var pairs in-memory and calling from_iter instead of mutating real process environment

Under The Hood

Architecture envy is built entirely around Serde’s Deserializer trait rather than a hand-written parser: src/lib.rs implements a custom Deserializer over a Vars iterator of (VarName, Val) pairs sourced from std::env::vars() or a caller-supplied iterator, and delegates the actual map traversal to Serde’s MapDeserializer. Each individual Val implements Deserializer itself, forwarding primitive types to str::parse via a forward_parsed_values! macro and special-casing deserialize_seq (comma-split for Vec fields) and deserialize_option/deserialize_enum. Prefixed and KeepNames are thin wrapper types that filter/transform the incoming iterator before running it through the same core Deserializer, so the whole crate funnels through one code path with no branching special cases per public API. If the core Deserializer implementation changed shape, every public entry point (from_env, from_iter, prefixed().from_env()) would need to move in lockstep since they all construct the same struct.

Tech Stack The crate has exactly one runtime dependency: serde (^1.0), used purely for its Deserialize/Deserializer/IntoDeserializer traits — no serde_json or other format crate is involved since envy is itself the “format” (env var strings). Dev-dependencies add serde with the derive feature for the test suite’s example structs. The crate targets the 2021 Rust edition, ships no build script, and CI runs cargo fmt, cargo clippy, cargo check, and cargo test across stable/beta/nightly via GitHub Actions, publishing docs to GitHub Pages and the crate to crates.io on tagged releases.

Code Quality Testing lives entirely in src/lib.rs’s #[cfg(test)] mod tests, covering successful deserialization, missing-value errors, prefix stripping, invalid-type errors, and case-sensitive keep_names behavior against representative structs (Foo, CrazyFoo) rather than trivial smoke tests. Error handling is fully typed through a small Error enum (MissingValue, Custom) implementing both std::error::Error and Serde’s de::Error, so callers get structured errors instead of panics or opaque strings. CI enforces rustfmt and clippy -D clippy::all on every push, and coverage is tracked via cargo-tarpaulin reported to Coveralls. The codebase is compact (two files, under 700 lines) with consistent naming and no unsafe code.

What Makes It Unique Rather than parsing env vars into a HashMap<String, String> and then manually pulling fields out with type coercion (the common ad-hoc pattern), envy implements a full Serde Deserializer, which means every serde attribute — #[serde(default)], #[serde(rename)], #[serde(flatten)]-adjacent behavior, custom Deserialize impls, nested structs — works on environment variables exactly as it would deserializing JSON or TOML. The prefixed() and keep_names() variants extend this to multi-tenant or embedded-library configuration scenarios without requiring a second parsing pass or wrapper struct.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search