chore: update config wrapper to prevent race condition
262b0266
1 file(s) · +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 | } |
|