#properties #proc-macro #utilities #debugging #value

macro props-util

A Rust library for easily loading and parsing properties files into strongly-typed structs

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

Download history 161/week @ 2025-04-16 48/week @ 2025-04-23 243/week @ 2025-04-30 162/week @ 2025-05-07 318/week @ 2025-05-14

825 downloads per month
Used in turn-proto

MIT license

34KB
242 lines

crates.io docs.rs Ask DeepWiki

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

  1. 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,
}
  1. 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(())
}
  1. 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> where T implements FromStr (values are comma-separated in the properties file)
  • Option<T> where T implements FromStr (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, the key attribute values must match between the source and target types. If no key 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 parsed
  • Err 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