use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use crate::error::Result;
use crate::paths;
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct Config {
#[serde(skip_serializing_if = "Option::is_none")]
pub lang: Option<String>,
}
impl Config {
pub fn path() -> Result<PathBuf> {
Ok(paths::scoop_home()?.join("config.json"))
}
pub fn load() -> Result<Self> {
let path = Self::path()?;
if !path.exists() {
return Ok(Self::default());
}
let content = std::fs::read_to_string(&path)?;
let config = serde_json::from_str(&content)?;
Ok(config)
}
pub fn save(&self) -> Result<()> {
let path = Self::path()?;
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let content = serde_json::to_string_pretty(self)?;
std::fs::write(&path, content)?;
Ok(())
}
pub fn set_lang(&mut self, lang: Option<String>) {
self.lang = lang;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = Config::default();
assert!(config.lang.is_none());
}
#[test]
fn test_serialize_config() {
let mut config = Config::default();
config.set_lang(Some("ko".to_string()));
let json = serde_json::to_string(&config).unwrap();
assert!(json.contains("\"lang\":\"ko\""));
}
#[test]
fn test_deserialize_config() {
let json = r#"{"lang":"ko"}"#;
let config: Config = serde_json::from_str(json).unwrap();
assert_eq!(config.lang, Some("ko".to_string()));
}
#[test]
fn test_deserialize_empty_config() {
let json = r#"{}"#;
let config: Config = serde_json::from_str(json).unwrap();
assert!(config.lang.is_none());
}
#[test]
fn test_skip_serializing_none() {
let config = Config::default();
let json = serde_json::to_string(&config).unwrap();
assert!(!json.contains("lang"));
}
#[test]
fn test_config_ignores_unknown_fields() {
let json = r#"{"lang":"en","theme":"dark","unknown_future_field":123}"#;
let config: Config = serde_json::from_str(json).unwrap();
assert_eq!(config.lang, Some("en".to_string()));
}
#[test]
fn test_config_backward_compat_minimal() {
let json = r#"{}"#;
let config: Config = serde_json::from_str(json).unwrap();
assert!(config.lang.is_none());
}
#[test]
fn test_config_roundtrip() {
let original = Config {
lang: Some("ko".to_string()),
};
let json = serde_json::to_string(&original).unwrap();
let restored: Config = serde_json::from_str(&json).unwrap();
assert_eq!(original.lang, restored.lang);
}
#[test]
fn test_config_unicode_lang() {
let json = r#"{"lang":"한국어"}"#;
let config: Config = serde_json::from_str(json).unwrap();
assert_eq!(config.lang, Some("한국어".to_string()));
}
#[test]
fn test_config_null_lang() {
let json = r#"{"lang":null}"#;
let config: Config = serde_json::from_str(json).unwrap();
assert!(config.lang.is_none());
}
#[test]
fn test_config_empty_string_lang() {
let json = r#"{"lang":""}"#;
let config: Config = serde_json::from_str(json).unwrap();
assert_eq!(config.lang, Some(String::new()));
}
#[test]
fn test_config_malformed_json() {
let invalid_jsons = [
r#"{"lang":"ko""#, r#"{"lang":}"#, r#"not json at all"#, r#"["lang","ko"]"#, ];
for json in invalid_jsons {
let result: std::result::Result<Config, _> = serde_json::from_str(json);
assert!(result.is_err(), "Should fail to parse: {}", json);
}
}
#[test]
fn test_config_wrong_type_for_lang() {
let json = r#"{"lang":123}"#; let result: std::result::Result<Config, _> = serde_json::from_str(json);
assert!(result.is_err());
}
#[test]
fn test_set_lang_some_to_none() {
let mut config = Config {
lang: Some("en".to_string()),
};
config.set_lang(None);
assert!(config.lang.is_none());
}
#[test]
fn test_set_lang_none_to_some() {
let mut config = Config::default();
config.set_lang(Some("ko".to_string()));
assert_eq!(config.lang, Some("ko".to_string()));
}
#[test]
fn test_set_lang_overwrites() {
let mut config = Config {
lang: Some("en".to_string()),
};
config.set_lang(Some("ko".to_string()));
assert_eq!(config.lang, Some("ko".to_string()));
}
}