cfgloader/
lib.rs

1use std::fs;
2use std::io;
3use std::path::Path;
4use std::path::PathBuf;
5
6use serde::{de::DeserializeOwned, Serialize};
7use snafu::{ResultExt, Snafu};
8
9use polyglot::Format;
10
11#[derive(Debug, Snafu)]
12pub enum Error {
13    #[snafu(display("Unknown config directory"))]
14    UnknownConfigDirectory,
15
16    FailedToCreateConfigDir {
17        source: io::Error,
18    },
19
20    FailedToCreateDefaultConfigFile {
21        source: io::Error,
22    },
23
24    FailedToSerializeDefaultConfig {
25        source: polyglot::Error,
26    },
27
28    #[snafu(display("Failed to find config file"))]
29    FailedToFindConfigFile,
30
31    FailedToOpenConfigFile {
32        source: io::Error,
33    },
34
35    FailedToDeserializeConfigFile {
36        source: polyglot::Error,
37    },
38}
39
40type Result<T> = std::result::Result<T, Error>;
41
42fn deser<T: DeserializeOwned>(file: &Path, format: Format) -> Result<T> {
43    let f = fs::File::open(file).context(FailedToOpenConfigFile)?;
44    polyglot::from_reader(f, format).context(FailedToDeserializeConfigFile)
45}
46
47fn find_config_file(namespace: &str, name: &str) -> Result<Option<(PathBuf, Format)>> {
48    let config_dir = dirs::config_dir().ok_or(Error::UnknownConfigDirectory)?;
49    let file_path = config_dir.join(namespace).join(name);
50
51    let (ext, format) = if file_path.with_extension("toml").exists() {
52        ("toml", Format::TOML)
53    } else if file_path.with_extension("json").exists() {
54        ("json", Format::JSON)
55    } else if file_path.with_extension("yml").exists() {
56        ("yml", Format::YAML)
57    } else {
58        return Ok(None);
59    };
60
61    Ok(Some((file_path.with_extension(ext), format)))
62}
63
64pub fn load<T: DeserializeOwned>(namespace: &str, name: &str) -> Result<T> {
65    let (file_path, format) =
66        find_config_file(namespace, name)?.ok_or(Error::FailedToFindConfigFile)?;
67    deser(&file_path, format)
68}
69
70pub fn load_or_default<T: DeserializeOwned + Serialize>(
71    namespace: &str,
72    name: &str,
73    default: T,
74) -> Result<T> {
75    if let Some((file, format)) = find_config_file(namespace, name)? {
76        deser(&file, format)
77    } else {
78        // Create the default config.
79        let config_dir = dirs::config_dir().ok_or(Error::UnknownConfigDirectory)?;
80        let namespace_dir = config_dir.join(namespace);
81        fs::create_dir_all(&namespace_dir).context(FailedToCreateConfigDir)?;
82        let file_path = namespace_dir.join(name).with_extension("toml");
83
84        let f = fs::File::create(&file_path).context(FailedToCreateDefaultConfigFile)?;
85        polyglot::to_writer(f, &default, Format::TOML).context(FailedToSerializeDefaultConfig)?;
86
87        Ok(default)
88    }
89}