dusk_cdf/
config.rs

1use core::mem;
2use std::path::{Path, PathBuf};
3use std::{fs, io};
4
5use serde::{Deserialize, Serialize};
6
7use crate::{
8    DecodableElement, DecoderContext, Element, EncodableElement, EncoderContext, Preamble,
9};
10
11/// Base configuration schema
12pub trait BaseConfig: Sized + Default + Serialize + for<'a> Deserialize<'a> {
13    /// Package name (e.g. `CARGO_PKG_NAME`)
14    const PACKAGE: &'static str;
15
16    /// Path of the serialized configuration
17    fn path() -> Option<PathBuf> {
18        dirs::config_dir()
19            .map(|p| p.join(Self::PACKAGE))
20            .map(|p| p.join("config.toml"))
21    }
22
23    /// Load a config instance from the config dir
24    fn load() -> io::Result<Self> {
25        Self::path()
26            .ok_or_else(|| {
27                io::Error::new(io::ErrorKind::Other, "unable to define configuration path")
28            })
29            .and_then(Self::load_path)
30    }
31
32    /// Load a config file from a given path
33    fn load_path<P>(path: P) -> io::Result<Self>
34    where
35        P: AsRef<Path>,
36    {
37        let path = path.as_ref();
38
39        if !path.exists() {
40            let config = Self::default();
41
42            // config serialization is optional
43            path.parent()
44                .ok_or_else(|| {
45                    io::Error::new(
46                        io::ErrorKind::Other,
47                        "unable to fetch parent dir of config file",
48                    )
49                })
50                .and_then(fs::create_dir_all)
51                .and_then(|_| {
52                    toml::to_string_pretty(&config)
53                        .map_err(|e| io::Error::new(io::ErrorKind::Other, e))
54                })
55                .and_then(|contents| fs::write(path, contents))
56                .unwrap_or_else(|e| eprintln!("failed to serialize config file: {}", e));
57
58            return Ok(config);
59        }
60
61        let contents = fs::read_to_string(path)?;
62
63        toml::from_str(&contents).map_err(|e| io::Error::new(io::ErrorKind::Other, e))
64    }
65}
66
67/// Configuration parameters for encoding and decoding
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
69pub struct Config {
70    /// Flag to zero skip scalar values during encoding, and zero them during decoding
71    pub zeroed_scalar_values: bool,
72}
73
74impl Default for Config {
75    fn default() -> Self {
76        Self::DEFAULT
77    }
78}
79
80impl Config {
81    /// Serialized length
82    pub const LEN: usize = mem::size_of::<bool>();
83
84    /// Default value as constant
85    pub const DEFAULT: Self = Self {
86        zeroed_scalar_values: false,
87    };
88
89    /// Set the flag to cache the source path
90    pub fn with_zeroed_scalar_values(&mut self, zeroed_scalar_values: bool) -> &mut Self {
91        self.zeroed_scalar_values = zeroed_scalar_values;
92        self
93    }
94}
95
96impl BaseConfig for Config {
97    const PACKAGE: &'static str = env!("CARGO_PKG_NAME");
98}
99
100impl Element for Config {
101    fn len(ctx: &Config) -> usize {
102        bool::len(ctx)
103    }
104
105    fn validate(&self, preamble: &Preamble) -> io::Result<()> {
106        self.zeroed_scalar_values.validate(preamble)?;
107
108        Ok(())
109    }
110}
111
112impl EncodableElement for Config {
113    fn to_buffer(&self, ctx: &mut EncoderContext, buf: &mut [u8]) {
114        let _ = self.zeroed_scalar_values.encode(ctx, buf);
115    }
116}
117
118impl DecodableElement for Config {
119    fn try_from_buffer_in_place<'a, 'b>(
120        &'a mut self,
121        ctx: &DecoderContext<'a>,
122        buf: &'b [u8],
123    ) -> io::Result<()> {
124        Self::validate_buffer(ctx.config(), buf)?;
125
126        let _ = self.zeroed_scalar_values.try_decode_in_place(ctx, buf)?;
127
128        Ok(())
129    }
130}
131
132#[test]
133fn base_config_load_works() {
134    let dir = tempdir::TempDir::new("base_config").expect("failed to create temp dir");
135    let path = dir.path().join("config.toml");
136
137    let config = Config::load_path(&path).expect("failed to load config from path");
138
139    assert_eq!(config, Config::default());
140
141    let config = Config::load_path(&path).expect("failed to read config from path");
142    let c = *Config::default().with_zeroed_scalar_values(Config::default().zeroed_scalar_values);
143
144    assert_eq!(config, c);
145
146    Config::load().expect("failed to load default config");
147}