use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
pub struct Config {
#[serde(default)]
pub default_org: Option<String>,
#[serde(default)]
pub default_project: Option<String>,
#[serde(default)]
pub favorite_repos: Vec<String>,
}
impl Config {
pub fn dir() -> Result<PathBuf> {
dirs::config_dir()
.map(|d| d.join("pike"))
.context("Could not determine config directory")
}
pub fn load() -> Result<Self> {
let path = Self::dir()?.join("config.json");
if !path.exists() {
return Ok(Self::default());
}
let content = std::fs::read_to_string(&path).context("Failed to read config file")?;
serde_json::from_str(&content).context("Failed to parse config file")
}
pub fn save(&self) -> Result<()> {
let dir = Self::dir()?;
std::fs::create_dir_all(&dir).context("Failed to create config directory")?;
let path = dir.join("config.json");
let content = serde_json::to_string_pretty(self).context("Failed to serialize config")?;
std::fs::write(&path, content).context("Failed to write config file")
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
fn with_temp_config_dir<F>(f: F)
where
F: FnOnce(PathBuf),
{
let tmp = TempDir::new().unwrap();
let dir = tmp.path().join("pike");
f(dir);
}
#[test]
fn config_default_values() {
let config = Config::default();
assert!(config.default_org.is_none());
assert!(config.default_project.is_none());
assert!(config.favorite_repos.is_empty());
}
#[test]
fn config_roundtrip() {
let config = Config {
default_org: Some("my-org".to_string()),
default_project: Some("Roadmap".to_string()),
favorite_repos: vec!["my-org/repo1".to_string(), "my-org/repo2".to_string()],
};
let json = serde_json::to_string_pretty(&config).unwrap();
let deserialized: Config = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.default_org, Some("my-org".to_string()));
assert_eq!(deserialized.default_project, Some("Roadmap".to_string()));
assert_eq!(
deserialized.favorite_repos,
vec!["my-org/repo1", "my-org/repo2"]
);
}
#[test]
fn config_save_and_load() {
with_temp_config_dir(|dir| {
let config = Config {
default_org: Some("test-org".to_string()),
default_project: None,
favorite_repos: vec![],
};
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("config.json");
let content = serde_json::to_string_pretty(&config).unwrap();
std::fs::write(&path, &content).unwrap();
let loaded: Config =
serde_json::from_str(&std::fs::read_to_string(&path).unwrap()).unwrap();
assert_eq!(loaded.default_org, Some("test-org".to_string()));
assert!(loaded.default_project.is_none());
});
}
#[test]
fn config_deserialize_missing_fields() {
let json = r#"{}"#;
let config: Config = serde_json::from_str(json).unwrap();
assert!(config.default_org.is_none());
assert!(config.default_project.is_none());
assert!(config.favorite_repos.is_empty());
}
}