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
11pub trait BaseConfig: Sized + Default + Serialize + for<'a> Deserialize<'a> {
13 const PACKAGE: &'static str;
15
16 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 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 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
69pub struct Config {
70 pub zeroed_scalar_values: bool,
72}
73
74impl Default for Config {
75 fn default() -> Self {
76 Self::DEFAULT
77 }
78}
79
80impl Config {
81 pub const LEN: usize = mem::size_of::<bool>();
83
84 pub const DEFAULT: Self = Self {
86 zeroed_scalar_values: false,
87 };
88
89 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}