toml_base_config/
lib.rs

1//! Shared configuration behaviour for projects. Using a config.toml file
2use std::path::{Path, PathBuf};
3use std::{fs, io};
4
5use serde::{Deserialize, Serialize};
6/// Base configuration schema. Maintains a config.toml for configs. Handles the
7/// reading/writing of config.toml. The config file is stored in the
8/// [`dirs::config_dir()`] and the full relative path can be obtained via
9/// [`BaseConfig::path`].
10pub trait BaseConfig: Sized + Default + Serialize + for<'a> Deserialize<'a> {
11    /// The Package name is usually `env!("CARGO_PKG_NAME")`. This is the name
12    /// of the folder inside the config dir. We store the config.toml inside
13    /// this folder.
14    ///
15    /// Calling [`BaseConfig::load`] will create the config file at
16    /// [`BaseConfig::path`].
17    const PACKAGE: &'static str;
18
19    /// Compute path for the `config.toml`.
20    ///
21    /// # Example
22    ///
23    /// ```
24    /// use serde::{Deserialize, Serialize};
25    /// use dusk_cdf::BaseConfig;
26    /// use std::path::{Path, Component};
27    /// use std::ffi::OsStr;
28    ///
29    /// #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
30    /// struct T  {}
31    ///
32    /// impl Default for T {
33    ///     fn default() -> Self {
34    ///         T {}
35    ///     }
36    /// }
37    ///
38    /// impl BaseConfig for T {
39    ///     const PACKAGE: &'static str = "Lisa";    
40    /// }
41    ///
42    /// let path = T::path().unwrap();
43    /// let mut components = path.components().rev(); // look at the last parts
44    ///
45    /// assert_eq!(components.next(), Some(Component::Normal(OsStr::new("config.toml"))));
46    /// assert_eq!(components.next(), Some(Component::Normal(OsStr::new("Lisa"))));
47    /// ```
48    fn path() -> Option<PathBuf> {
49        dirs::config_dir()
50            .map(|p| p.join(Self::PACKAGE))
51            .map(|p| p.join("config.toml"))
52    }
53
54    /// Serialize the toml if config.toml exists, else create a config.toml at
55    /// the path returned by [`BaseConfig::path`].
56    ///
57    /// The contents for the created config.toml is obtained by
58    /// `BaseConfig::default()` implementation
59    fn load() -> io::Result<Self> {
60        Self::path()
61            .ok_or_else(|| {
62                io::Error::new(io::ErrorKind::Other, "unable to define configuration path")
63            })
64            .and_then(Self::load_path)
65    }
66
67    /// Load a config file from a given path
68    fn load_path<P>(path: P) -> io::Result<Self>
69    where
70        P: AsRef<Path>,
71    {
72        let path = path.as_ref();
73
74        if !path.exists() {
75            let config = Self::default();
76
77            // config serialization is optional
78            path.parent()
79                .ok_or_else(|| {
80                    io::Error::new(
81                        io::ErrorKind::Other,
82                        "unable to fetch parent dir of config file",
83                    )
84                })
85                .and_then(fs::create_dir_all)
86                .and_then(|_| {
87                    toml::to_string_pretty(&config)
88                        .map_err(|e| io::Error::new(io::ErrorKind::Other, e))
89                })
90                .and_then(|contents| fs::write(path, contents))
91                .unwrap_or_else(|e| eprintln!("failed to serialize config file: {}", e));
92
93            return Ok(config);
94        }
95
96        let contents = fs::read_to_string(path)?;
97
98        toml::from_str(&contents).map_err(|e| io::Error::new(io::ErrorKind::Other, e))
99    }
100}