use crate::core::error::{GraphRAGError, Result};
use serde::{Deserialize, Serialize};
use std::path::Path;
#[cfg(feature = "json5-support")]
pub fn load_json5_config<T, P>(path: P) -> Result<T>
where
T: for<'de> Deserialize<'de>,
P: AsRef<Path>,
{
let path = path.as_ref();
let contents = std::fs::read_to_string(path).map_err(|e| GraphRAGError::Config {
message: format!("Failed to read JSON5 file {:?}: {}", path, e),
})?;
parse_json5_str(&contents)
}
#[cfg(feature = "json5-support")]
pub fn parse_json5_str<T>(contents: &str) -> Result<T>
where
T: for<'de> Deserialize<'de>,
{
json5::from_str(contents).map_err(|e| GraphRAGError::Config {
message: format!("Failed to parse JSON5: {}", e),
})
}
#[cfg(feature = "json5-support")]
pub fn save_json5_config<T, P>(config: &T, path: P) -> Result<()>
where
T: Serialize,
P: AsRef<Path>,
{
let path = path.as_ref();
let json_str = serde_json::to_string_pretty(config).map_err(|e| GraphRAGError::Config {
message: format!("Failed to serialize config: {}", e),
})?;
std::fs::write(path, json_str).map_err(|e| GraphRAGError::Config {
message: format!("Failed to write JSON5 file {:?}: {}", path, e),
})?;
Ok(())
}
pub fn detect_config_format(path: &Path) -> Option<ConfigFormat> {
path.extension()
.and_then(|ext| ext.to_str())
.and_then(|ext| match ext.to_lowercase().as_str() {
"json5" => Some(ConfigFormat::Json5),
"json" => Some(ConfigFormat::Json),
"toml" => Some(ConfigFormat::Toml),
"yaml" | "yml" => Some(ConfigFormat::Yaml),
_ => None,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConfigFormat {
Json5,
Json,
Toml,
Yaml,
}
impl ConfigFormat {
pub fn extension(&self) -> &'static str {
match self {
ConfigFormat::Json5 => "json5",
ConfigFormat::Json => "json",
ConfigFormat::Toml => "toml",
ConfigFormat::Yaml => "yaml",
}
}
pub fn mime_type(&self) -> &'static str {
match self {
ConfigFormat::Json5 | ConfigFormat::Json => "application/json",
ConfigFormat::Toml => "application/toml",
ConfigFormat::Yaml => "application/x-yaml",
}
}
}
#[cfg(all(test, feature = "json5-support"))]
mod tests {
use super::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct TestConfig {
name: String,
value: i32,
enabled: bool,
}
#[test]
fn test_parse_json5_with_comments() {
let json5_str = r#"{
// This is a comment
name: "test", // Unquoted key, trailing comma
value: 42,
enabled: true,
}"#;
let config: TestConfig = parse_json5_str(json5_str).unwrap();
assert_eq!(config.name, "test");
assert_eq!(config.value, 42);
assert_eq!(config.enabled, true);
}
#[test]
fn test_parse_json5_with_single_quotes() {
let json5_str = r#"{
name: 'test',
value: 42,
enabled: true
}"#;
let config: TestConfig = parse_json5_str(json5_str).unwrap();
assert_eq!(config.name, "test");
}
#[test]
fn test_detect_format() {
assert_eq!(
detect_config_format(Path::new("config.json5")),
Some(ConfigFormat::Json5)
);
assert_eq!(
detect_config_format(Path::new("config.json")),
Some(ConfigFormat::Json)
);
assert_eq!(
detect_config_format(Path::new("config.toml")),
Some(ConfigFormat::Toml)
);
assert_eq!(
detect_config_format(Path::new("config.yaml")),
Some(ConfigFormat::Yaml)
);
assert_eq!(detect_config_format(Path::new("config.txt")), None);
}
}