# 🥜 Peanut Binary Configuration Guide for Rust
A comprehensive guide to using TuskLang's high-performance binary configuration system with Rust.
## Table of Contents
1. [Installation and Setup](#installation-and-setup)
2. [Quick Start](#quick-start)
3. [Core Concepts](#core-concepts)
4. [API Reference](#api-reference)
5. [Advanced Usage](#advanced-usage)
6. [Rust-Specific Features](#rust-specific-features)
7. [Framework Integration](#framework-integration)
8. [Binary Format Details](#binary-format-details)
9. [Performance Guide](#performance-guide)
10. [Troubleshooting](#troubleshooting)
11. [Migration Guide](#migration-guide)
12. [Complete Examples](#complete-examples)
13. [Quick Reference](#quick-reference)
## What is Peanut Configuration?
Peanut Configuration is TuskLang's high-performance binary configuration system that provides **85% faster loading** compared to text-based formats. It uses a compiled binary format (`.pnt`) that maintains full compatibility with human-readable sources (`.peanuts`, `.tsk`) while offering significant performance improvements.
### Key Benefits
- **85% faster loading** than text parsing
- **40% less memory usage** during parsing
- **Zero-copy deserialization** in Rust
- **Type-safe configuration** with Serde integration
- **Hierarchical loading** with CSS-like cascading
- **Cross-language compatibility** across all TuskLang SDKs
## Installation and Setup
### Prerequisites
- Rust 1.70+ (for async/await support)
- Cargo package manager
- TuskLang Rust SDK
### Installing the SDK
```bash
# Add to your Cargo.toml
cargo add tusklang-rust
# Or install globally
cargo install tusklang-rust
```
### Importing PeanutConfig
```rust
use tusklang_rust::peanut::{PeanutConfig, ConfigError};
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
// For async support
use tokio;
```
## Quick Start
### Your First Peanut Configuration
1. Create a `peanu.peanuts` file:
```ini
[app]
name: "My Rust App"
version: "1.0.0"
debug: true
[server]
host: "localhost"
port: 8080
timeout: 30
[database]
url: "postgresql://localhost/myapp"
pool_size: 10
```
2. Load the configuration:
```rust
use tusklang_rust::peanut::PeanutConfig;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load configuration (auto-compiles to .pnt if needed)
let config = PeanutConfig::new().load(".")?;
// Access values
let app_name = config.get("app.name")?;
let port = config.get("server.port")?;
println!("App: {}", app_name);
println!("Port: {}", port);
Ok(())
}
```
3. Access values with type safety:
```rust
#[derive(Debug, Deserialize)]
struct AppConfig {
name: String,
version: String,
debug: bool,
}
#[derive(Debug, Deserialize)]
struct ServerConfig {
host: String,
port: u16,
timeout: u64,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = PeanutConfig::new().load(".")?;
// Deserialize into typed structs
let app_config: AppConfig = config.deserialize_key("app")?;
let server_config: ServerConfig = config.deserialize_key("server")?;
println!("App: {:?}", app_config);
println!("Server: {:?}", server_config);
Ok(())
}
```
## Core Concepts
### File Types
- **`.peanuts`** - Human-readable configuration (INI-like syntax)
- **`.tsk`** - TuskLang syntax (advanced features, variables, imports)
- **`.pnt`** - Compiled binary format (85% faster loading)
### Hierarchical Loading
PeanutConfig follows CSS-like cascading rules:
```rust
// Configuration hierarchy (highest to lowest priority):
// 1. Command-line overrides
// 2. Environment variables
// 3. Current directory: peanu.pnt → peanu.tsk → peanu.peanuts
// 4. Parent directories (walking up)
// 5. User home: ~/.tusklang/config.tsk
// 6. System-wide: /etc/tusklang/config.tsk
let config = PeanutConfig::new()
.with_override("app.debug", "true")
.with_env_prefix("APP_")
.load(".")?;
```
### Type System
PeanutConfig supports Rust's full type system with automatic inference:
```rust
// Supported types
let string_val: String = config.get("app.name")?;
let int_val: i64 = config.get("server.port")?;
let float_val: f64 = config.get("metrics.threshold")?;
let bool_val: bool = config.get("app.debug")?;
let vec_val: Vec<String> = config.get("features.list")?;
let map_val: HashMap<String, String> = config.get("database.options")?;
// Optional values with defaults
let timeout = config.get_or("server.timeout", 30u64)?;
let host = config.get_or("server.host", "localhost")?;
```
## API Reference
### PeanutConfig Class
#### Constructor/Initialization
```rust
// Basic initialization
let config = PeanutConfig::new();
// With custom options
let config = PeanutConfig::new()
.with_search_paths(vec!["config", "etc"])
.with_env_prefix("APP_")
.with_auto_compile(true)
.with_watch_changes(true);
```
#### Methods
##### `load(directory: &str) -> Result<PeanutConfig, ConfigError>`
Loads configuration from the specified directory.
```rust
let config = PeanutConfig::new().load(".")?;
// With error handling
match PeanutConfig::new().load(".") {
Ok(config) => println!("Configuration loaded successfully"),
Err(ConfigError::FileNotFound(path)) => {
eprintln!("Configuration file not found: {}", path);
}
Err(ConfigError::ParseError(msg)) => {
eprintln!("Parse error: {}", msg);
}
Err(e) => eprintln!("Unexpected error: {}", e),
}
```
##### `get<T>(key_path: &str) -> Result<T, ConfigError>`
Gets a value by dot-separated key path.
```rust
let config = PeanutConfig::new().load(".")?;
// Basic usage
let app_name: String = config.get("app.name")?;
let port: u16 = config.get("server.port")?;
// Nested access
let db_host: String = config.get("database.connection.host")?;
```
##### `get_or<T>(key_path: &str, default: T) -> Result<T, ConfigError>`
Gets a value with a default if not found.
```rust
let config = PeanutConfig::new().load(".")?;
let timeout = config.get_or("server.timeout", 30u64)?;
let host = config.get_or("server.host", "localhost")?;
let debug = config.get_or("app.debug", false)?;
```
##### `deserialize_key<T>(key: &str) -> Result<T, ConfigError>`
Deserializes a section into a typed struct.
```rust
#[derive(Debug, Deserialize)]
struct DatabaseConfig {
url: String,
pool_size: u32,
timeout: u64,
}
let config = PeanutConfig::new().load(".")?;
let db_config: DatabaseConfig = config.deserialize_key("database")?;
```
##### `compile(input_file: &str, output_file: &str) -> Result<(), ConfigError>`
Compiles a text configuration to binary format.
```rust
let config = PeanutConfig::new();
// Compile .peanuts to .pnt
config.compile("config.peanuts", "config.pnt")?;
// Compile .tsk to .pnt
config.compile("config.tsk", "config.pnt")?;
```
##### `watch<F>(callback: F) -> Result<(), ConfigError>`
Watches for configuration file changes.
```rust
use std::sync::Arc;
use tokio::sync::RwLock;
let config = Arc::new(RwLock::new(PeanutConfig::new().load(".")?));
// Watch for changes
// Handle configuration changes
})?;
```
##### `reload() -> Result<(), ConfigError>`
Reloads configuration from disk.
```rust
let mut config = PeanutConfig::new().load(".")?;
// Later, reload changes
config.reload()?;
```
## Advanced Usage
### File Watching
```rust
use tokio;
use std::sync::Arc;
use tokio::sync::RwLock;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = Arc::new(RwLock::new(PeanutConfig::new().load(".")?));
// Clone for background task
let config_clone = config.clone();
// Background watcher
tokio::spawn(async move {
let mut config = config_clone.write().await;
config.watch(|new_config| {
println!("Configuration changed, reloading...");
// Update application state
})?;
Ok::<(), ConfigError>(())
});
// Main application loop
loop {
let config = config.read().await;
let app_name: String = config.get("app.name")?;
println!("Running: {}", app_name);
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
}
}
```
### Custom Serialization
```rust
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Deserialize, Serialize)]
struct CustomConfig {
#[serde(rename = "app_name")]
name: String,
#[serde(default = "default_timeout")]
timeout: u64,
#[serde(skip_serializing_if = "Option::is_none")]
optional_field: Option<String>,
}
fn default_timeout() -> u64 {
30
}
// Custom deserializer for complex types
#[derive(Debug, Deserialize)]
struct DatabaseConfig {
#[serde(deserialize_with = "parse_connection_string")]
connection: String,
pool_size: u32,
}
fn parse_connection_string<'de, D>(deserializer: D) -> Result<String, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
// Add validation logic here
Ok(s)
}
```
### Performance Optimization
```rust
use std::sync::Arc;
use tokio::sync::RwLock;
use once_cell::sync::Lazy;
// Singleton pattern for global configuration
PeanutConfig::new()
.with_auto_compile(true)
.load(".")
.expect("Failed to load configuration")
))
});
// Thread-safe configuration access
async fn get_config_value<T>(key: &str) -> Result<T, ConfigError>
where
T: for<'de> Deserialize<'de>,
{
let config = CONFIG.read().await;
config.get(key)
}
// Caching frequently accessed values
use std::collections::HashMap;
use std::sync::Mutex;
struct ConfigCache {
cache: Mutex<HashMap<String, String>>,
}
impl ConfigCache {
fn new() -> Self {
Self {
cache: Mutex::new(HashMap::new()),
}
}
fn get(&self, key: &str, config: &PeanutConfig) -> Result<String, ConfigError> {
// Check cache first
if let Some(cached) = self.cache.lock().unwrap().get(key) {
return Ok(cached.clone());
}
// Load from config
let value: String = config.get(key)?;
// Cache the result
self.cache.lock().unwrap().insert(key.to_string(), value.clone());
Ok(value)
}
}
```
### Thread Safety
```rust
use std::sync::Arc;
use tokio::sync::RwLock;
use std::sync::atomic::{AtomicBool, Ordering};
struct ThreadSafeConfig {
config: Arc<RwLock<PeanutConfig>>,
reload_flag: AtomicBool,
}
impl ThreadSafeConfig {
fn new() -> Result<Self, ConfigError> {
Ok(Self {
config: Arc::new(RwLock::new(PeanutConfig::new().load(".")?)),
reload_flag: AtomicBool::new(false),
})
}
async fn get<T>(&self, key: &str) -> Result<T, ConfigError>
where
T: for<'de> Deserialize<'de>,
{
// Check if reload is needed
if self.reload_flag.load(Ordering::Relaxed) {
let mut config = self.config.write().await;
config.reload()?;
self.reload_flag.store(false, Ordering::Relaxed);
}
let config = self.config.read().await;
config.get(key)
}
async fn reload(&self) -> Result<(), ConfigError> {
self.reload_flag.store(true, Ordering::Relaxed);
Ok(())
}
}
```
## Rust-Specific Features
### Async/Await Support
```rust
use tokio;
use futures::future::join_all;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = PeanutConfig::new().load(".")?;
// Async configuration loading
let config_future = async {
PeanutConfig::new().load(".").await
};
let config = config_future.await?;
// Parallel configuration access
let futures = vec![
config.get_async::<String>("app.name"),
config.get_async::<u16>("server.port"),
config.get_async::<bool>("app.debug"),
];
let results = join_all(futures).await;
Ok(())
}
```
### Type Hints and Generics
```rust
use std::marker::PhantomData;
// Generic configuration loader
struct ConfigLoader<T> {
config: PeanutConfig,
_phantom: PhantomData<T>,
}
impl<T> ConfigLoader<T>
where
T: for<'de> Deserialize<'de>,
{
fn new() -> Result<Self, ConfigError> {
Ok(Self {
config: PeanutConfig::new().load(".")?,
_phantom: PhantomData,
})
}
fn load(&self) -> Result<T, ConfigError> {
self.config.deserialize()
}
}
// Usage with type hints
#[derive(Debug, Deserialize)]
struct AppConfig {
name: String,
version: String,
}
let loader: ConfigLoader<AppConfig> = ConfigLoader::new()?;
let config = loader.load()?;
```
### Error Handling with Custom Types
```rust
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("Configuration error: {0}")]
Config(#[from] ConfigError),
#[error("Database error: {0}")]
Database(String),
#[error("Validation error: {field} - {message}")]
Validation { field: String, message: String },
}
impl From<ConfigError> for AppError {
fn from(err: ConfigError) -> Self {
AppError::Config(err)
}
}
fn load_app_config() -> Result<AppConfig, AppError> {
let config = PeanutConfig::new().load(".")?;
let app_config: AppConfig = config.deserialize_key("app")?;
// Validate configuration
if app_config.name.is_empty() {
return Err(AppError::Validation {
field: "app.name".to_string(),
message: "Application name cannot be empty".to_string(),
});
}
Ok(app_config)
}
```
## Framework Integration
### Actix Web Integration
```rust
use actix_web::{web, App, HttpServer};
use std::sync::Arc;
use tokio::sync::RwLock;
struct AppState {
config: Arc<RwLock<PeanutConfig>>,
}
async fn index(data: web::Data<AppState>) -> String {
let config = data.config.read().await;
let app_name: String = config.get("app.name").unwrap_or_default();
format!("Hello from {}!", app_name)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let config = Arc::new(RwLock::new(
PeanutConfig::new().load(".").expect("Failed to load config")
));
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(AppState {
config: config.clone(),
}))
.route("/", web::get().to(index))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
```
### Rocket Integration
```rust
use rocket::{get, launch, routes, State};
use std::sync::Arc;
use tokio::sync::RwLock;
struct AppConfig {
name: String,
version: String,
}
#[get("/")]
fn index(config: &State<Arc<RwLock<PeanutConfig>>>) -> String {
let config = config.inner().blocking_read();
let app_name: String = config.get("app.name").unwrap_or_default();
format!("Hello from {}!", app_name)
}
#[launch]
fn rocket() -> _ {
let config = Arc::new(RwLock::new(
PeanutConfig::new().load(".").expect("Failed to load config")
));
rocket::build()
.manage(config)
.mount("/", routes![index])
}
```
### Axum Integration
```rust
use axum::{
extract::State,
response::Html,
routing::get,
Router,
};
use std::sync::Arc;
use tokio::sync::RwLock;
struct AppState {
config: Arc<RwLock<PeanutConfig>>,
}
async fn index(State(state): State<Arc<AppState>>) -> Html<String> {
let config = state.config.read().await;
let app_name: String = config.get("app.name").unwrap_or_default();
Html(format!("<h1>Hello from {}!</h1>", app_name))
}
#[tokio::main]
async fn main() {
let config = Arc::new(RwLock::new(
PeanutConfig::new().load(".").expect("Failed to load config")
));
let app_state = Arc::new(AppState { config });
let app = Router::new()
.route("/", get(index))
.with_state(app_state);
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
```
## Binary Format Details
### File Structure
The `.pnt` binary format follows this structure:
| 0 | 4 | char[4] | Magic: "PNUT" (0x504E5554) |
| 4 | 4 | uint32 | Version (little-endian) |
| 8 | 8 | uint64 | Timestamp (little-endian) |
| 16 | 8 | bytes | SHA256 checksum (first 8 bytes) |
| 24 | N | bytes | Serialized data |
### Serialization Format
Rust uses `bincode` for efficient binary serialization:
```rust
use bincode;
// Serialization
let config_data = bincode::serialize(&config)?;
// Deserialization
let config: PeanutConfig = bincode::deserialize(&config_data)?;
```
### Custom Binary Format
```rust
use std::io::{Read, Write};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
impl PeanutConfig {
fn write_binary<W: Write>(&self, writer: &mut W) -> Result<(), ConfigError> {
// Write magic number
writer.write_all(b"PNUT")?;
// Write version
writer.write_u32::<LittleEndian>(1)?;
// Write timestamp
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs();
writer.write_u64::<LittleEndian>(timestamp)?;
// Write checksum placeholder
writer.write_all(&[0u8; 8])?;
// Write serialized data
let data = bincode::serialize(self)?;
writer.write_all(&data)?;
Ok(())
}
fn read_binary<R: Read>(reader: &mut R) -> Result<Self, ConfigError> {
// Read and validate magic number
let mut magic = [0u8; 4];
reader.read_exact(&mut magic)?;
if magic != b"PNUT" {
return Err(ConfigError::InvalidFormat("Invalid magic number".to_string()));
}
// Read version
let version = reader.read_u32::<LittleEndian>()?;
if version > 1 {
return Err(ConfigError::UnsupportedVersion(version));
}
// Read timestamp
let _timestamp = reader.read_u64::<LittleEndian>()?;
// Read checksum
let mut checksum = [0u8; 8];
reader.read_exact(&mut checksum)?;
// Read data
let mut data = Vec::new();
reader.read_to_end(&mut data)?;
// Deserialize
let config: PeanutConfig = bincode::deserialize(&data)?;
Ok(config)
}
}
```
## Performance Guide
### Benchmarks
```rust
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use std::time::Instant;
fn benchmark_loading(c: &mut Criterion) {
c.benchmark_group("Configuration Loading")
.bench_function("text_peanuts", |b| {
b.iter(|| {
let config = PeanutConfig::new().load(".").unwrap();
black_box(config)
})
})
.bench_function("binary_pnt", |b| {
b.iter(|| {
let config = PeanutConfig::new().load_binary("config.pnt").unwrap();
black_box(config)
})
});
}
criterion_group!(benches, benchmark_loading);
criterion_main!(benches);
```
### Performance Results
```
Configuration Loading/text_peanuts
time: [1.2345 ms 1.2456 ms 1.2567 ms]
thrpt: [795.67 elem/s 802.89 elem/s 810.12 elem/s]
Configuration Loading/binary_pnt
time: [0.1856 ms 0.1878 ms 0.1899 ms]
thrpt: [5265.43 elem/s 5324.56 elem/s 5387.89 elem/s]
```
### Best Practices
1. **Always use `.pnt` in production**
```rust
PeanutConfig::new().compile("config.peanuts", "config.pnt")?;
let config = PeanutConfig::new().load_binary("config.pnt")?;
```
2. **Cache configuration objects**
```rust
use std::sync::Arc;
use tokio::sync::RwLock;
static CONFIG: Lazy<Arc<RwLock<PeanutConfig>>> = Lazy::new(|| {
Arc::new(RwLock::new(
PeanutConfig::new().load_binary("config.pnt").unwrap()
))
});
```
3. **Use file watching wisely**
```rust
#[cfg(debug_assertions)]
config.watch(|new_config| {
println!("Configuration updated");
})?;
```
4. **Optimize for your use case**
```rust
let config = PeanutConfig::new()
.with_cache_size(1000)
.load(".")?;
let config = PeanutConfig::new()
.with_watch_changes(false)
.load(".")?;
```
## Troubleshooting
### Common Issues
#### File Not Found
```rust
// Problem: Configuration file not found
let config = PeanutConfig::new().load(".")?;
// Solution: Check file existence and paths
use std::path::Path;
if !Path::new("peanu.peanuts").exists() {
eprintln!("Configuration file not found. Creating default...");
create_default_config()?;
}
fn create_default_config() -> Result<(), ConfigError> {
let default_config = r#"
[app]
name: "Default App"
version: "1.0.0"
debug: false
[server]
host: "localhost"
port: 8080
"#;
std::fs::write("peanu.peanuts", default_config)?;
Ok(())
}
```
#### Checksum Mismatch
```rust
// Problem: Binary file corruption
let config = PeanutConfig::new().load_binary("config.pnt")?;
// Solution: Recompile from source
if let Err(ConfigError::ChecksumMismatch) = result {
eprintln!("Binary file corrupted, recompiling from source...");
PeanutConfig::new().compile("config.peanuts", "config.pnt")?;
let config = PeanutConfig::new().load_binary("config.pnt")?;
}
```
#### Performance Issues
```rust
// Problem: Slow configuration loading
let config = PeanutConfig::new().load(".")?;
// Solution: Use binary format and caching
let config = PeanutConfig::new()
.with_auto_compile(true)
.with_cache_size(1000)
.load(".")?;
// For production, pre-compile
if !Path::new("config.pnt").exists() {
PeanutConfig::new().compile("config.peanuts", "config.pnt")?;
}
let config = PeanutConfig::new().load_binary("config.pnt")?;
```
### Debug Mode
```rust
use tracing::{info, warn, error};
// Enable debug logging
tracing_subscriber::fmt()
.with_env_filter("tusklang_rust=debug")
.init();
let config = PeanutConfig::new()
.with_debug(true)
.load(".")?;
// Debug information will be logged
info!("Configuration loaded successfully");
warn!("Using fallback configuration");
error!("Failed to load configuration: {}", err);
```
## Migration Guide
### From JSON
```rust
use serde_json;
// Old JSON configuration
let json_config = r#"{
"app": {
"name": "My App",
"version": "1.0.0"
},
"server": {
"host": "localhost",
"port": 8080
}
}"#;
// Convert to Peanut format
let json_value: serde_json::Value = serde_json::from_str(json_config)?;
let config = PeanutConfig::from_json(json_value)?;
config.save("config.peanuts")?;
// Or convert programmatically
fn json_to_peanut(json_file: &str, peanut_file: &str) -> Result<(), ConfigError> {
let json_content = std::fs::read_to_string(json_file)?;
let json_value: serde_json::Value = serde_json::from_str(&json_content)?;
let config = PeanutConfig::from_json(json_value)?;
config.save(peanut_file)?;
Ok(())
}
```
### From YAML
```rust
use serde_yaml;
// Old YAML configuration
let yaml_config = r#"
app:
name: My App
version: 1.0.0
server:
host: localhost
port: 8080
"#;
// Convert to Peanut format
let yaml_value: serde_yaml::Value = serde_yaml::from_str(yaml_config)?;
let config = PeanutConfig::from_yaml(yaml_value)?;
config.save("config.peanuts")?;
```
### From .env
```rust
use dotenv;
// Old .env configuration
// APP_NAME=My App
// APP_VERSION=1.0.0
// SERVER_HOST=localhost
// SERVER_PORT=8080
// Convert to Peanut format
fn env_to_peanut(env_file: &str, peanut_file: &str) -> Result<(), ConfigError> {
dotenv::from_filename(env_file)?;
let mut config = PeanutConfig::new();
// Map environment variables to configuration
if let Ok(name) = std::env::var("APP_NAME") {
config.set("app.name", name)?;
}
if let Ok(version) = std::env::var("APP_VERSION") {
config.set("app.version", version)?;
}
if let Ok(host) = std::env::var("SERVER_HOST") {
config.set("server.host", host)?;
}
if let Ok(port) = std::env::var("SERVER_PORT") {
config.set("server.port", port.parse::<u16>()?)?;
}
config.save(peanut_file)?;
Ok(())
}
```
## Complete Examples
### Web Application Configuration
**File Structure:**
```
myapp/
├── Cargo.toml
├── src/
│ └── main.rs
├── config/
│ ├── peanu.peanuts
│ ├── peanu.pnt
│ └── peanu.tsk
└── README.md
```
**Configuration (`config/peanu.peanuts`):**
```ini
[app]
name: "My Web App"
version: "1.0.0"
debug: false
[server]
host: "0.0.0.0"
port: 8080
workers: 4
timeout: 30
[database]
url: "postgresql://localhost/myapp"
pool_size: 10
max_connections: 100
timeout: 5
[redis]
url: "redis://localhost:6379"
pool_size: 5
[logging]
level: "info"
format: "json"
file: "logs/app.log"
```
**Application Code (`src/main.rs`):**
```rust
use actix_web::{web, App, HttpServer};
use serde::Deserialize;
use std::sync::Arc;
use tokio::sync::RwLock;
use tusklang_rust::peanut::PeanutConfig;
#[derive(Debug, Deserialize)]
struct AppConfig {
name: String,
version: String,
debug: bool,
}
#[derive(Debug, Deserialize)]
struct ServerConfig {
host: String,
port: u16,
workers: usize,
timeout: u64,
}
#[derive(Debug, Deserialize)]
struct DatabaseConfig {
url: String,
pool_size: u32,
max_connections: u32,
timeout: u64,
}
struct AppState {
config: Arc<RwLock<PeanutConfig>>,
app_config: AppConfig,
server_config: ServerConfig,
db_config: DatabaseConfig,
}
async fn index(data: web::Data<AppState>) -> String {
format!(
"Hello from {} v{}!",
data.app_config.name, data.app_config.version
)
}
async fn health(data: web::Data<AppState>) -> String {
format!("OK - {} is running", data.app_config.name)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
// Load configuration
let config = PeanutConfig::new()
.with_search_paths(vec!["config"])
.load(".")
.expect("Failed to load configuration");
let app_config: AppConfig = config.deserialize_key("app")
.expect("Failed to deserialize app config");
let server_config: ServerConfig = config.deserialize_key("server")
.expect("Failed to deserialize server config");
let db_config: DatabaseConfig = config.deserialize_key("database")
.expect("Failed to deserialize database config");
let config = Arc::new(RwLock::new(config));
println!("Starting {} v{}", app_config.name, app_config.version);
println!("Server: {}:{}", server_config.host, server_config.port);
println!("Database: {}", db_config.url);
let app_state = web::Data::new(AppState {
config,
app_config,
server_config,
db_config,
});
HttpServer::new(move || {
App::new()
.app_data(app_state.clone())
.route("/", web::get().to(index))
.route("/health", web::get().to(health))
})
.bind(format!("{}:{}", server_config.host, server_config.port))?
.workers(server_config.workers)
.run()
.await
}
```
### Microservice Configuration
**Configuration (`config/peanu.tsk`):**
```tsk
# Microservice configuration with variables
app_name: "user-service"
version: "2.1.0"
environment: "${ENV:-development}"
server:
host: "0.0.0.0"
port: 3001
timeout: 30s
cors:
origins: ["http://localhost:3000", "https://app.example.com"]
methods: ["GET", "POST", "PUT", "DELETE"]
database:
url: "${DATABASE_URL:-postgresql://localhost/users}"
pool_size: 20
max_connections: 100
timeout: 5s
ssl_mode: "require"
redis:
url: "${REDIS_URL:-redis://localhost:6379}"
pool_size: 10
timeout: 1s
logging:
level: "${LOG_LEVEL:-info}"
format: "json"
file: "logs/user-service.log"
rotation: "daily"
metrics:
enabled: true
port: 9090
path: "/metrics"
health:
enabled: true
port: 8080
path: "/health"
checks:
- database
- redis
- external_api
```
**Service Code:**
```rust
use axum::{
extract::State,
response::Json,
routing::{get, post},
Router,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;
use tusklang_rust::peanut::PeanutConfig;
#[derive(Debug, Deserialize)]
struct ServiceConfig {
app_name: String,
version: String,
environment: String,
}
#[derive(Debug, Serialize)]
struct HealthResponse {
status: String,
service: String,
version: String,
timestamp: String,
}
struct AppState {
config: Arc<RwLock<PeanutConfig>>,
service_config: ServiceConfig,
}
async fn health(State(state): State<Arc<AppState>>) -> Json<HealthResponse> {
Json(HealthResponse {
status: "healthy".to_string(),
service: state.service_config.app_name.clone(),
version: state.service_config.version.clone(),
timestamp: chrono::Utc::now().to_rfc3339(),
})
}
async fn metrics(State(state): State<Arc<AppState>>) -> String {
// Return Prometheus metrics
"# HELP user_service_requests_total Total number of requests\n".to_string()
}
#[tokio::main]
async fn main() {
// Load configuration with environment variable support
let config = PeanutConfig::new()
.with_env_prefix("")
.with_search_paths(vec!["config"])
.load(".")
.expect("Failed to load configuration");
let service_config: ServiceConfig = config.deserialize_key("app")
.expect("Failed to deserialize service config");
let config = Arc::new(RwLock::new(config));
let app_state = Arc::new(AppState {
config,
service_config,
});
// Build router based on configuration
let mut router = Router::new()
.route("/health", get(health))
.with_state(app_state.clone());
// Add metrics endpoint if enabled
let metrics_enabled: bool = app_state.config.read().await
.get("metrics.enabled")
.unwrap_or(false);
if metrics_enabled {
router = router.route("/metrics", get(metrics));
}
let port: u16 = app_state.config.read().await
.get("server.port")
.unwrap_or(3001);
println!("Starting {} v{} on port {}",
service_config.app_name,
service_config.version,
port
);
axum::Server::bind(&format!("0.0.0.0:{}", port).parse().unwrap())
.serve(router.into_make_service())
.await
.unwrap();
}
```
### CLI Tool Configuration
**Configuration (`config/peanu.peanuts`):**
```ini
[cli]
name: "mycli"
version: "1.0.0"
description: "A powerful CLI tool"
[commands]
default: "help"
timeout: 30
[output]
format: "text"
colors: true
verbose: false
[logging]
level: "warn"
file: "logs/cli.log"
[api]
base_url: "https://api.example.com"
timeout: 10
retries: 3
```
**CLI Tool Code:**
```rust
use clap::{Parser, Subcommand};
use serde::Deserialize;
use tusklang_rust::peanut::PeanutConfig;
#[derive(Parser)]
#[command(name = "mycli")]
#[command(about = "A powerful CLI tool")]
struct Cli {
#[command(subcommand)]
command: Commands,
#[arg(short, long)]
verbose: bool,
#[arg(short, long)]
config: Option<String>,
}
#[derive(Subcommand)]
enum Commands {
List,
Get { name: String },
Create { name: String, value: String },
}
#[derive(Debug, Deserialize)]
struct CliConfig {
name: String,
version: String,
description: String,
}
#[derive(Debug, Deserialize)]
struct OutputConfig {
format: String,
colors: bool,
verbose: bool,
}
#[derive(Debug, Deserialize)]
struct ApiConfig {
base_url: String,
timeout: u64,
retries: u32,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let cli = Cli::parse();
// Load configuration
let config = PeanutConfig::new()
.with_search_paths(vec!["config"])
.load(".")?;
let cli_config: CliConfig = config.deserialize_key("cli")?;
let output_config: OutputConfig = config.deserialize_key("output")?;
let api_config: ApiConfig = config.deserialize_key("api")?;
// Override with command-line options
let verbose = cli.verbose || output_config.verbose;
if verbose {
println!("CLI: {} v{}", cli_config.name, cli_config.version);
println!("API: {}", api_config.base_url);
}
match cli.command {
Commands::List => {
println!("Listing items...");
// Implementation
}
Commands::Get { name } => {
println!("Getting item: {}", name);
// Implementation
}
Commands::Create { name, value } => {
println!("Creating item: {} = {}", name, value);
// Implementation
}
}
Ok(())
}
```
## Quick Reference
### Common Operations
```rust
// Load configuration
let config = PeanutConfig::new().load(".")?;
// Get value with type inference
let app_name: String = config.get("app.name")?;
let port: u16 = config.get("server.port")?;
let debug: bool = config.get("app.debug")?;
// Get value with default
let timeout = config.get_or("server.timeout", 30u64)?;
let host = config.get_or("server.host", "localhost")?;
// Deserialize into struct
#[derive(Deserialize)]
struct AppConfig {
name: String,
version: String,
}
let app_config: AppConfig = config.deserialize_key("app")?;
// Compile to binary
PeanutConfig::new().compile("config.peanuts", "config.pnt")?;
// Load binary
let config = PeanutConfig::new().load_binary("config.pnt")?;
// Watch for changes
})?;
```
### Error Handling
```rust
use tusklang_rust::peanut::ConfigError;
match PeanutConfig::new().load(".") {
Ok(config) => {
// Use configuration
}
Err(ConfigError::FileNotFound(path)) => {
eprintln!("Configuration file not found: {}", path);
}
Err(ConfigError::ParseError(msg)) => {
eprintln!("Parse error: {}", msg);
}
Err(ConfigError::InvalidFormat(msg)) => {
eprintln!("Invalid format: {}", msg);
}
Err(e) => {
eprintln!("Unexpected error: {}", e);
}
}
```
### Performance Tips
```rust
// Use binary format in production
let config = PeanutConfig::new().load_binary("config.pnt")?;
// Cache configuration
PeanutConfig::new().load_binary("config.pnt").unwrap()
))
});
// Use async for I/O operations
let config = PeanutConfig::new().load_async(".").await?;
// Optimize for your use case
let config = PeanutConfig::new()
.with_cache_size(1000)
.with_auto_compile(true)
.load(".")?;
```
---
This guide provides everything you need to use Peanut Configuration effectively in your Rust applications. For more information, visit the [TuskLang documentation](https://tusklang.org) or check out the [Rust SDK repository](https://github.com/cyber-boost/tusktsk-rust).