1pub mod format;
2
3use crate::format::*;
4use std::borrow::Cow;
5use std::collections::HashMap;
6use std::error::Error;
7use std::fs::{read, File};
8use std::io::Write;
9use std::path::Path;
10use std::ptr::NonNull;
11use std::str;
12use thiserror::Error;
13
14#[derive(Debug, Error)]
18pub enum KonfigError {
19 #[error("Validation callback error {0}")]
20 ValidationError(String),
21 #[error("OnLoad callback error {0}")]
22 OnLoadError(String),
23 #[error("Marshal error {0}")]
24 MarshalError(String),
25 #[error("Unmarshal error {0}")]
26 UnmarshalError(String),
27 #[error("Load error {0}")]
28 LoadError(String),
29 #[error("Save error {0}")]
30 SaveError(String),
31 #[error("Registration error {0}")]
32 RegistrationError(String),
33 #[error(transparent)]
34 Other(#[from] Box<dyn Error>),
35}
36
37pub trait KonfigSection {
44 fn name(&self) -> Cow<'_, str>;
45 fn validate(&self) -> Result<(), KonfigError> {
46 Ok(())
47 }
48 fn on_load(&self) -> Result<(), KonfigError> {
49 Ok(())
50 }
51 fn to_bytes(&self, format: &FormatHandlerEnum) -> Result<Vec<u8>, KonfigError>;
52 fn update_from_bytes(
53 &mut self,
54 bytes: &[u8],
55 format: &FormatHandlerEnum,
56 ) -> Result<(), KonfigError>;
57}
58
59struct SectionPtr {
61 ptr: NonNull<dyn KonfigSection>,
62}
63
64unsafe impl Send for SectionPtr {}
65unsafe impl Sync for SectionPtr {}
66
67impl SectionPtr {
68 fn new<T: KonfigSection + 'static>(section: &mut T) -> Self {
69 let ptr = unsafe { NonNull::new_unchecked(section as *mut T as *mut dyn KonfigSection) };
72 SectionPtr { ptr }
73 }
74
75 unsafe fn as_ref(&self) -> &dyn KonfigSection {
76 unsafe { self.ptr.as_ref() }
77 }
78
79 unsafe fn as_mut(&mut self) -> &mut dyn KonfigSection {
80 unsafe { self.ptr.as_mut() }
81 }
82}
83
84pub struct KonfigOptions {
86 pub format: Format,
88 pub auto_save: bool,
92 pub use_callbacks: bool,
94 pub config_path: String,
97}
98
99pub struct KonfigManager {
137 opts: KonfigOptions,
138 format_handler: FormatHandlerEnum,
139 path: Box<Path>,
140 sections: HashMap<String, SectionPtr>,
141}
142
143impl KonfigManager {
148 pub fn new(opts: KonfigOptions) -> Self {
150 let m = KonfigManager {
151 format_handler: opts.format.create_handler(),
152 path: Box::from(Path::new(&opts.config_path)),
153 opts,
154 sections: HashMap::new(),
155 };
156
157 if m.opts.auto_save {
159 }
171
172 m
173 }
174
175 pub fn load(&mut self) -> Result<(), KonfigError> {
179 if File::open(&self.path).is_err() {
180 File::create(&self.path).map_err(|err| KonfigError::LoadError(err.to_string()))?;
181 }
182 let data = read(&self.path).map_err(|err| KonfigError::LoadError(err.to_string()))?;
183
184 if data.is_empty() {
185 return Ok(());
186 }
187
188 let config: serde_json::Value = match &self.format_handler {
189 FormatHandlerEnum::JSON(handler) => handler.unmarshal(data.as_slice())?,
190 FormatHandlerEnum::YAML(handler) => handler.unmarshal(data.as_slice())?,
191 FormatHandlerEnum::TOML(handler) => handler.unmarshal(data.as_slice())?,
192 };
193
194 let config_map = config
195 .as_object()
196 .ok_or_else(|| KonfigError::LoadError("Config root must be an object".to_string()))?;
197
198 for (name, section_value) in config_map {
199 if let Some(section_ptr) = self.sections.get_mut(name) {
200 let bytes = self.format_handler.marshal(section_value)?;
201 unsafe {
202 let section = section_ptr.as_mut();
203 section.update_from_bytes(&bytes, &self.format_handler)?;
204 if self.opts.use_callbacks {
205 section.validate()?;
206 section.on_load()?;
207 }
208 }
209 }
210 }
211
212 Ok(())
213 }
214
215 pub fn save(&self) -> Result<(), KonfigError> {
226 let mut map: HashMap<String, serde_json::Value> = HashMap::new();
227
228 for (name, section_ptr) in &self.sections {
229 let section = unsafe { section_ptr.as_ref() };
230 let bytes = section.to_bytes(&self.format_handler)?;
231
232 let value: serde_json::Value = match &self.format_handler {
233 FormatHandlerEnum::JSON(_) => serde_json::from_slice(&bytes)
234 .map_err(|err| KonfigError::UnmarshalError(err.to_string()))?,
235 FormatHandlerEnum::YAML(_) => serde_yaml::from_slice(&bytes)
236 .map_err(|err| KonfigError::UnmarshalError(err.to_string()))?,
237 FormatHandlerEnum::TOML(_) => {
238 let s = str::from_utf8(&bytes)
239 .map_err(|err| KonfigError::UnmarshalError(err.to_string()))?;
240 toml::from_str(s).map_err(|err| KonfigError::UnmarshalError(err.to_string()))?
241 }
242 };
243
244 map.insert(name.clone(), value);
245 }
246
247 let out = self.format_handler.marshal(&map)?;
248
249 let mut f =
250 File::create(&self.path).map_err(|err| KonfigError::SaveError(err.to_string()))?;
251
252 f.write_all(out.as_slice())
253 .map_err(|err| KonfigError::SaveError(err.to_string()))?;
254
255 Ok(())
256 }
257
258 pub fn register_section<T>(&mut self, section: &mut T) -> Result<(), KonfigError>
263 where
264 T: KonfigSection + 'static,
265 {
266 let name = section.name().to_string();
267
268 if self.sections.contains_key(&name) {
269 return Err(KonfigError::RegistrationError(format!(
270 "Failed to register {}, section already registered",
271 name
272 )));
273 }
274
275 let section_ptr = SectionPtr::new(section);
276
277 self.sections.insert(name, section_ptr);
278 Ok(())
279 }
280
281 pub fn validate_all(&self) -> Vec<(String, Result<(), KonfigError>)> {
284 self.sections
285 .iter()
286 .map(|(name, section_ptr)| {
287 let result = unsafe { section_ptr.as_ref().validate() };
288 (name.clone(), result)
289 })
290 .collect()
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297 use konfig_rust_derive::KonfigSection;
298 use serde::{Deserialize, Serialize};
299
300 #[test]
301 fn test_konfig() {
302 #[derive(Serialize, Deserialize, KonfigSection)]
303 struct TestData {
304 a: i32,
305 b: String,
306 }
307
308 #[derive(Serialize, Deserialize, KonfigSection)]
309 struct TestData2 {
310 port: String,
311 host: String,
312 }
313
314 let mut t = TestData {
315 a: 1,
316 b: "test".to_string(),
317 };
318
319 let mut t2 = TestData2 {
320 port: "8080".to_string(),
321 host: "localhost".to_string(),
322 };
323
324 let mut mngr = KonfigManager::new(KonfigOptions {
325 format: Format::JSON,
326 auto_save: false,
327 use_callbacks: true,
328 config_path: "test.json".to_string(),
329 });
330
331 mngr.register_section(&mut t)
332 .map_err(|err| println!("{}", err.to_string()))
333 .unwrap();
334
335 mngr.register_section(&mut t2)
336 .map_err(|err| println!("{}", err.to_string()))
337 .unwrap();
338
339 mngr.load()
340 .map_err(|err| println!("{}", err.to_string()))
341 .unwrap();
342
343 t.a = t.a + 1;
344
345 mngr.save()
346 .map_err(|err| println!("{}", err.to_string()))
347 .unwrap();
348
349 for (name, result) in mngr.validate_all() {
350 println!("{}: {}", name, result.is_ok());
351 }
352 println!("TestData: {}, {}", t.a, t.b);
353 println!("TestData2: {}, {}", t2.port, t2.host);
354 }
355}