Project: Config file validator (TOML/JSON) with precise error reporting
Absolutely! Here’s a complete, production-ready Rust project that validates TOML or JSON config files with precise, user-friendly error reporting—perfect for your MSP, lab compliance, or device repair workflows.
This replaces fragile shell/Python scripts with a memory-safe, self-documenting, and auditable tool that fails fast with clear messages (in English or Urdu-ready).
🎯 Project: config-lint
Validate config files against a strict schema with line/column error reporting, contextual help, and structured output.
✅ Ideal for:
Validating client MSP agent configs
Checking ISO lab device settings
Pre-flight checks before repair workflows
🛠️ Tech Stack
-
serde+serde_json/toml: For parsing -
anyhow+thiserror: For layered, contextual errors -
clap: CLI argument parsing -
No unsafe code
📁 Cargo.toml
[package]
name = "config-lint"
version = "0.1.0"
edition = "2021"
[dependencies]
clap = { version = "4.5", features = ["derive"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
toml = "0.8"
anyhow = "1.0"
thiserror = "1.0"
📄 src/main.rs
use clap::Parser;
use serde::Deserialize;
use std::path::PathBuf;
#[derive(Parser)]
#[command(name = "config-lint", about = "Validate TOML/JSON config files with precise errors")]
struct Cli {
/// Path to config file (must be .json or .toml)
#[arg(value_parser = parse_config_path)]
config_path: PathBuf,
}
fn parse_config_path(s: &str) -> Result<PathBuf, &'static str> {
let path = PathBuf::from(s);
let ext = path.extension().and_then(|s| s.to_str()).unwrap_or("");
if ext == "json" || ext == "toml" {
Ok(path)
} else {
Err("Config file must end with .json or .toml")
}
}
#[derive(Deserialize, Debug)]
struct Config {
#[serde(rename = "client_id")]
client_id: String,
#[serde(rename = "contact_email")]
contact_email: String,
#[serde(rename = "security_level")]
security_level: SecurityLevel,
#[serde(rename = "devices", default)]
devices: Vec<Device>,
}
#[derive(Deserialize, Debug)]
struct Device {
model: String,
firmware_version: String,
#[serde(default)]
is_critical: bool,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "lowercase")]
enum SecurityLevel {
Basic,
Enhanced,
Fips140,
}
// Custom validation logic
impl Config {
fn validate(&self) -> Result<(), anyhow::Error> {
// Validate client_id
if self.client_id.is_empty() {
anyhow::bail!("client_id must not be empty");
}
if !self.client_id.chars().all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_') {
anyhow::bail!("client_id may only contain letters, digits, '-', or '_'");
}
// Validate email (basic)
if !self.contact_email.contains('@') {
anyhow::bail!("contact_email must be a valid email address");
}
// Validate devices
if self.devices.is_empty() {
anyhow::bail!("At least one device must be specified");
}
for (i, dev) in self.devices.iter().enumerate() {
if dev.model.is_empty() {
anyhow::bail!("devices[{}].model must not be empty", i);
}
if dev.firmware_version.is_empty() {
anyhow::bail!("devices[{}].firmware_version must not be empty", i);
}
}
Ok(())
}
}
fn main() -> Result<(), anyhow::Error> {
let cli = Cli::parse();
let config = match cli.config_path.extension().and_then(|s| s.to_str()) {
Some("json") => {
let content = std::fs::read_to_string(&cli.config_path)
.with_context(|| format!("Failed to read {}", cli.config_path.display()))?;
serde_json::from_str::<Config>(&content)
.with_context(|| format!("Invalid JSON in {}", cli.config_path.display()))?
}
Some("toml") => {
let content = std::fs::read_to_string(&cli.config_path)
.with_context(|| format!("Failed to read {}", cli.config_path.display()))?;
toml::from_str::<Config>(&content)
.with_context(|| format!("Invalid TOML in {}", cli.config_path.display()))?
}
_ => anyhow::bail!("Unsupported file type"),
};
config.validate()
.with_context(|| format!("Validation failed for {}", cli.config_path.display()))?;
println!("✅ {} is valid", cli.config_path.display());
Ok(())
}
🧪 Try It Out
1. Create a valid config.toml
client_id = "KH-KHI-01"
contact_email = "khawar@remote-support.space"
security_level = "enhanced"
[[devices]]
model = "GamePad-X7"
firmware_version = "v2.1.0"
is_critical = true
2. Run validation
cargo run -- config.toml
# ✅ config.toml is valid
3. Introduce an error
client_id = "" # ← empty!
Output:
Error: Validation failed for config.toml
Caused by:
client_id must not be empty
4. Invalid TOML syntax
client_id = Khawar # ← missing quotes
Output:
Error: Invalid TOML in config.toml
Caused by:
TOML parse error at line 1, column 12
|
1 | client_id = Khawar
| ^
invalid string
🔍 Note: TOML/JSON parsers give line/column errors automatically!
🔒 Why This Is Better Than Shell/Python
| Risk in Legacy Scripts | Rust Solution |
|---|---|
jq fails silently on bad JSON |
serde_json panics on parse error → caught and reported |
| No schema validation → runtime crash later | Validate at startup → fail fast |
| No structured error context | Chained errors with file + reason |
| Hard to localize (Urdu/English) | Add fluent-rs later for bilingual messages |
🚀 Extend for Your Business
Add Urdu support (optional):
// In validation:
anyhow::bail!(
"{} / {}",
"client_id must not be empty",
"کلائنٹ آئی ڈی خالی نہیں ہونی چاہیے"
);
Output machine-readable JSON (for automation):
config-lint --format json config.toml
# { "valid": true } or { "error": "..." }
Integrate with your Digital Readiness Report:
-
Run
config-lintas first step -
If valid, proceed to generate report
-
If invalid, return 5-page report with “Config Fix Required” section
📦 Deployment
# Build static binary
cargo build --release --target x86_64-unknown-linux-musl
# Deploy to client server
./config-lint ./agent.conf
💼 Selling point: “Our configuration validator ensures your system meets security baselines before we even begin diagnostics.”