opentalk_client_data_persistence/
config_manager.rs1use std::{fs, path::PathBuf};
6
7use snafu::{OptionExt, ResultExt as _};
8
9use crate::{
10 ConfigError,
11 config::Config,
12 config_error::{
13 FolderNotCreatableSnafu, NotLoadableSnafu, NotReadableSnafu, NotStorableSnafu,
14 NotWriteableSnafu, SystemConfigHomeNotSetSnafu,
15 },
16};
17
18#[derive(Debug)]
20pub struct ConfigManager {
21 path: PathBuf,
22}
23
24impl ConfigManager {
25 pub fn new() -> Result<Self, ConfigError> {
27 let config_path = dirs::config_dir()
28 .context(SystemConfigHomeNotSetSnafu)?
29 .join("opentalk/cli.toml");
30
31 Ok(Self { path: config_path })
32 }
33 pub fn load(&self) -> Result<Config, ConfigError> {
35 let file = fs::read_to_string(&self.path).context(NotLoadableSnafu {
36 path: self.path.clone(),
37 })?;
38 let config = toml::from_str(file.as_str()).context(NotReadableSnafu {
39 path: self.path.clone(),
40 })?;
41
42 Ok(config)
43 }
44
45 pub fn store(&self, config: &Config) -> Result<(), ConfigError> {
47 if let Some(config_dir) = self.path.parent() {
48 fs::create_dir_all(config_dir).context(FolderNotCreatableSnafu { path: config_dir })?;
49 }
50
51 let config_str = toml::to_string_pretty(config).context(NotWriteableSnafu {
52 path: self.path.clone(),
53 })?;
54
55 fs::write(&self.path, config_str).context(NotStorableSnafu {
56 path: self.path.clone(),
57 })?;
58
59 Ok(())
60 }
61}
62
63#[cfg(test)]
64mod tests {
65
66 use std::{collections::BTreeMap, io::Write as _, path::Path};
67
68 use pretty_assertions::{assert_eq, assert_matches};
69 use tempfile::tempdir;
70
71 use super::Config;
72 use crate::{
73 OpenTalkAccountConfig,
74 config_manager::{ConfigError, ConfigManager},
75 opentalk_instance_config::OpenTalkInstanceConfig,
76 };
77
78 const EXAMPLE_CONFIG: &str = r#"default_instance = "https://ot.example.com/"
79
80[instances."https://ot.example.com/"]
81default_account = "one"
82
83[instances."https://ot.example.com/".accounts.one]
84oidc_client_id = "device"
85
86[instances."https://ot.example.com/".accounts.two]
87oidc_client_id = "device"
88
89[instances."https://ot01.example.com/"]
90default_account = "three"
91
92[instances."https://ot01.example.com/".accounts.three]
93oidc_client_id = "device"
94"#;
95
96 fn example() -> Config {
97 Config {
98 default_instance: Some("https://ot.example.com".parse().unwrap()),
99 instances: BTreeMap::from_iter([
100 (
101 "https://ot.example.com".parse().unwrap(),
102 OpenTalkInstanceConfig {
103 default_account: "one".parse().unwrap(),
104 accounts: BTreeMap::from_iter([
105 (
106 "one".parse().unwrap(),
107 OpenTalkAccountConfig {
108 oidc_client_id: "device".to_string(),
109 },
110 ),
111 (
112 "two".parse().unwrap(),
113 OpenTalkAccountConfig {
114 oidc_client_id: "device".to_string(),
115 },
116 ),
117 ]),
118 },
119 ),
120 (
121 "https://ot01.example.com".parse().unwrap(),
122 OpenTalkInstanceConfig {
123 default_account: "three".parse().unwrap(),
124 accounts: BTreeMap::from_iter([(
125 "three".parse().unwrap(),
126 OpenTalkAccountConfig {
127 oidc_client_id: "device".to_string(),
128 },
129 )]),
130 },
131 ),
132 ]),
133 }
134 }
135
136 #[test]
137 fn success_load_with_empty_config() {
138 let temp_dir = tempdir().unwrap();
140
141 let config_path = temp_dir.path().join("opentalk-cli.toml");
142 {
143 let _ = std::fs::File::create(&config_path).unwrap();
144 }
145 let config_manager = ConfigManager {
146 path: config_path.clone(),
147 };
148 let config = config_manager.load().unwrap();
149
150 assert_eq!(Config::default(), config);
151 }
152
153 #[test]
154 fn error_load_with_missing_file() {
155 let temp_dir = tempdir().unwrap();
157
158 let config_path = temp_dir.path().join("opentalk-cli.toml");
159 let config_manager = ConfigManager {
160 path: config_path.clone(),
161 };
162 let config = config_manager.load();
163
164 assert_matches!(config, Err(ConfigError::NotLoadable { path, source: _ }) if path == config_path);
165 }
166
167 #[test]
168 fn success_load_example_config() {
169 let temp_dir = tempdir().unwrap();
171
172 let config_path = temp_dir.path().join("opentalk-cli.toml");
173 {
174 let mut file = std::fs::File::create(&config_path).unwrap();
175 write!(file, "{EXAMPLE_CONFIG}").unwrap()
176 }
177 let config_manager = ConfigManager {
178 path: config_path.clone(),
179 };
180 let config = config_manager.load().unwrap();
181
182 assert_eq!(example(), config);
183 }
184
185 #[test]
186 fn success_store_config() {
187 let config = example();
188
189 let temp_dir = tempdir().unwrap();
190 let config_path = temp_dir.path().join("config/opentalk-cli.toml");
191
192 let config_manager = ConfigManager {
193 path: config_path.clone(),
194 };
195
196 config_manager.store(&config).unwrap();
197
198 let stored_config = std::fs::read_to_string(&config_path).unwrap();
199
200 assert_eq!(EXAMPLE_CONFIG, stored_config);
201 }
202
203 #[test]
204 fn success_new() {
205 #[allow(unsafe_code)]
206 unsafe {
207 std::env::set_var("XDG_CONFIG_HOME", "/home/example/.config");
210 }
211 let config_manager = ConfigManager::new().unwrap();
212 assert_eq!(
213 Path::new("/home/example/.config/opentalk/cli.toml"),
214 config_manager.path,
215 );
216 }
217}