chore: update config wrapper to prevent race condition 262b0266
Steve · 2026-04-13 22:40 1 file(s) · +21 −13
apps/sipp/src/config.rs +21 −13
1 1
use serde::{Deserialize, Serialize};
2 -
use std::path::PathBuf;
2 +
use std::path::{Path, PathBuf};
3 3
4 4
#[derive(Debug, Default, Serialize, Deserialize)]
5 5
pub struct Config {
9 9
10 10
pub fn config_path() -> PathBuf {
11 11
    let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
12 -
    PathBuf::from(home).join(".config/sipp/config.toml")
12 +
    config_path_from(Path::new(&home))
13 +
}
14 +
15 +
pub fn config_path_from(home: &Path) -> PathBuf {
16 +
    home.join(".config/sipp/config.toml")
13 17
}
14 18
15 19
pub fn load_config() -> Config {
16 -
    let path = config_path();
17 -
    match std::fs::read_to_string(&path) {
20 +
    load_config_from(&config_path())
21 +
}
22 +
23 +
pub fn load_config_from(path: &Path) -> Config {
24 +
    match std::fs::read_to_string(path) {
18 25
        Ok(contents) => toml::from_str(&contents).unwrap_or_default(),
19 26
        Err(_) => Config::default(),
20 27
    }
21 28
}
22 29
23 30
pub fn save_config(config: &Config) -> Result<(), Box<dyn std::error::Error>> {
24 -
    let path = config_path();
31 +
    save_config_to(&config_path(), config)
32 +
}
33 +
34 +
pub fn save_config_to(path: &Path, config: &Config) -> Result<(), Box<dyn std::error::Error>> {
25 35
    if let Some(parent) = path.parent() {
26 36
        std::fs::create_dir_all(parent)?;
27 37
    }
28 38
    let contents = toml::to_string_pretty(config)?;
29 -
    std::fs::write(&path, contents)?;
39 +
    std::fs::write(path, contents)?;
30 40
    Ok(())
31 41
}
32 42
68 78
    #[test]
69 79
    fn load_config_missing_file_returns_default() {
70 80
        let tmp = tempfile::tempdir().unwrap();
71 -
        // SAFETY: test-only, single-threaded test runner for this test
72 -
        unsafe { std::env::set_var("HOME", tmp.path()); }
73 -
        let config = load_config();
81 +
        let path = config_path_from(tmp.path());
82 +
        let config = load_config_from(&path);
74 83
        assert!(config.remote_url.is_none());
75 84
        assert!(config.api_key.is_none());
76 85
    }
78 87
    #[test]
79 88
    fn save_and_load_config_roundtrip() {
80 89
        let tmp = tempfile::tempdir().unwrap();
81 -
        // SAFETY: test-only, single-threaded test runner for this test
82 -
        unsafe { std::env::set_var("HOME", tmp.path()); }
90 +
        let path = config_path_from(tmp.path());
83 91
84 92
        let config = Config {
85 93
            remote_url: Some("https://sipp.example.com".to_string()),
86 94
            api_key: Some("key123".to_string()),
87 95
        };
88 -
        save_config(&config).unwrap();
96 +
        save_config_to(&path, &config).unwrap();
89 97
90 -
        let loaded = load_config();
98 +
        let loaded = load_config_from(&path);
91 99
        assert_eq!(loaded.remote_url, config.remote_url);
92 100
        assert_eq!(loaded.api_key, config.api_key);
93 101
    }