pub mod format;
use crate::format::*;
use std::borrow::Cow;
use std::collections::HashMap;
use std::error::Error;
use std::fs::{read, File};
use std::io::Write;
use std::path::Path;
use std::ptr::NonNull;
use std::str;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum KonfigError {
#[error("Validation callback error {0}")]
ValidationError(String),
#[error("OnLoad callback error {0}")]
OnLoadError(String),
#[error("Marshal error {0}")]
MarshalError(String),
#[error("Unmarshal error {0}")]
UnmarshalError(String),
#[error("Load error {0}")]
LoadError(String),
#[error("Save error {0}")]
SaveError(String),
#[error("Registration error {0}")]
RegistrationError(String),
#[error(transparent)]
Other(#[from] Box<dyn Error>),
}
pub trait KonfigSection: KonfigSerialization {
fn name(&self) -> Cow<'_, str>;
fn validate(&self) -> Result<(), KonfigError> {
Ok(())
}
fn on_load(&self) -> Result<(), KonfigError> {
Ok(())
}
}
pub trait KonfigSerialization {
fn to_bytes(&self, format: &FormatHandlerEnum) -> Result<Vec<u8>, KonfigError>;
fn update_from_bytes(
&mut self,
bytes: &[u8],
format: &FormatHandlerEnum,
) -> Result<(), KonfigError>;
}
impl<T: ?Sized> KonfigSerialization for T
where
T: serde::Serialize + serde::de::DeserializeOwned
{
fn to_bytes(&self, format: &FormatHandlerEnum) -> Result<Vec<u8>, KonfigError> {
format.marshal(self)
}
fn update_from_bytes(&mut self, bytes: &[u8], format: &FormatHandlerEnum) -> Result<(), KonfigError> {
let new_instance: T = match format {
FormatHandlerEnum::JSON(_) => {
serde_json::from_slice(bytes)
.map_err(|err| KonfigError::UnmarshalError(err.to_string()))?
},
FormatHandlerEnum::YAML(_) => {
serde_yaml::from_slice(bytes)
.map_err(|err| KonfigError::UnmarshalError(err.to_string()))?
},
FormatHandlerEnum::TOML(_) => {
let s = str::from_utf8(bytes)
.map_err(|err| KonfigError::UnmarshalError(err.to_string()))?;
toml::from_str(s)
.map_err(|err| KonfigError::UnmarshalError(err.to_string()))?
},
};
*self = new_instance;
Ok(())
}
}
struct SectionPtr {
ptr: NonNull<dyn KonfigSection>,
}
unsafe impl Send for SectionPtr {}
unsafe impl Sync for SectionPtr {}
impl SectionPtr {
fn new<T: KonfigSection + 'static>(section: &mut T) -> Self {
let ptr = unsafe { NonNull::new_unchecked(section as *mut T as *mut dyn KonfigSection) };
SectionPtr { ptr }
}
unsafe fn as_ref(&self) -> &dyn KonfigSection {
unsafe { self.ptr.as_ref() }
}
unsafe fn as_mut(&mut self) -> &mut dyn KonfigSection {
unsafe { self.ptr.as_mut() }
}
}
pub struct KonfigOptions {
pub format: Format,
pub auto_save: bool,
pub use_callbacks: bool,
pub config_path: String,
}
pub struct KonfigManager {
opts: KonfigOptions,
format_handler: FormatHandlerEnum,
path: Box<Path>,
sections: HashMap<String, SectionPtr>,
}
impl KonfigManager {
pub fn new(opts: KonfigOptions) -> Self {
let m = KonfigManager {
format_handler: opts.format.create_handler(),
path: Box::from(Path::new(&opts.config_path)),
opts,
sections: HashMap::new(),
};
if m.opts.auto_save {
}
m
}
pub fn load(&mut self) -> Result<(), KonfigError> {
if File::open(&self.path).is_err() {
File::create(&self.path).map_err(|err| KonfigError::LoadError(err.to_string()))?;
}
let data = read(&self.path).map_err(|err| KonfigError::LoadError(err.to_string()))?;
if data.is_empty() {
return Ok(());
}
let config: serde_json::Value = match &self.format_handler {
FormatHandlerEnum::JSON(handler) => handler.unmarshal(data.as_slice())?,
FormatHandlerEnum::YAML(handler) => handler.unmarshal(data.as_slice())?,
FormatHandlerEnum::TOML(handler) => handler.unmarshal(data.as_slice())?,
};
let config_map = config
.as_object()
.ok_or_else(|| KonfigError::LoadError("Config root must be an object".to_string()))?;
for (name, section_value) in config_map {
if let Some(section_ptr) = self.sections.get_mut(name) {
let bytes = self.format_handler.marshal(section_value)?;
unsafe {
let section = section_ptr.as_mut();
section.update_from_bytes(&bytes, &self.format_handler)?;
if self.opts.use_callbacks {
section.validate()?;
section.on_load()?;
}
}
}
}
Ok(())
}
pub fn save(&self) -> Result<(), KonfigError> {
let mut map: HashMap<String, serde_json::Value> = HashMap::new();
for (name, section_ptr) in &self.sections {
let section = unsafe { section_ptr.as_ref() };
let bytes = section.to_bytes(&self.format_handler)?;
let value: serde_json::Value = match &self.format_handler {
FormatHandlerEnum::JSON(_) => serde_json::from_slice(&bytes)
.map_err(|err| KonfigError::UnmarshalError(err.to_string()))?,
FormatHandlerEnum::YAML(_) => serde_yaml::from_slice(&bytes)
.map_err(|err| KonfigError::UnmarshalError(err.to_string()))?,
FormatHandlerEnum::TOML(_) => {
let s = str::from_utf8(&bytes)
.map_err(|err| KonfigError::UnmarshalError(err.to_string()))?;
toml::from_str(s).map_err(|err| KonfigError::UnmarshalError(err.to_string()))?
}
};
map.insert(name.clone(), value);
}
let out = self.format_handler.marshal(&map)?;
let mut f =
File::create(&self.path).map_err(|err| KonfigError::SaveError(err.to_string()))?;
f.write_all(out.as_slice())
.map_err(|err| KonfigError::SaveError(err.to_string()))?;
Ok(())
}
pub fn register_section<T>(&mut self, section: &mut T) -> Result<(), KonfigError>
where
T: KonfigSection + 'static,
{
let name = section.name().to_string();
if self.sections.contains_key(&name) {
return Err(KonfigError::RegistrationError(format!(
"Failed to register {}, section already registered",
name
)));
}
let section_ptr = SectionPtr::new(section);
self.sections.insert(name, section_ptr);
Ok(())
}
pub fn validate_all(&self) -> Vec<(String, Result<(), KonfigError>)> {
self.sections
.iter()
.map(|(name, section_ptr)| {
let result = unsafe { section_ptr.as_ref().validate() };
(name.clone(), result)
})
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use konfig_rust_derive::KonfigSection;
use serde::{Deserialize, Serialize};
#[test]
fn test_konfig() {
#[derive(Serialize, Deserialize, KonfigSection)]
struct TestData {
a: i32,
b: String,
}
#[derive(Serialize, Deserialize, KonfigSection)]
struct TestData2 {
port: String,
host: String,
}
let mut t = TestData {
a: 1,
b: "test".to_string(),
};
let mut t2 = TestData2 {
port: "8080".to_string(),
host: "localhost".to_string(),
};
let mut mngr = KonfigManager::new(KonfigOptions {
format: Format::JSON,
auto_save: false,
use_callbacks: true,
config_path: "test.json".to_string(),
});
mngr.register_section(&mut t)
.map_err(|err| println!("{}", err.to_string()))
.unwrap();
mngr.register_section(&mut t2)
.map_err(|err| println!("{}", err.to_string()))
.unwrap();
mngr.load()
.map_err(|err| println!("{}", err.to_string()))
.unwrap();
t.a = t.a + 1;
mngr.save()
.map_err(|err| println!("{}", err.to_string()))
.unwrap();
for (name, result) in mngr.validate_all() {
println!("{}: {}", name, result.is_ok());
}
println!("TestData: {}, {}", t.a, t.b);
println!("TestData2: {}, {}", t2.port, t2.host);
}
}