Crates.io | better-config |
lib.rs | better-config |
version | 0.2.2 |
created_at | 2025-06-10 01:05:05.180739+00 |
updated_at | 2025-08-22 09:49:17.177372+00 |
description | better-config is a library for managing environment variables in Rust applications, providing a more ergonomic and type-safe configuration management experience. |
homepage | https://github.com/bingryan/better-config-rs |
repository | https://github.com/bingryan/better-config-rs |
max_upload_size | |
id | 1706556 |
size | 62,309 |
better-config
is a library for the configuration of Rust. It is designed to be simple, flexible, and easy to use.
FromStr
trait for structOption
for field type[✓] env : EnvConfig
-> load from env file
[✓] toml : TomlConfig
-> load from toml file
[✓] json : JsonConfig
-> load from json file
[✓] yaml/yml : YmlConfig
-> load from yaml/yml file
[✓] ini : IniConfig
-> load from ini file
[✗] More...
Run the following Cargo command in your project directory:
cargo add better-config
Or add the following line to your Cargo.toml:
better-config = "0.2"
crate features:
env
: for load from env file, default target is .env
toml
: for load from toml file, default target is config.toml
json
: for load from json file, default target is config.json
yml
: for load from yaml/yml file, default target is config.yml
ini
: for load from ini file, default target is config.ini
full
: for all featuresuse better_config::{env, EnvConfig};
#[env(EnvConfig)]
pub struct AppConfig {
#[conf(from = "DB_HOST", default = "localhost")]
pub host: String,
}
fn main() {
let config = AppConfig::builder().build().unwrap();
// load from .env file default
// if not found, use default value
assert_eq!(config.host, "env");
}
use better_config::{env, EnvConfig};
#[env(EnvConfig(prefix = "BETTER_", target = ".env.prod,.env.staging,.env.dev"))]
pub struct AppConfig {
#[conf(from = "DB_HOST", default = "localhost")]
pub host: String,
}
fn main() {
let config = AppConfig::builder().build().unwrap();
// priority: .env.prod > .env.staging > .env.dev
assert_eq!(config.host, "prod");
}
if your custom type implement FromStr
, you can use it directly.
use better_config::{env, EnvConfig};
use std::str::FromStr;
#[derive(Debug, Default, PartialEq)]
pub struct Address {
pub ip: String,
pub port: u16,
}
impl FromStr for Address {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<&str> = s.split(":").collect();
if parts.len() != 2 {
return Err("Invalid address format".to_string());
}
let ip = parts[0].to_string();
let port = parts[1]
.parse::<u16>()
.map_err(|_| "Invalid port number".to_string())?;
Ok(Address { ip, port })
}
}
#[env(EnvConfig)]
pub struct AppConfig {
#[conf(default = "default_key")]
pub api_key: String,
#[conf(default = "8000")]
pub port: u16,
#[conf(default = "false")]
pub debug: bool,
#[conf(from = "ADDRESS")]
pub address: Address,
}
fn main() {
let config: AppConfig = AppConfig::builder().build().unwrap();
assert_eq!(config.api_key, "default_key");
assert_eq!(config.port, 8080);
assert!(!config.debug);
assert_eq!(config.address, Address { ip: "127.0.0.1".to_string(), port: 8080 });
}
use better_config::{env, EnvConfig};
#[env(EnvConfig(prefix = "BETTER_", target = ".env.prod,.env.staging,.env.dev"))]
pub struct AppConfig {
#[conf(from = "DB_HOST", default = "localhost")]
pub host: String,
#[conf(from = "DB_PORT", default = "8000")]
pub port: u16,
#[conf(getter = "get_url")]
pub url: String,
#[conf(getter = "get_wrap_url")]
pub wrap_url: WrapURL,
}
#[derive(Debug, PartialEq)]
pub struct WrapURL(String);
impl AppConfigBuilder {
pub fn get_url(&self, params: &std::collections::HashMap<String, String>) -> String {
format!(
"{}",
params
.get("BETTER_DB_HOST")
.unwrap_or(&"better".to_string())
)
}
pub fn get_wrap_url(&self, p: &std::collections::HashMap<String, String>) -> WrapURL {
WrapURL(format!(
"{}",
p.get("BETTER_DB_HOST")
.unwrap_or(&"better wrap url".to_string())
))
}
}
fn main() {
let config = AppConfig::builder().build().unwrap();
// priority: .env.prod > .env.staging > .env.dev
assert_eq!(config.host, "prod");
assert_eq!(config.port, 8000);
assert_eq!(config.url, "prod");
assert_eq!(config.wrap_url, WrapURL("prod".to_string()));
}
use better_config::{env, EnvConfig};
#[env(EnvConfig(target = ".env.nested"))]
pub struct AppConfig {
#[conf(default = "default_key")]
pub api_key: String,
#[conf(from = "DEBUG", default = "false")]
pub debug: bool,
#[env]
pub database: DatabaseConfig,
}
// the target can be diffrent from the AppConfig's target, if you want to split the config file
#[env(EnvConfig(prefix = "DATABASE_", target = ".env.nested"))]
pub struct DatabaseConfig {
#[conf(from = "HOST", default = "localhost")]
pub host: String,
#[conf(from = "PORT", default = "3306")]
pub port: u16,
#[conf(from = "USER", default = "root")]
pub user: String,
#[conf(from = "PASSWORD", default = "123456")]
pub password: String,
}
fn main() {
let config: AppConfig = AppConfig::builder().build().unwrap();
assert_eq!(config.api_key, "default_key");
assert!(config.debug);
assert_eq!(config.database.host, "127.0.0.1");
assert_eq!(config.database.port, 3307);
assert_eq!(config.database.user, "admin");
assert_eq!(config.database.password, "password");
}
use better_config::{env, JsonConfig};
#[env(JsonConfig(target = "config-nested.json"))]
pub struct AppConfig {
#[conf(default = "default_key")]
pub api_key: String,
#[conf(from = "debug", default = "false")]
pub debug: bool,
#[env]
pub database: DatabaseConfig,
}
// the target can be diffrent from the AppConfig's target, if you want to split the config file
#[env(JsonConfig(prefix = "database.", target = "config-nested.json"))]
pub struct DatabaseConfig {
#[conf(from = "host", default = "localhost")]
pub host: String,
#[conf(from = "port", default = "3306")]
pub port: u16,
#[conf(from = "user", default = "root")]
pub user: String,
#[conf(from = "password", default = "123456")]
pub password: String,
}
fn main() {
let config = AppConfig::builder().build().unwrap();
assert_eq!(config.api_key, "default_key");
assert!(config.debug)
assert_eq!(config.database.host, "127.0.0.1");
assert_eq!(config.database.port, 3307);
assert_eq!(config.database.user, "admin");
assert_eq!(config.database.password, "password");
}
[!NOTE]
toml
feature is requiredfrom format:
from = "key"
, key is a dot-separated flattened key path.
use better_config::{env, TomlConfig};
#[env(TomlConfig)]
pub struct AppConfig {
#[conf(default = "default_key")]
pub api_key: String,
#[conf(from = "title", default = "hello toml")]
pub title: String,
#[conf(from = "database.enabled", default = "false")]
pub database_enabled: bool,
#[conf(from = "database.ports")]
pub database_ports: String,
}
fn main() {
let config = AppConfig::builder().build().unwrap();
assert_eq!(config.api_key, "default_key");
assert_eq!(config.title, "TOML Example");
assert!(config.database_enabled);
assert_eq!(config.database_ports, "[8000, 8001, 8002]");
}
[!NOTE]
json
feature is requiredfrom format:
from = "key"
, key is a dot-separated flattened key path.
use better_config::{env, JsonConfig};
#[env(JsonConfig)]
pub struct AppConfig {
#[conf(default = "json_default_key")]
pub api_key: String,
#[conf(from = "name")]
pub name: String,
#[conf(from = "version")]
pub version: f64,
#[conf(from = "public")]
pub public: bool,
#[conf(from = "scripts.echo")]
pub echo: String,
}
fn main() {
let config = AppConfig::builder().build().unwrap();
assert_eq!(config.api_key, "json_default_key");
assert_eq!(config.name, "config.json");
assert_eq!(config.version, 1.0);
assert!(config.public);
assert_eq!(config.echo, "echo");
}
[!NOTE]
yml
feature is required
use better_config::{env, YmlConfig};
#[env(YmlConfig)]
pub struct AppConfig {
#[conf(default = "yml_default_key")]
pub api_key: String,
#[conf(from = "title", default = "hello yml")]
pub title: String,
#[conf(from = "database.host", default = "localhost")]
pub database_host: String,
#[conf(from = "database.port")]
pub database_port: u16,
}
fn main() {
let config = AppConfig::builder().build().unwrap();
assert_eq!(config.api_key, "yml_default_key");
assert_eq!(config.title, "Yml Example");
assert_eq!(config.database_host, "127.0.0.1");
assert_eq!(config.database_port, 3306);
}
[!NOTE]
ini
feature is requiredfrom format:
from = "key"
, key is a dot-separated flattened key path.
use better_config::{env, IniConfig};
#[env(IniConfig)]
pub struct AppConfig {
#[conf(default = "ini_default_key")]
pub api_key: String,
#[conf(from = "title", default = "hello ini")]
pub title: String,
#[conf(from = "scripts.echo")]
pub scripts_echo: String,
}
fn main() {
let config = AppConfig::builder().build().unwrap();
assert_eq!(config.api_key, "ini_default_key");
assert_eq!(config.title, "INI Example");
assert_eq!(config.scripts_echo, "echo");
}
if you want to custom loader, you can implement AbstractConfig
trait and custom load function.
use better_config::{env, AbstractConfig, Error};
use std::collections::HashMap;
pub mod custom {
use super::*;
pub trait Config<T = HashMap<String, String>>: AbstractConfig {
fn load(target: Option<String>) -> Result<T, Error>
where
T: Default,
HashMap<String, String>: Into<T>,
Self: Sized,
{
// step1: pre load environment variables from target file
println!("Loading environment variables from target: {:?}", target);
// step2: load result from you logic, getter params from return HashMap
let mut map = HashMap::new();
for (key, value) in std::env::vars() {
map.insert(key, value);
}
Ok(map.into())
}
}
}
#[env(custom::Config)]
pub struct AppConfig {
#[conf(default = "default_key")]
pub api_key: String,
#[conf(default = "8000")]
pub port: u16,
#[conf(default = "false")]
pub debug: bool,
}
fn main() {
let config: AppConfig = AppConfig::builder().build().unwrap();
assert_eq!(config.api_key, "default_key");
assert_eq!(config.port, 8000);
assert_eq!(config.debug, false);
}
Contributors are welcomed to join this project. Please check CONTRIBUTING about how to contribute to this project.