Skip to main content

muster/adapter/config/
settings.rs

1use std::{fs, path::PathBuf};
2
3use super::yaml::{config_dir_path, write_config};
4use crate::domain::{config::ConfigError, port::SettingsStore, settings::Settings};
5
6/// Settings file name under the muster config directory.
7const SETTINGS_FILE: &str = "settings.yml";
8/// Shipped first-run settings. Defaults stay explicit in configuration rather
9/// than being duplicated as Rust values.
10const DEFAULT_SETTINGS: &str = include_str!("default_settings.yml");
11
12/// A [`SettingsStore`] backed by `settings.yml` in the user's config directory
13/// (`~/.config/muster/settings.yml` on Linux). On first run it writes the
14/// defaults so every value stays explicit on disk rather than living in Rust.
15#[derive(Default)]
16pub struct YamlSettingsStore;
17
18impl YamlSettingsStore {
19    /// Path to the settings file, when a config directory can be resolved.
20    fn path() -> Option<PathBuf> {
21        config_dir_path(SETTINGS_FILE)
22    }
23
24    /// Parses the shipped settings that are materialized on first run.
25    fn defaults() -> Result<Settings, ConfigError> {
26        Ok(serde_yaml_ng::from_str(DEFAULT_SETTINGS)?)
27    }
28
29    /// Loads settings from `path`, failing closed when no platform config
30    /// directory exists so an enabled default never becomes impossible to save.
31    fn load_from(path: Option<PathBuf>) -> Result<Settings, ConfigError> {
32        let path = path.ok_or(ConfigError::NoConfigDir)?;
33        if !path.exists() {
34            let defaults = Self::defaults()?;
35            write_config(&path, &defaults)?;
36            return Ok(defaults);
37        }
38        let raw = fs::read_to_string(&path).map_err(|source| ConfigError::Read {
39            path: path.clone(),
40            source,
41        })?;
42        Ok(serde_yaml_ng::from_str(&raw)?)
43    }
44}
45
46impl SettingsStore for YamlSettingsStore {
47    fn load(&self) -> Result<Settings, ConfigError> {
48        Self::load_from(Self::path())
49    }
50
51    fn save(&self, settings: &Settings) -> Result<(), ConfigError> {
52        let path = Self::path().ok_or(ConfigError::NoConfigDir)?;
53        write_config(&path, settings)
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    #[test]
62    fn no_config_directory_fails_closed() {
63        assert!(matches!(
64            YamlSettingsStore::load_from(None),
65            Err(ConfigError::NoConfigDir)
66        ));
67    }
68
69    #[test]
70    fn shipped_defaults_are_explicit_and_valid() {
71        let settings = YamlSettingsStore::defaults().unwrap();
72
73        assert!(*settings.desktop_notifications());
74    }
75}