binconf/
json_conf.rs

1use crate::{ConfigError, ConfigLocation, ConfigType};
2use std::{fs::read_to_string, io::Write};
3
4/// Loads a config file from the config, cache, cwd, or local data directory of the current user. In `json` format.
5///
6/// It will load a config file, deserialize it and return it.
7///
8/// If the flag `reset_conf_on_err` is set to `true`, the config file will be reset to the default config if
9/// the deserialization fails, if set to `false` an error will be returned.
10///
11/// # Errors
12///
13/// This function will return an error if the config, cache or local data directory could not be found or created, or if something went wrong while deserializing the config.
14///
15/// If the flag `reset_conf_on_err` is set to `false` and the deserialization fails, an error will be returned. If it is set to `true` the config file will be reset to the default config.
16///
17/// # Example
18///
19/// ```
20/// use binconf::ConfigLocation::{Cache, Config, LocalData, Cwd};
21/// use serde::{Deserialize, Serialize};
22///
23/// #[derive(Default, Serialize, Deserialize, PartialEq, Debug)]
24/// struct TestConfig {
25///    test: String,
26///    test_vec: Vec<u8>,
27/// }
28///
29/// let config = binconf::load_json::<TestConfig>("test-binconf-read-json", None, Config, false).unwrap();
30/// assert_eq!(config, TestConfig::default());
31/// ```
32pub fn load_json<'a, T>(
33    app_name: impl AsRef<str>,
34    config_name: impl Into<Option<&'a str>>,
35    location: impl AsRef<ConfigLocation>,
36    reset_conf_on_err: bool,
37) -> Result<T, ConfigError>
38where
39    T: Default + serde::Serialize + serde::de::DeserializeOwned,
40{
41    let config_file_path = crate::config_location(
42        app_name.as_ref(),
43        config_name.into(),
44        ConfigType::Json.as_str(),
45        location.as_ref(),
46    )?;
47
48    let save_default_conf = || {
49        let default_config = T::default();
50        let json_str = serde_json::to_string_pretty(&default_config)?;
51        crate::save_config_str(&config_file_path, &json_str)?;
52        Ok(default_config)
53    };
54
55    if !config_file_path.try_exists()? {
56        return save_default_conf();
57    }
58
59    let json_str = read_to_string(&config_file_path)?;
60    let config = match serde_json::from_str::<T>(&json_str) {
61        Ok(config) => config,
62        Err(err) => {
63            if reset_conf_on_err {
64                return save_default_conf();
65            }
66            return Err(err.into());
67        }
68    };
69
70    Ok(config)
71}
72
73/// Stores a config file in the config, cache, cwd, or local data directory of the current user. In `json` format.
74///
75/// It will store a config file, serializing it with the `serde_json` crate.
76///
77/// # Errors
78///
79/// This function will return an error if the config, cache or local data directory could not be found or created, or if something went wrong while serializing the config.
80///
81/// # Example
82///
83/// ```
84/// use binconf::ConfigLocation::{Cache, Config, LocalData, Cwd};
85/// use serde::{Deserialize, Serialize};
86///
87/// #[derive(Default, Serialize, Deserialize, PartialEq, Debug)]
88/// struct TestConfig {
89///   test: String,
90///   test_vec: Vec<u8>,
91/// }
92///
93/// let test_config = TestConfig {
94///  test: String::from("test-json"),
95///  test_vec: vec![1, 2, 3, 4, 5],
96/// };
97///
98/// binconf::store_json("test-binconf-store-json", None, Config, &test_config).unwrap();
99///
100/// let config = binconf::load_json::<TestConfig>("test-binconf-store-json", None, Config, false).unwrap();
101/// assert_eq!(config, test_config);
102/// ```
103pub fn store_json<'a, T>(
104    app_name: impl AsRef<str>,
105    config_name: impl Into<Option<&'a str>>,
106    location: impl AsRef<ConfigLocation>,
107    data: T,
108) -> Result<(), ConfigError>
109where
110    T: serde::Serialize,
111{
112    let config_file_path = crate::config_location(
113        app_name.as_ref(),
114        config_name.into(),
115        ConfigType::Json.as_str(),
116        location.as_ref(),
117    )?;
118
119    let mut file = std::io::BufWriter::new(std::fs::File::create(config_file_path)?);
120
121    let json_str = serde_json::to_string_pretty(&data)?;
122
123    file.write_all(json_str.as_bytes())?;
124
125    Ok(())
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131
132    use serde::Deserialize;
133    use ConfigLocation::{Cache, Config, Cwd, LocalData};
134
135    #[derive(Default, serde::Serialize, Deserialize, PartialEq, Debug, Clone)]
136    struct TestConfig {
137        test: String,
138        test_vec: Vec<u8>,
139    }
140
141    #[test]
142    fn read_default_config_json() {
143        let config = load_json::<TestConfig>(
144            "test-binconf-read_default_config-string-json",
145            None,
146            Config,
147            false,
148        )
149        .unwrap();
150        assert_eq!(config, TestConfig::default());
151
152        let test_config = TestConfig {
153            test: String::from("test"),
154            test_vec: vec![1, 2, 3, 4, 5],
155        };
156
157        let config: TestConfig = load_json(
158            "test-binconf-read_default_config-struct-json",
159            None,
160            Config,
161            false,
162        )
163        .unwrap();
164        assert_eq!(config, TestConfig::default());
165
166        store_json(
167            "test-binconf-read_default_config-struct-json",
168            None,
169            Config,
170            &test_config,
171        )
172        .unwrap();
173        let config: TestConfig = load_json(
174            "test-binconf-read_default_config-struct-json",
175            None,
176            Config,
177            false,
178        )
179        .unwrap();
180        assert_eq!(config, test_config);
181    }
182
183    #[test]
184    fn config_with_name_json() {
185        let config = load_json::<TestConfig>(
186            "test-binconf-config_with_name-string-json",
187            Some("test-config.json"),
188            Config,
189            false,
190        )
191        .unwrap();
192        assert_eq!(config, TestConfig::default());
193
194        let test_config = TestConfig {
195            test: String::from("test"),
196            test_vec: vec![1, 2, 3, 4, 5],
197        };
198
199        let config: TestConfig = load_json(
200            "test-binconf-config_with_name-struct-json",
201            Some("test-config.json"),
202            Config,
203            false,
204        )
205        .unwrap();
206        assert_eq!(config, TestConfig::default());
207
208        store_json(
209            "test-binconf-config_with_name-struct-json",
210            Some("test-config.json"),
211            Config,
212            &test_config,
213        )
214        .unwrap();
215        let config: TestConfig = load_json(
216            "test-binconf-config_with_name-struct-json",
217            Some("test-config.json"),
218            Config,
219            false,
220        )
221        .unwrap();
222        assert_eq!(config, test_config);
223    }
224
225    #[test]
226    fn returns_error_on_invalid_config_json() {
227        let data = TestConfig {
228            test: String::from("test"),
229            test_vec: vec![1, 2, 3, 4, 5],
230        };
231
232        store_json(
233            "test-binconf-returns_error_on_invalid_config-json",
234            None,
235            Config,
236            &data,
237        )
238        .unwrap();
239        let config = load_json::<String>(
240            "test-binconf-returns_error_on_invalid_config-json",
241            None,
242            Config,
243            false,
244        );
245
246        assert!(config.is_err());
247    }
248
249    #[test]
250    fn save_config_user_config_json() {
251        let data = TestConfig {
252            test: String::from("test"),
253            test_vec: vec![1, 2, 3, 4, 5],
254        };
255
256        store_json(
257            "test-binconf-save_config_user_config-json",
258            None,
259            Config,
260            &data,
261        )
262        .unwrap();
263        let config: TestConfig = load_json(
264            "test-binconf-save_config_user_config-json",
265            None,
266            Config,
267            false,
268        )
269        .unwrap();
270        assert_eq!(config, data);
271    }
272
273    #[test]
274    fn save_config_user_cache_json() {
275        let data = TestConfig {
276            test: String::from("test"),
277            test_vec: vec![1, 2, 3, 4, 5],
278        };
279
280        store_json(
281            "test-binconf-save_config_user_cache-json",
282            None,
283            Cache,
284            &data,
285        )
286        .unwrap();
287        let config: TestConfig = load_json(
288            "test-binconf-save_config_user_cache-json",
289            None,
290            Cache,
291            false,
292        )
293        .unwrap();
294        assert_eq!(config, data);
295    }
296
297    #[test]
298    fn save_config_user_local_data_json() {
299        let data = TestConfig {
300            test: String::from("test"),
301            test_vec: vec![1, 2, 3, 4, 5],
302        };
303
304        store_json(
305            "test-binconf-save_config_user_local_data-json",
306            None,
307            LocalData,
308            &data,
309        )
310        .unwrap();
311        let config: TestConfig = load_json(
312            "test-binconf-save_config_user_local_data-json",
313            None,
314            LocalData,
315            false,
316        )
317        .unwrap();
318        assert_eq!(config, data);
319    }
320
321    #[test]
322    fn save_config_user_cwd_json() {
323        let data = TestConfig {
324            test: String::from("test"),
325            test_vec: vec![1, 2, 3, 4, 5],
326        };
327
328        store_json("test-binconf-save_config_user_cwd-json", None, Cwd, &data).unwrap();
329        let config: TestConfig =
330            load_json("test-binconf-save_config_user_cwd-json", None, Cwd, false).unwrap();
331        assert_eq!(config, data);
332    }
333}