mod common;
mod from_v0_1;
use std::{fs, io, marker::PhantomData};
use thiserror::Error;
use toml_edit::DocumentMut;
use crate::tracing::LogResult as _;
use super::{
CONFIG_FILE_NAME, Config, ConfigFileError, FromTomlError, config_file,
};
#[must_use]
#[derive(Debug)]
pub struct ConfigUpdater<State> {
parsed_config: Config,
toml_config: DocumentMut,
_state: PhantomData<State>,
}
#[derive(Debug)]
pub struct Init;
#[derive(Debug)]
pub struct Updated;
#[derive(Debug, Clone, Copy)]
pub enum AskForTicket {
Ask {
require: bool,
},
DontAsk,
}
#[derive(Debug, Error)]
pub enum LoadError {
#[error("Failed to get the configuration file path")]
ConfigFileError(#[from] ConfigFileError),
#[error("No configuration file")]
NoConfigFile,
#[error("Failed to read {CONFIG_FILE_NAME}")]
ReadError(#[source] io::Error),
#[error("Invalid configuration in {CONFIG_FILE_NAME}")]
InvalidConfig(#[from] FromTomlError),
#[error("Failed to parse {CONFIG_FILE_NAME} into a TOML document")]
TomlEditError(#[source] toml_edit::TomlError),
}
#[derive(Debug, Error)]
pub enum UpdateError {
#[error(
"Tried to update from version {tried_from}, but the actual version is {actual}."
)]
IncorrectVersion {
tried_from: String,
actual: String,
},
}
#[derive(Debug, Error)]
pub enum SaveError {
#[error("Failed to get the configuration file path")]
ConfigFileError(#[from] ConfigFileError),
#[error("Failed to write {CONFIG_FILE_NAME}")]
WriteError(#[source] io::Error),
}
impl ConfigUpdater<Init> {
#[tracing::instrument(name = "load_config", level = "trace")]
pub fn load() -> Result<Self, LoadError> {
let config_file = config_file()?;
match fs::read_to_string(&config_file) {
Ok(toml) => {
tracing::info!(?config_file, "loading the configuration");
let parsed_config = Config::from_toml(&toml)?;
let toml_config =
toml.parse().map_err(LoadError::TomlEditError).log_err()?;
tracing::debug!(?parsed_config);
Ok(Self {
parsed_config,
toml_config,
_state: PhantomData,
})
}
Err(error) => {
if error.kind() == io::ErrorKind::NotFound {
tracing::error!(?config_file, "no configuration file");
Err(LoadError::NoConfigFile)
} else {
Err(LoadError::ReadError(error)).log_err()
}
}
}
}
pub fn parsed_config(&self) -> &Config {
&self.parsed_config
}
pub fn config_version(&self) -> &str {
&self.parsed_config.version
}
pub fn update_from_v0_1(
mut self,
switch_scopes_to_any: bool,
ask_for_ticket: AskForTicket,
empty_prefix_to_hash: bool,
) -> Result<ConfigUpdater<Updated>, UpdateError> {
self.check_version("0.1")?;
tracing::debug!(
?switch_scopes_to_any,
?ask_for_ticket,
?empty_prefix_to_hash,
"updating the configuration"
);
from_v0_1::update(
&mut self.toml_config,
switch_scopes_to_any,
ask_for_ticket,
empty_prefix_to_hash,
);
Ok(ConfigUpdater {
parsed_config: self.parsed_config,
toml_config: self.toml_config,
_state: PhantomData,
})
}
fn check_version(&self, updater_version: &str) -> Result<(), UpdateError> {
let config_version = self.config_version();
if config_version == updater_version {
Ok(())
} else {
Err(UpdateError::IncorrectVersion {
tried_from: updater_version.to_owned(),
actual: config_version.to_owned(),
})
.log_err()
}
}
}
impl ConfigUpdater<Updated> {
#[tracing::instrument(level = "trace", skip_all)]
pub fn save(self) -> Result<(), SaveError> {
tracing::info!("saving the configuration");
fs::write(config_file()?, self.toml_config.to_string())
.map_err(SaveError::WriteError)
.log_err()?;
Ok(())
}
}