use std::path::Path;
use serde::de::DeserializeOwned;
pub fn load_config_from_file<T: DeserializeOwned>(
path: impl AsRef<Path>,
) -> Result<T, ConfigLoadError> {
let path = path.as_ref();
let content = std::fs::read_to_string(path)?;
load_config_from_str(&content, path.extension().and_then(|e| e.to_str()))
}
pub fn load_config_from_str<T: DeserializeOwned>(
content: &str,
ext: Option<&str>,
) -> Result<T, ConfigLoadError> {
match ext {
Some("json") => {
serde_json::from_str(content).map_err(|e| ConfigLoadError::Parse(e.to_string()))
}
_ => {
let trimmed = content.trim();
if trimmed.starts_with('{') || trimmed.starts_with('[') {
serde_json::from_str(content).map_err(|e| ConfigLoadError::Parse(e.to_string()))
} else {
serde_json::from_str(content).map_err(|e| ConfigLoadError::Parse(e.to_string()))
}
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum ConfigLoadError {
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("parse error: {0}")]
Parse(String),
}
#[cfg(test)]
mod tests {
use super::*;
use serde::Deserialize;
#[derive(Debug, Deserialize, PartialEq)]
struct TestConfig {
name: String,
value: u32,
}
#[test]
fn parse_json_with_hint() {
let json = r#"{"name": "test", "value": 42}"#;
let config: TestConfig = load_config_from_str(json, Some("json")).unwrap();
assert_eq!(config.name, "test");
assert_eq!(config.value, 42);
}
#[test]
fn parse_json_auto_detect() {
let json = r#"{"name": "auto", "value": 1}"#;
let config: TestConfig = load_config_from_str(json, None).unwrap();
assert_eq!(config.name, "auto");
}
#[test]
fn parse_json_array_auto_detect() {
let json = r#"[{"name": "a", "value": 1}]"#;
let configs: Vec<TestConfig> = load_config_from_str(json, None).unwrap();
assert_eq!(configs.len(), 1);
}
#[test]
fn parse_error_on_invalid_json() {
let result: Result<TestConfig, _> = load_config_from_str("not json", Some("json"));
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("parse error"));
}
#[test]
fn io_error_on_missing_file() {
let result: Result<TestConfig, _> = load_config_from_file("/nonexistent/file.json");
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("io error"));
}
}