6 releases
Uses new Rust 2024
new 0.2.1 | May 22, 2025 |
---|---|
0.2.0 | May 17, 2025 |
0.1.5 | May 15, 2025 |
0.1.3 | Apr 30, 2025 |
#738 in Configuration
825 downloads per month
Used in turn-proto
34KB
242 lines
Props-Util
A Rust library for easily loading and parsing properties files into strongly-typed structs.
Overview
Props-Util provides a procedural macro that allows you to derive a Properties
trait for your structs, enabling automatic parsing of properties files into your struct fields. This makes configuration management in Rust applications more type-safe and convenient.
Features
- Derive macro for automatic properties parsing
- Support for default values
- Type conversion from string to your struct's field types
- Error handling for missing or malformed properties
- Support for both file-based and default initialization
Installation
Add the following to your Cargo.toml
:
[dependencies]
props-util = "0.1.0" # Replace with the actual version
Usage
Basic Example
- Define a struct with the
Properties
derive macro:
use props_util::Properties;
#[derive(Properties, Debug)]
struct Config {
#[prop(key = "server.host", default = "localhost")]
host: String,
#[prop(key = "server.port", default = "8080")]
port: u16,
#[prop(key = "debug.enabled", default = "false")]
debug: bool,
}
- Load properties from a file:
fn main() -> std::io::Result<()> {
let config = Config::from_file("config.properties")?;
println!("Server: {}:{}", config.host, config.port);
println!("Debug mode: {}", config.debug);
Ok(())
}
- Create a properties file (e.g.,
config.properties
):
server.host=example.com
server.port=9090
debug.enabled=true
Attribute Parameters
The #[prop]
attribute accepts the following parameters:
key
: The property key to look for in the properties file (optional). If not specified, the field name will be used as the key.default
: A default value to use if the property is not found in the file (optional)env
: The environment variable name to look for (optional). If the environment variable is set, its value will be used instead of the value from the properties file.
Example of using environment variables:
use props_util::Properties;
use std::io::Result;
#[derive(Properties, Debug)]
struct Config {
#[prop(key = "server.host", env = "SERVER_HOST", default = "localhost")]
host: String,
#[prop(key = "server.port", env = "SERVER_PORT", default = "8080")]
port: u16,
#[prop(key = "api.key", env = "API_KEY")] // No default, must be set in env or props file
api_key: String,
}
fn main() -> Result<()> {
// Set environment variables for testing
std::env::set_var("SERVER_HOST", "env.example.com");
std::env::set_var("SERVER_PORT", "9090");
// Create a properties file with different values
let temp_file = tempfile::NamedTempFile::new()?;
std::fs::write(&temp_file, "server.host=file.example.com\nserver.port=8080\napi.key=test123")?;
let config = Config::from_file(temp_file.path().to_str().unwrap())?;
// Environment variables take precedence over file values
println!("Host: {}", config.host); // Will print "env.example.com"
println!("Port: {}", config.port); // Will print "9090"
println!("API Key: {}", config.api_key); // Will print "test123" (from file)
Ok(())
}
Field Types
Props-Util supports any type that implements FromStr
. This includes:
String
- Numeric types (
u8
,u16
,u32
,u64
,i8
,i16
,i32
,i64
,f32
,f64
) - Boolean (
bool
) Vec<T>
whereT
implementsFromStr
(values are comma-separated in the properties file)Option<T>
whereT
implementsFromStr
(optional fields that may or may not be present in the properties file)- Custom types that implement
FromStr
Example of using Vec and Option types:
#[derive(Properties, Debug)]
struct Config {
#[prop(key = "numbers", default = "1,2,3")]
numbers: Vec<i32>,
#[prop(key = "strings", default = "hello,world")]
strings: Vec<String>,
#[prop(key = "optional_port")] // No default needed for Option
optional_port: Option<u16>,
#[prop(key = "optional_host")] // No default needed for Option
optional_host: Option<String>,
}
In the properties file:
numbers=4,5,6,7
strings=test,vec,parsing
optional_port=9090
# optional_host is not set, so it will be None
Converting Between Different Types
You can use the from
function to convert between different configuration types. This is particularly useful when you have multiple structs that share similar configuration fields but with different types or structures:
use props_util::Properties;
use std::io::Result;
#[derive(Properties, Debug)]
struct ServerConfig {
#[prop(key = "host", default = "localhost")]
host: String,
#[prop(key = "port", default = "8080")]
port: u16,
}
#[derive(Properties, Debug)]
struct ClientConfig {
#[prop(key = "host", default = "localhost")] // Note: using same key as ServerConfig
server_host: String,
#[prop(key = "port", default = "8080")] // Note: using same key as ServerConfig
server_port: u16,
}
fn main() -> Result<()> {
// Create a temporary file for testing
let temp_file = tempfile::NamedTempFile::new()?;
std::fs::write(&temp_file, "host=example.com\nport=9090")?;
// Convert from ServerConfig to ClientConfig using the from function
let server_config = ServerConfig::from_file(temp_file.path().to_str().unwrap())?;
let client_config = ClientConfig::from(server_config)?;
println!("Server host: {}", client_config.server_host);
println!("Server port: {}", client_config.server_port);
Ok(())
}
Important: When converting between types using
from
, thekey
attribute values must match between the source and target types. If nokey
is specified, the field names must match. This ensures that the configuration values are correctly mapped between the different types.
This approach is useful when:
- You need to migrate between different configuration formats
- You have multiple applications that share configuration but use different struct layouts
- You want to transform configuration between different versions of your application
Error Handling
The from_file
method returns a std::io::Result<T>
, which will contain:
Ok(T)
if the properties file was successfully parsedErr
with an appropriate error message if:- The file couldn't be opened or read
- A required property is missing (and no default is provided)
- A property value couldn't be parsed into the expected type
- The properties file is malformed (e.g., missing
=
character)
Default Initialization
You can also create an instance with default values without reading from a file:
let config = Config::default()?;
This will use the default values specified in the #[prop]
attributes.
Properties File Format
The properties file follows a simple key-value format:
- Each line represents a single property
- The format is
key=value
- Lines starting with
#
or!
are treated as comments and ignored - Empty lines are ignored
- Leading and trailing whitespace around both key and value is trimmed
Example:
# Application settings
app.name=MyAwesomeApp
app.version=2.1.0
# Database configuration
database.url=postgres://user:pass@localhost:5432/mydb
database.pool_size=20
# Logging settings
logging.level=debug
logging.file=debug.log
# Network settings
allowed_ips=10.0.0.1,10.0.0.2,192.168.0.1
ports=80,443,8080,8443
# Features
enabled_features=ssl,compression,caching
# Optional settings
optional_ssl_port=8443
Dependencies
~295–750KB
~17K SLoC