Skip to main content

hs_relmon/
config.rs

1// SPDX-License-Identifier: MPL-2.0
2
3use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5
6#[derive(Debug, Default, Serialize, Deserialize)]
7pub struct Config {
8    pub gitlab: Option<GitlabConfig>,
9}
10
11#[derive(Debug, Serialize, Deserialize)]
12pub struct GitlabConfig {
13    pub access_token: String,
14}
15
16pub fn config_path() -> Result<PathBuf, Box<dyn std::error::Error>> {
17    let config_dir = std::env::var("XDG_CONFIG_HOME")
18        .map(PathBuf::from)
19        .unwrap_or_else(|_| {
20            let home = std::env::var("HOME")
21                .unwrap_or_else(|_| ".".into());
22            PathBuf::from(home).join(".config")
23        });
24    Ok(config_dir.join("hs-relmon").join("config.toml"))
25}
26
27pub fn load() -> Result<Config, Box<dyn std::error::Error>> {
28    load_from(&config_path()?)
29}
30
31pub fn save(config: &Config) -> Result<(), Box<dyn std::error::Error>> {
32    save_to(config, &config_path()?)
33}
34
35pub fn load_from(
36    path: &std::path::Path,
37) -> Result<Config, Box<dyn std::error::Error>> {
38    let contents = std::fs::read_to_string(path)?;
39    Ok(toml::from_str(&contents)?)
40}
41
42pub fn save_to(
43    config: &Config,
44    path: &std::path::Path,
45) -> Result<(), Box<dyn std::error::Error>> {
46    if let Some(parent) = path.parent() {
47        std::fs::create_dir_all(parent)?;
48    }
49    let contents = toml::to_string_pretty(config)?;
50    std::fs::write(path, contents)?;
51    Ok(())
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[test]
59    fn test_config_deserialize() {
60        let toml_str = r#"
61[gitlab]
62access_token = "glpat-abc123"
63"#;
64        let config: Config = toml::from_str(toml_str).unwrap();
65        assert_eq!(
66            config.gitlab.as_ref().unwrap().access_token,
67            "glpat-abc123"
68        );
69    }
70
71    #[test]
72    fn test_config_deserialize_no_gitlab() {
73        let config: Config = toml::from_str("").unwrap();
74        assert!(config.gitlab.is_none());
75    }
76
77    #[test]
78    fn test_config_serialize() {
79        let config = Config {
80            gitlab: Some(GitlabConfig {
81                access_token: "glpat-abc123".into(),
82            }),
83        };
84        let s = toml::to_string_pretty(&config).unwrap();
85        assert!(s.contains("[gitlab]"));
86        assert!(s.contains("access_token = \"glpat-abc123\""));
87    }
88
89    #[test]
90    fn test_config_roundtrip() {
91        let config = Config {
92            gitlab: Some(GitlabConfig {
93                access_token: "test-token".into(),
94            }),
95        };
96        let s = toml::to_string_pretty(&config).unwrap();
97        let parsed: Config = toml::from_str(&s).unwrap();
98        assert_eq!(
99            parsed.gitlab.unwrap().access_token,
100            "test-token"
101        );
102    }
103
104    #[test]
105    fn test_config_path() {
106        let path = config_path().unwrap();
107        assert!(path.ends_with("hs-relmon/config.toml"));
108    }
109
110    #[test]
111    fn test_save_and_load() {
112        let dir = std::env::temp_dir().join("hs-relmon-test");
113        let path = dir.join("config.toml");
114        let config = Config {
115            gitlab: Some(GitlabConfig {
116                access_token: "test-save-load".into(),
117            }),
118        };
119        save_to(&config, &path).unwrap();
120        let loaded = load_from(&path).unwrap();
121        assert_eq!(
122            loaded.gitlab.unwrap().access_token,
123            "test-save-load"
124        );
125        let _ = std::fs::remove_dir_all(&dir);
126    }
127
128    #[test]
129    fn test_load_missing_file() {
130        let path = PathBuf::from("/tmp/hs-relmon-nonexistent/x");
131        assert!(load_from(&path).is_err());
132    }
133}