Break config-traits out in to crate

This commit is contained in:
Luke D. Jones
2023-01-07 20:46:00 +13:00
parent ea5e5db490
commit 90b711c7b9
26 changed files with 161 additions and 110 deletions

14
config-traits/Cargo.toml Normal file
View File

@@ -0,0 +1,14 @@
[package]
name = "config-traits"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true
ron.workspace = true
log.workspace = true

191
config-traits/src/lib.rs Normal file
View File

@@ -0,0 +1,191 @@
use std::fs::{create_dir, File, OpenOptions};
use std::io::{Read, Write};
use std::path::PathBuf;
use log::{error, warn};
use ron::ser::PrettyConfig;
use serde::de::DeserializeOwned;
use serde::Serialize;
/// Config file helper traits. Only `new()` and `file_name()` are required to be
/// implemented, the rest are intended to be free methods.
pub trait StdConfig
where
Self: Serialize + DeserializeOwned,
{
fn new() -> Self;
fn file_name() -> &'static str;
fn config_dir() -> PathBuf;
fn file_path() -> PathBuf {
let mut config = Self::config_dir();
if !config.exists() {
create_dir(config.as_path())
.unwrap_or_else(|e| panic!("Could not create {:?} {e}", Self::config_dir()));
}
config.push(Self::file_name());
config
}
fn file_open() -> File {
OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(Self::file_path())
.unwrap_or_else(|e| panic!("Could not open {:?} {e}", Self::file_path()))
}
fn read(&mut self) {
let mut file = match OpenOptions::new().read(true).open(Self::file_path()) {
Ok(data) => data,
Err(err) => {
error!("Error reading {:?}: {}", Self::file_path(), err);
return;
}
};
let mut buf = String::new();
if let Ok(l) = file.read_to_string(&mut buf) {
if l == 0 {
warn!("File is empty {:?}", Self::file_path());
} else if let Ok(data) = ron::from_str(&buf) {
*self = data;
} else if let Ok(data) = serde_json::from_str(&buf) {
*self = data;
} else {
warn!("Could not deserialise {:?}", Self::file_path());
}
}
}
fn write(&self) {
let mut file = match File::create(Self::file_path()) {
Ok(data) => data,
Err(e) => {
error!(
"Couldn't overwrite config {:?}, error: {e}",
Self::file_path()
);
return;
}
};
let ron = match ron::ser::to_string_pretty(&self, PrettyConfig::new().depth_limit(4)) {
Ok(data) => data,
Err(e) => {
error!("Parse {:?} to RON failed, error: {e}", Self::file_path());
return;
}
};
file.write_all(ron.as_bytes())
.unwrap_or_else(|err| error!("Could not write config: {}", err));
}
/// Renames the existing file to `<file>-old`
fn rename_file_old() {
warn!(
"Renaming {} to {}-old and recreating config",
Self::file_name(),
Self::file_name()
);
let cfg_old = Self::file_path().to_string_lossy().to_string() + "-old";
std::fs::rename(Self::file_path(), cfg_old).unwrap_or_else(|err| {
error!(
"Could not rename. Please remove {} then restart service: Error {}",
Self::file_name(),
err
)
});
}
}
pub trait StdConfigLoad1<T>
where
T: StdConfig + DeserializeOwned + Serialize,
{
fn load() -> T {
let mut file = T::file_open();
let mut buf = String::new();
let config: T;
if let Ok(read_len) = file.read_to_string(&mut buf) {
if read_len == 0 {
config = T::new();
} else if let Ok(data) = ron::from_str(&buf) {
config = data;
} else if let Ok(data) = serde_json::from_str(&buf) {
config = data;
} else {
T::rename_file_old();
config = T::new();
}
} else {
config = T::new();
}
config.write();
config
}
}
pub trait StdConfigLoad2<T1, T2>
where
T1: StdConfig + DeserializeOwned + Serialize,
T2: DeserializeOwned + Into<T1>,
{
fn load() -> T1 {
let mut file = T1::file_open();
let mut buf = String::new();
let config: T1;
if let Ok(read_len) = file.read_to_string(&mut buf) {
if read_len == 0 {
config = T1::new();
} else if let Ok(data) = ron::from_str(&buf) {
config = data;
} else if let Ok(data) = serde_json::from_str(&buf) {
config = data;
} else if let Ok(data) = serde_json::from_str::<T2>(&buf) {
config = data.into();
} else {
T1::rename_file_old();
config = T1::new();
}
} else {
config = T1::new();
}
config.write();
config
}
}
pub trait StdConfigLoad3<T1, T2, T3>
where
T1: StdConfig + DeserializeOwned + Serialize,
T2: DeserializeOwned + Into<T1>,
T3: DeserializeOwned + Into<T1>,
{
fn load() -> T1 {
let mut file = T1::file_open();
let mut buf = String::new();
let config: T1;
if let Ok(read_len) = file.read_to_string(&mut buf) {
if read_len == 0 {
config = T1::new();
} else if let Ok(data) = ron::from_str(&buf) {
config = data;
} else if let Ok(data) = serde_json::from_str(&buf) {
config = data;
} else if let Ok(data) = serde_json::from_str::<T2>(&buf) {
config = data.into();
} else if let Ok(data) = serde_json::from_str::<T3>(&buf) {
config = data.into();
} else {
T1::rename_file_old();
config = T1::new();
}
} else {
config = T1::new();
}
config.write();
config
}
}