#[cfg(feature = "serde")] use super::Format;
use super::{Config, Error};
#[cfg(feature = "serde")]
use crate::util::warn_about_error_with_path;
use std::cell::RefCell;
#[cfg(feature = "serde")] use std::path::PathBuf;
use std::rc::Rc;
pub trait ConfigFactory {
fn read_config(&mut self) -> Result<Rc<RefCell<Config>>, Error>;
fn writer(self) -> Option<Box<dyn FnMut(&Config)>>;
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct DefaultFactory;
impl ConfigFactory for DefaultFactory {
fn read_config(&mut self) -> Result<Rc<RefCell<Config>>, Error> {
Ok(Rc::new(RefCell::new(Config::default())))
}
fn writer(self) -> Option<Box<dyn FnMut(&Config)>> {
None
}
}
#[cfg(feature = "serde")]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum ConfigMode {
Auto,
Read,
ReadWrite,
WriteDefault,
}
#[cfg(feature = "serde")]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ReadWriteFactory {
path: PathBuf,
mode: ConfigMode,
fail_on_error: bool,
}
#[cfg(feature = "serde")]
impl ReadWriteFactory {
pub fn new(path: PathBuf, mode: ConfigMode) -> Self {
let fail_on_error = false;
ReadWriteFactory {
path,
mode,
fail_on_error,
}
}
pub fn fail_on_error(mut self) -> Self {
self.fail_on_error = true;
self
}
pub fn from_env() -> Self {
use std::env::var;
let mut path = PathBuf::new();
let mut mode = ConfigMode::Auto;
let mut fail_on_error = false;
if let Ok(v) = var("KAS_CONFIG") {
path = v.into();
}
if let Ok(mut v) = var("KAS_CONFIG_MODE") {
v.make_ascii_uppercase();
mode = match v.as_str() {
"READ" => ConfigMode::Read,
"READWRITE" => ConfigMode::ReadWrite,
"WRITEDEFAULT" => ConfigMode::WriteDefault,
other => {
log::error!("from_env: bad var KAS_CONFIG_MODE={other}");
log::error!("from_env: supported config modes: READ, READWRITE, WRITEDEFAULT");
mode
}
};
}
if let Ok(v) = var("KAS_CONFIG_FAIL_ON_ERROR") {
fail_on_error = match v.parse() {
Ok(b) => b,
_ => {
log::error!("from_env: bad var KAS_CONFIG_FAIL_ON_ERROR={v}");
true
}
};
}
ReadWriteFactory {
path,
mode,
fail_on_error,
}
}
}
#[cfg(feature = "serde")]
impl ConfigFactory for ReadWriteFactory {
fn read_config(&mut self) -> Result<Rc<RefCell<Config>>, Error> {
let config = match self.mode {
_ if self.path.as_os_str().is_empty() => Config::default(),
ConfigMode::Auto | ConfigMode::Read | ConfigMode::ReadWrite => {
match Format::guess_and_read_path(&self.path) {
Ok(config) => {
self.mode = match std::fs::metadata(&self.path) {
Ok(meta) if meta.is_file() && !meta.permissions().readonly() => {
ConfigMode::ReadWrite
}
_ => ConfigMode::Read,
};
config
}
Err(error) => {
if matches!(&error, Error::IoError(e) if e.kind() == std::io::ErrorKind::NotFound)
{
self.mode = ConfigMode::WriteDefault;
} else {
warn_about_error_with_path("failed to read config", &error, &self.path);
if self.fail_on_error {
return Err(error);
}
}
Config::default()
}
}
}
ConfigMode::WriteDefault => Default::default(),
};
if self.mode == ConfigMode::WriteDefault
&& let Err(error) = Format::guess_and_write_path(&self.path, &config)
{
self.mode = ConfigMode::Read;
warn_about_error_with_path("failed to write default config: ", &error, &self.path);
}
Ok(Rc::new(RefCell::new(config)))
}
fn writer(self) -> Option<Box<dyn FnMut(&Config)>> {
if self.path.as_os_str().is_empty()
|| matches!(self.mode, ConfigMode::Read | ConfigMode::ReadWrite)
{
return None;
}
let path = self.path;
Some(Box::new(move |config| {
if let Err(error) = Format::guess_and_write_path(&path, config) {
warn_about_error_with_path("failed to write config: ", &error, &path);
}
}))
}
}
#[cfg(not(feature = "serde"))]
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct AutoFactory(DefaultFactory);
#[cfg(feature = "serde")]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct AutoFactory(ReadWriteFactory);
#[cfg(feature = "serde")]
impl Default for AutoFactory {
fn default() -> Self {
AutoFactory(ReadWriteFactory::from_env())
}
}
impl ConfigFactory for AutoFactory {
#[inline]
fn read_config(&mut self) -> Result<Rc<RefCell<Config>>, Error> {
self.0.read_config()
}
#[inline]
fn writer(self) -> Option<Box<dyn FnMut(&Config)>> {
self.0.writer()
}
}