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: KonfigSerialization {
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}
52
53pub trait KonfigSerialization {
57 fn to_bytes(&self, format: &FormatHandlerEnum) -> Result<Vec<u8>, KonfigError>;
58 fn update_from_bytes(
59 &mut self,
60 bytes: &[u8],
61 format: &FormatHandlerEnum,
62 ) -> Result<(), KonfigError>;
63}
64
65impl<T: ?Sized> KonfigSerialization for T
67where
68 T: serde::Serialize + serde::de::DeserializeOwned
69{
70 fn to_bytes(&self, format: &FormatHandlerEnum) -> Result<Vec<u8>, KonfigError> {
71 format.marshal(self)
72 }
73
74 fn update_from_bytes(&mut self, bytes: &[u8], format: &FormatHandlerEnum) -> Result<(), KonfigError> {
75 let new_instance: T = match format {
76 FormatHandlerEnum::JSON(_) => {
77 serde_json::from_slice(bytes)
78 .map_err(|err| KonfigError::UnmarshalError(err.to_string()))?
79 },
80 FormatHandlerEnum::YAML(_) => {
81 serde_yaml::from_slice(bytes)
82 .map_err(|err| KonfigError::UnmarshalError(err.to_string()))?
83 },
84 FormatHandlerEnum::TOML(_) => {
85 let s = str::from_utf8(bytes)
86 .map_err(|err| KonfigError::UnmarshalError(err.to_string()))?;
87 toml::from_str(s)
88 .map_err(|err| KonfigError::UnmarshalError(err.to_string()))?
89 },
90 };
91
92 *self = new_instance;
93 Ok(())
94 }
95}
96
97struct SectionPtr {
99 ptr: NonNull<dyn KonfigSection>,
100}
101
102unsafe impl Send for SectionPtr {}
103unsafe impl Sync for SectionPtr {}
104
105impl SectionPtr {
106 fn new<T: KonfigSection + 'static>(section: &mut T) -> Self {
107 let ptr = unsafe { NonNull::new_unchecked(section as *mut T as *mut dyn KonfigSection) };
110 SectionPtr { ptr }
111 }
112
113 unsafe fn as_ref(&self) -> &dyn KonfigSection {
114 unsafe { self.ptr.as_ref() }
115 }
116
117 unsafe fn as_mut(&mut self) -> &mut dyn KonfigSection {
118 unsafe { self.ptr.as_mut() }
119 }
120}
121
122pub struct KonfigOptions {
124 pub format: Format,
126 pub auto_save: bool,
130 pub use_callbacks: bool,
132 pub config_path: String,
135}
136
137pub struct KonfigManager {
175 opts: KonfigOptions,
176 format_handler: FormatHandlerEnum,
177 path: Box<Path>,
178 sections: HashMap<String, SectionPtr>,
179}
180
181impl KonfigManager {
186 pub fn new(opts: KonfigOptions) -> Self {
188 let m = KonfigManager {
189 format_handler: opts.format.create_handler(),
190 path: Box::from(Path::new(&opts.config_path)),
191 opts,
192 sections: HashMap::new(),
193 };
194
195 if m.opts.auto_save {
197 }
209
210 m
211 }
212
213 pub fn load(&mut self) -> Result<(), KonfigError> {
217 if File::open(&self.path).is_err() {
218 File::create(&self.path).map_err(|err| KonfigError::LoadError(err.to_string()))?;
219 }
220 let data = read(&self.path).map_err(|err| KonfigError::LoadError(err.to_string()))?;
221
222 if data.is_empty() {
223 return Ok(());
224 }
225
226 let config: serde_json::Value = match &self.format_handler {
227 FormatHandlerEnum::JSON(handler) => handler.unmarshal(data.as_slice())?,
228 FormatHandlerEnum::YAML(handler) => handler.unmarshal(data.as_slice())?,
229 FormatHandlerEnum::TOML(handler) => handler.unmarshal(data.as_slice())?,
230 };
231
232 let config_map = config
233 .as_object()
234 .ok_or_else(|| KonfigError::LoadError("Config root must be an object".to_string()))?;
235
236 for (name, section_value) in config_map {
237 if let Some(section_ptr) = self.sections.get_mut(name) {
238 let bytes = self.format_handler.marshal(section_value)?;
239 unsafe {
240 let section = section_ptr.as_mut();
241 section.update_from_bytes(&bytes, &self.format_handler)?;
242 if self.opts.use_callbacks {
243 section.validate()?;
244 section.on_load()?;
245 }
246 }
247 }
248 }
249
250 Ok(())
251 }
252
253 pub fn save(&self) -> Result<(), KonfigError> {
264 let mut map: HashMap<String, serde_json::Value> = HashMap::new();
265
266 for (name, section_ptr) in &self.sections {
267 let section = unsafe { section_ptr.as_ref() };
268 let bytes = section.to_bytes(&self.format_handler)?;
269
270 let value: serde_json::Value = match &self.format_handler {
271 FormatHandlerEnum::JSON(_) => serde_json::from_slice(&bytes)
272 .map_err(|err| KonfigError::UnmarshalError(err.to_string()))?,
273 FormatHandlerEnum::YAML(_) => serde_yaml::from_slice(&bytes)
274 .map_err(|err| KonfigError::UnmarshalError(err.to_string()))?,
275 FormatHandlerEnum::TOML(_) => {
276 let s = str::from_utf8(&bytes)
277 .map_err(|err| KonfigError::UnmarshalError(err.to_string()))?;
278 toml::from_str(s).map_err(|err| KonfigError::UnmarshalError(err.to_string()))?
279 }
280 };
281
282 map.insert(name.clone(), value);
283 }
284
285 let out = self.format_handler.marshal(&map)?;
286
287 let mut f =
288 File::create(&self.path).map_err(|err| KonfigError::SaveError(err.to_string()))?;
289
290 f.write_all(out.as_slice())
291 .map_err(|err| KonfigError::SaveError(err.to_string()))?;
292
293 Ok(())
294 }
295
296 pub fn register_section<T>(&mut self, section: &mut T) -> Result<(), KonfigError>
301 where
302 T: KonfigSection + 'static,
303 {
304 let name = section.name().to_string();
305
306 if self.sections.contains_key(&name) {
307 return Err(KonfigError::RegistrationError(format!(
308 "Failed to register {}, section already registered",
309 name
310 )));
311 }
312
313 let section_ptr = SectionPtr::new(section);
314
315 self.sections.insert(name, section_ptr);
316 Ok(())
317 }
318
319 pub fn validate_all(&self) -> Vec<(String, Result<(), KonfigError>)> {
322 self.sections
323 .iter()
324 .map(|(name, section_ptr)| {
325 let result = unsafe { section_ptr.as_ref().validate() };
326 (name.clone(), result)
327 })
328 .collect()
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335 use konfig_rust_derive::KonfigSection;
336 use serde::{Deserialize, Serialize};
337
338 #[test]
339 fn test_konfig() {
340 #[derive(Serialize, Deserialize, KonfigSection)]
341 struct TestData {
342 a: i32,
343 b: String,
344 }
345
346 #[derive(Serialize, Deserialize, KonfigSection)]
347 struct TestData2 {
348 port: String,
349 host: String,
350 }
351
352 let mut t = TestData {
353 a: 1,
354 b: "test".to_string(),
355 };
356
357 let mut t2 = TestData2 {
358 port: "8080".to_string(),
359 host: "localhost".to_string(),
360 };
361
362 let mut mngr = KonfigManager::new(KonfigOptions {
363 format: Format::JSON,
364 auto_save: false,
365 use_callbacks: true,
366 config_path: "test.json".to_string(),
367 });
368
369 mngr.register_section(&mut t)
370 .map_err(|err| println!("{}", err.to_string()))
371 .unwrap();
372
373 mngr.register_section(&mut t2)
374 .map_err(|err| println!("{}", err.to_string()))
375 .unwrap();
376
377 mngr.load()
378 .map_err(|err| println!("{}", err.to_string()))
379 .unwrap();
380
381 t.a = t.a + 1;
382
383 mngr.save()
384 .map_err(|err| println!("{}", err.to_string()))
385 .unwrap();
386
387 for (name, result) in mngr.validate_all() {
388 println!("{}: {}", name, result.is_ok());
389 }
390 println!("TestData: {}, {}", t.a, t.b);
391 println!("TestData2: {}, {}", t2.port, t2.host);
392 }
393}