Skip to content

peanutbother/serini

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

serini Crates.io

A serde-based INI file parser for Rust that supports automatic serialization and deserialization of structs to INI format.

Features

  • 🚀 Automatic serialization - Convert Rust structs to INI format
  • 🔧 Type-safe deserialization - Parse INI files into strongly-typed Rust structs
  • 📁 Section support - Nested structs become INI sections automatically
  • 💭 Option handling - None values are serialized as commented lines if serialization is not skipped using #[serde(skip)] or similiar
  • 🛡️ Escape sequences - Properly handles special characters in values
  • 🏷️ Serde integration - Supports serde attributes like #[serde(rename = "...")]

Installation

Add this to your Cargo.toml:

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serini = "0.2"

Quick Example

use serde::{Deserialize, Serialize};
use serini::{from_str, to_string};

#[derive(Debug, Serialize, Deserialize)]
struct Config {
    name: String,
    port: u16,
    #[serde(skip_serializing_if = "Option::is_none")]
    debug: Option<usize>,
    database: Database,
}

#[derive(Debug, Serialize, Deserialize)]
struct Database {
    host: String,
    username: String,
    password: Option<String>,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = Config {
        name: "My App".to_string(),
        port: 8080,
        debug: None,
        database: Database {
            host: "localhost".to_string(),
            username: "admin".to_string(),
            password: None,
        },
    };

    // Serialize to INI
    let ini_string = to_string(&config)?;
    println!("{}", ini_string);

    // Deserialize from INI
    let parsed: Config = from_str(&ini_string)?;
    assert_eq!(config, parsed);
    Ok(())
}

Output:

name = My App
port = 8080

[database]
host = localhost
username = admin
; password = 

Usage Guide

Basic Types

serini supports all basic Rust types:

#[derive(Serialize, Deserialize)]
struct Settings {
    // Integers
    max_connections: u32,      // max_connections = 100
    retry_count: i8,          // retry_count = 3
    
    // Floats
    timeout: f64,             // timeout = 30.5
    
    // Booleans
    debug_mode: bool,         // debug_mode = true
    
    // Strings
    server_name: String,      // server_name = Production Server
    
    // Options
    description: Option<String>, // ; description =  (when None)
}

Sections from Nested Structs

Nested structs automatically become INI sections:

#[derive(Serialize, Deserialize)]
struct App {
    version: String,
    
    server: ServerConfig,
    database: DbConfig,
}

#[derive(Serialize, Deserialize)]
struct ServerConfig {
    host: String,
    port: u16,
}

#[derive(Serialize, Deserialize)]
struct DbConfig {
    url: String,
    pool_size: u32,
}

Produces:

version = 1.0.0

[server]
host = 0.0.0.0
port = 8080

[database]
url = postgres://localhost/mydb
pool_size = 10

Self-Referential Structs

serini supports self-referential structs using Option<Box<T>>, allowing sections to override values from the root configuration:

#[derive(Debug, Serialize, Deserialize)]
struct Config {
    speed: f32,
    movie: Option<Box<Config>>,
    anime: Option<Box<Config>>,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Parse from INI
    let ini_str = r#"
speed = 1

[anime]
speed = 1.5

[movie]
speed = 2
"#;

    let config: Config = from_str(ini_str)?;
    
    println!("Default speed: {}", config.speed);
    if let Some(anime_config) = &config.anime {
        println!("Anime speed: {}", anime_config.speed);
    }
    if let Some(movie_config) = &config.movie {
        println!("Movie speed: {}", movie_config.speed);
    }

    // Serialize to INI
    let new_config = Config {
        speed: 1.0,
        anime: Some(Box::new(Config {
            speed: 1.5,
            anime: None,
            movie: None,
        })),
        movie: Some(Box::new(Config {
            speed: 2.0,
            anime: None,
            movie: None,
        })),
    };

    let ini_output = to_string(&new_config)?;
    println!("{}", ini_output);
    Ok(())
}

Output:

speed = 1

[anime]
speed = 1.5

[movie]
speed = 2

This pattern is useful for configuration files where different profiles or modes can override default settings.

Field Renaming

Use serde's rename attribute:

#[derive(Serialize, Deserialize)]
struct Config {
    #[serde(rename = "app-name")]
    app_name: String,
    
    #[serde(rename = "log-level")]
    log_level: String,
}

Escape Sequences

Special characters are automatically escaped:

Character Escaped
\ \\
newline \n
tab \t
" \"
; \;
# \#

Real-World Example

use serde::{Deserialize, Serialize};
use serini::{from_str, to_string};

#[derive(Debug, Serialize, Deserialize)]
struct ServerConfig {
    #[serde(rename = "app-name")]
    app_name: String,
    environment: String,
    
    http: HttpConfig,
    database: DatabaseConfig,
    redis: Option<RedisConfig>,
}

#[derive(Debug, Serialize, Deserialize)]
struct HttpConfig {
    host: String,
    port: u16,
    #[serde(rename = "request-timeout")]
    request_timeout: u64,
    #[serde(rename = "enable-tls")]
    enable_tls: bool,
}

#[derive(Debug, Serialize, Deserialize)]
struct DatabaseConfig {
    url: String,
    #[serde(rename = "max-connections")]
    max_connections: u32,
    #[serde(rename = "connection-timeout")]
    connection_timeout: u64,
}

#[derive(Debug, Serialize, Deserialize)]
struct RedisConfig {
    url: String,
    #[serde(rename = "connection-pool")]
    connection_pool: u32,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = ServerConfig {
        app_name: "my-awesome-app".to_string(),
        environment: "production".to_string(),
        
        http: HttpConfig {
            host: "0.0.0.0".to_string(),
            port: 443,
            request_timeout: 30,
            enable_tls: true,
        },
        
        database: DatabaseConfig {
            url: "postgres://user:pass@localhost/mydb".to_string(),
            max_connections: 100,
            connection_timeout: 5,
        },
        
        redis: None,
    };

    let ini = to_string(&config)?;
    println!("{}", ini);
    Ok(())
}

Output:

app-name = my-awesome-app
environment = production

[http]
host = 0.0.0.0
port = 443
request-timeout = 30
enable-tls = true

[database]
url = postgres://user:pass@localhost/mydb
max-connections = 100
connection-timeout = 5

; redis = 

API

// Serialize a value to INI string
pub fn to_string<T: Serialize>(value: &T) -> Result<String, Error>

// Deserialize from INI string
pub fn from_str<'a, T: Deserialize<'a>>(s: &'a str) -> Result<T, Error>

Error Types

serini uses a custom error type with helpful error messages:

use serini::Error;

match result {
    Err(Error::InvalidValue { typ, value }) => {
        eprintln!("Invalid {} value: {}", typ, value);
    }
    Err(Error::UnsupportedFeature(feature)) => {
        eprintln!("Unsupported feature: {}", feature);
    }
    Err(e) => eprintln!("Error: {}", e),
    Ok(_) => {}
}

Limitations

The following types are not supported:

  • Sequences (Vec, arrays)
  • Tuples and tuple structs
  • Enums with variants
  • Maps (HashMap, BTreeMap)
  • Nested arrays or complex data structures

Why serini?

  • Simple - Minimal API with just two functions
  • Type-safe - Leverages Rust's type system and serde
  • Automatic - No manual parsing or writing required
  • Flexible - Supports Options, escaping, and field renaming
  • Fast - Zero-copy deserialization where possible

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.

About

Ser(de)ini parser

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •  

Languages