| Crates.io | fromenv |
| lib.rs | fromenv |
| version | 0.1.0 |
| created_at | 2025-08-10 20:27:17.2793+00 |
| updated_at | 2025-08-10 20:27:17.2793+00 |
| description | Derive-based, type-safe configuration from environment variables |
| homepage | |
| repository | https://github.com/ollyswanson/fromenv |
| max_upload_size | |
| id | 1789271 |
| size | 51,993 |
A declarative, type-safe library for loading configuration from environment variables.
FromEnv, that handles environment variable to
struct mapping.#[env(nested)] attribute.#[env(from, default = "...")] attribute.#[env(from, with = my_parser)] attribute.requirements method.use fromenv::FromEnv;
#[derive(FromEnv, Debug)]
pub struct Config {
#[env(from = "DATABASE_URL")]
database_url: String,
#[env(from = "PORT", default = "8080")]
port: u16,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = Config::from_env().finalize()?;
println!("Config: {:?}", config);
println!("Documentation:\n{}", Config::requirements());
Ok(())
}
use fromenv::FromEnv;
#[derive(FromEnv, Debug)]
pub struct DatabaseConfig {
#[env(from = "DB_HOST", default = "localhost")]
host: String,
#[env(from = "DB_PORT", default = "5432")]
port: u16,
}
#[derive(FromEnv, Debug)]
pub struct Config {
#[env(nested)]
database: DatabaseConfig,
#[env(from = "APP_NAME")]
name: String,
}
By default, FromEnv will use the types FromStr implementation. This can
be specified explicitly using #[env(from, with = fromstr)].
You can also specify #[env(from, with = into)] which can be useful for
types, such as secrecy's
SecretString
type which can't be constructed using FromStr but can using Into.
In addition to these built in parsers, you can supply a path to your own parser function.
use fromenv::{FromEnv, ParseResult};
use secrecy::SecretString;
// Any function with the signature `fn<T>(&str) -> Result<T, Box<dyn StdError>>`
// can be used as a custom parser.
fn comma_separated(s: &str) -> ParseResult<Vec<String>> {
Ok(s.split(',').map(ToOwned::to_owned).collect())
}
#[derive(FromEnv, Debug)]
pub struct KafkaConfig {
#[env(from = "KAFKA_BOOTSTRAP_SERVERS", with = comma_separated)]
bootstrap_servers: Vec<String>,
}
#[derive(FromEnv, Debug)]
pub struct Config {
#[env(from = "API_KEY", with = into)]
api_key: SecretString,
#[env(nested)]
kafka: KafkaConfig,
}
Both "flat" and "nested" fields can be made optional. When making a field optional:
nested configuration, the field will be set to None if
and only if all of the errors returned from attempting to parse it are due
to missing env vars or values.use fromenv::FromEnv;
#[derive(FromEnv, Debug)]
pub struct Config {
#[env(from = "OTEL_RESOURCE_ATTRIBUTES")]
resource_attributes: Option<String>,
}
In tests especially, it can be frustrating to want to override a portion of the configuration but read the rest from environment variables, only to find yourself having to explictly set every field in the config.
It is possible to override portions of the configuration before calling
finalize.
Overriding has a higher precedence than reading environment variables, and any fields which have been overridden will skip reading from the environment, avoiding any errors that might arise from missing environment variables.
use fromenv::FromEnv;
#[derive(FromEnv, Debug)]
pub struct TelemetryConfig {
#[env(from = "RUST_LOG")]
log_level: String,
}
#[derive(FromEnv, Debug)]
pub struct Config {
#[env(from)]
database_url: String,
#[env(from, default = "8080")]
port: u16,
#[env(nested)]
telemetry: TelemetryConfig,
}
#[test]
fn test() {
// Override `port` and `telemetry.log_level` but read `database_url` from
// the environment.
let config = Config::from_env()
.port(0)
.telemetry(|telemetry| telemetry.log_level("debug".into()))
.finalize()
.unwrap();
// Rest of the test...
}
#[env(from = "ENV_NAME")] - Load from specified environment variable.#[env(from)] - Load from environment variable matching field's uppercase
name.#[env(from, default = "value")] - Default value if environment variable
is not set.#[env(from, with = parser_fn)] - Custom parser function.#[env(nested)] - For nested configuration structures.env attribute for a field, but to avoid any
errors the value must be set using the override methods before calling
finalize.The finalize() method returns a Result<T, FromEnvErrors> where
FromEnvErrors contains the accumulated configuration errors.
This produces error messages of the form:
2 configuration errors:
1. `Config.database_url`: Missing required environment variable 'DATABASE_URL'
2. `Config.port`: Failed to parse 'PORT'="invalid": invalid digit found in string
This crate is designed to scratch a very specific itch.
cargo insta to both document the env vars required for my
applications, and to prevent unintended breakage in my configuration.If you don't have the same itch, or would rather find other workarounds, or (sensibly) want to use what everyone else is using, use:
Licensed under either of Apache License, Version 2.0 or MIT license at your option.