//! `config migrate` subcommand mod v20240701; use crate::config::{Revision, CLIENT_CONFIG_PATH, OLD_CONFIG_PATH, SERVER_CONFIG_PATH}; use serde::Deserialize; use std::{fs, io::Read, path::Path}; #[derive(thiserror::Error, Debug)] pub(crate) enum MigrationError { #[error("failed to determine the current config revision ({0})")] UnknownRevision(&'static str), #[error(transparent)] ReadFile(#[from] std::io::Error), #[error(transparent)] InvalidConfig(#[from] toml::de::Error), #[error(transparent)] V20240701(#[from] v20240701::Error), } fn next_revision() -> Result, MigrationError> { 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(MigrationError::UnknownRevision( "client config file does not exist", )); } if !server_config_exists && client_config_exists { return Err(MigrationError::UnknownRevision( "server config file does not exist", )); } if !old_config_exists && !server_config_exists && !client_config_exists { return Err(MigrationError::UnknownRevision( "config file does not exist", )); } if old_config_exists && !server_config_exists && !client_config_exists { return Ok(Some(Revision::V20240701)); } #[derive(Deserialize)] struct Config { config_revision: Revision, } let mut buffer = String::new(); let mut server_toml = fs::File::open(SERVER_CONFIG_PATH)?; server_toml.read_to_string(&mut buffer)?; let server_config_revision = toml::from_str::(&buffer)?.config_revision; let mut client_toml = fs::File::open(CLIENT_CONFIG_PATH)?; client_toml.read_to_string(&mut buffer)?; let client_config_revision = toml::from_str::(&buffer)?.config_revision; if server_config_revision != client_config_revision { return Err(MigrationError::UnknownRevision( "server config revision and client config revision are different", )); } let next_revision = match server_config_revision { Revision::V20240701 => None, }; Ok(next_revision) } async fn update_to_latest() -> Result<(), MigrationError> { if next_revision()?.is_none() { println!("Your config files are already up-to-date! (as of this fishctl release)"); return Ok(()); } while let Some(next_revision) = next_revision()? { run_impl(next_revision).await?; } Ok(()) } async fn run_impl(revision: Revision) -> Result<(), MigrationError> { match revision { Revision::V20240701 => v20240701::run().await?, } Ok(()) } pub(super) async fn run(revision: Option) -> Result<(), MigrationError> { match revision { Some(revision) => run_impl(revision).await, None => update_to_latest().await, } }