| Crates.io | procenv |
| lib.rs | procenv |
| version | 0.1.15 |
| created_at | 2025-11-28 16:22:47.393386+00 |
| updated_at | 2025-12-06 21:56:55.671436+00 |
| description | Derive macro for declarative environment variable configuration with error accumulation and rich diagnostics |
| homepage | |
| repository | https://github.com/consistent-milk12/procenv |
| max_upload_size | |
| id | 1955586 |
| size | 663,551 |
A Rust derive macro for configuration that shows you all your errors at once.
NOTE: This is a learning project. For production, check out figment or config-rs.
Most config libraries stop at the first error. You fix it, run again, hit another error, fix that... procenv just shows you everything that's wrong:
Error: 3 configuration error(s) occurred
× missing required environment variable: DATABASE_URL
× failed to parse PORT: expected u16, got "not_a_number"
× missing required environment variable: SECRET
File configs get source spans pointing to the exact line:
╭─[config.toml:2:8]
2 │ port = "not_a_number"
· ───────┬──────
· ╰── expected u16
╰────
use procenv::EnvConfig;
#[derive(EnvConfig)]
struct Config {
#[env(var = "DATABASE_URL")]
database_url: String,
#[env(var = "PORT", default = "8080")]
port: u16,
#[env(var = "API_KEY", optional)]
api_key: Option<String>,
#[env(var = "SECRET", secret)] // Masked in errors
secret: String,
}
fn main() -> Result<(), procenv::Error> {
let config = Config::from_env()?;
println!("Running on port {}", config.port);
Ok(())
}
[dependencies]
procenv = "0.1" # Just env vars + dotenv
# Or with file configs (no serde needed on your structs!)
procenv = { version = "0.1", features = ["file-all"] }
# Everything
procenv = { version = "0.1", features = ["full"] }
Requires Rust 1.91.1+ (uses 2024 edition).
The #[derive(EnvConfig)] macro generates:
from_env() - Load from environmentfrom_config() - Load from files + env (with file feature)from_args() - Load from CLI + env (with clap feature)env_example() - Generate .env.example docsDebug impl that redacts secrets| Feature | What it does |
|---|---|
dotenv (default) |
Load .env files |
file-all |
TOML/JSON/YAML support (serde-free!) |
clap |
CLI argument generation |
validator |
Validation via validator crate |
secrecy |
SecretString for runtime protection |
watch |
Hot reload on file changes |
Load from TOML/JSON/YAML without adding serde to your structs:
#[derive(EnvConfig)]
#[env_config(prefix = "APP_", file_optional = "config.toml")]
struct Config {
#[env(var = "PORT", default = "8080")]
port: u16,
#[env(flatten)] // Nested configs work too
database: DatabaseConfig,
}
let config = Config::from_config()?; // files + env layered
#[derive(EnvConfig)]
#[env_config(profile_env = "APP_ENV", profiles = ["dev", "prod"])]
struct Config {
#[env(var = "DATABASE_URL")]
#[profile(dev = "postgres://localhost/dev", prod = "postgres://prod/db")]
database_url: String,
}
See where each value came from:
let (config, sources) = Config::from_env_with_sources()?;
println!("{}", sources);
database_url <- Environment variable [DATABASE_URL]
port <- Default value [PORT]
api_key <- .env file [API_KEY]
Fields marked secret are never shown in error messages:
#[env(var = "API_KEY", secret)]
api_key: String, // Shows "<redacted>" in errors
For runtime protection, use SecretString (requires secrecy feature):
#[env(var = "API_KEY", secret)]
api_key: SecretString, // Requires .expose_secret() to access
validator crate via #[env_config(validate)]#[env(arg = "port", short = 'p')]WatchBuilderProvider trait for Vault, SSM, etc.cargo run --example basic
cargo run --example file_config --features file-all
cargo run --example hot_reload --features watch
This started as a learning project for proc-macros. It works, has decent test coverage (~345 tests), but hasn't seen real production use. The API might change.
MIT