fishctl/src/command/config.rs
2024-06-21 11:05:27 +09:00

124 lines
3.5 KiB
Rust

//! `config` subcommand
mod update;
mod validate;
use crate::config::{Revision, CLIENT_CONFIG_PATH, OLD_CONFIG_PATH, SERVER_CONFIG_PATH};
use clap::Subcommand;
use serde::Deserialize;
use std::{
fs,
io::{self, Read},
path::Path,
};
#[derive(Subcommand)]
pub(crate) enum Commands {
/// Convert old config files to the new format
Update { revision: Option<Revision> },
/// Validate the config files
Validate,
}
/// Errors that can happen in `config` subcommand
#[derive(thiserror::Error, Debug)]
pub(crate) enum ConfigError {
#[error(transparent)]
Update(#[from] update::UpdateError),
#[error(transparent)]
Validate(#[from] validate::ValidationError),
}
#[derive(thiserror::Error, Debug)]
pub(crate) enum RevisionCheckError {
#[error("failed to determine the current config revision ({0})")]
UnknownRevision(&'static str),
#[error(transparent)]
ReadFile(#[from] ReadError),
}
#[derive(thiserror::Error, Debug)]
pub(crate) enum ReadError {
#[error(transparent)]
ReadFile(#[from] io::Error),
#[error("the config file is not written in the correct format")]
InvalidFormat(#[from] toml::de::Error),
}
fn read_file_string(path: &str) -> Result<String, ReadError> {
let mut file = fs::File::open(path)?;
let mut result = String::new();
file.read_to_string(&mut result)?;
Ok(result)
}
fn read_server_config<T>() -> Result<T, ReadError>
where
T: serde::de::DeserializeOwned,
{
toml::from_str(&read_file_string(SERVER_CONFIG_PATH)?).map_err(ReadError::InvalidFormat)
}
fn read_client_config<T>() -> Result<T, ReadError>
where
T: serde::de::DeserializeOwned,
{
toml::from_str(&read_file_string(CLIENT_CONFIG_PATH)?).map_err(ReadError::InvalidFormat)
}
fn current_revision() -> Result<Revision, RevisionCheckError> {
let old_config_exists = Path::new(OLD_CONFIG_PATH).is_file();
let server_config_exists = Path::new(SERVER_CONFIG_PATH).is_file();
let client_config_exists = Path::new(CLIENT_CONFIG_PATH).is_file();
if server_config_exists && !client_config_exists {
return Err(RevisionCheckError::UnknownRevision(
"client config file does not exist",
));
}
if !server_config_exists && client_config_exists {
return Err(RevisionCheckError::UnknownRevision(
"server config file does not exist",
));
}
if !old_config_exists && !server_config_exists && !client_config_exists {
return Err(RevisionCheckError::UnknownRevision(
"config file does not exist",
));
}
if old_config_exists && !server_config_exists && !client_config_exists {
return Ok(Revision::V0);
}
#[derive(Deserialize)]
struct Config {
config_revision: Revision,
}
let server_config_revision = read_server_config::<Config>()?.config_revision;
let client_config_revision = read_server_config::<Config>()?.config_revision;
if server_config_revision != client_config_revision {
return Err(RevisionCheckError::UnknownRevision(
"server config revision and client config revision are different",
));
}
match server_config_revision {
Revision::V0 => Err(RevisionCheckError::UnknownRevision(
"revision 0 does not exist",
)),
_ => Ok(server_config_revision),
}
}
pub(crate) async fn run(command: Commands) -> Result<(), ConfigError> {
match command {
Commands::Update { revision } => update::run(revision).await?,
Commands::Validate => validate::run()?,
}
Ok(())
}