From 8c12dfc1329eaf2eab4325c1d261ff718f03cc5f Mon Sep 17 00:00:00 2001 From: meeg_leeto Date: Thu, 12 May 2022 18:29:01 +0100 Subject: [PATCH] feat: versioning of configuration files --- src/main.rs | 96 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 74 insertions(+), 22 deletions(-) diff --git a/src/main.rs b/src/main.rs index a4b6e59..7fa4878 100644 --- a/src/main.rs +++ b/src/main.rs @@ -145,6 +145,12 @@ mod conf { /// example). See the definition of each of the member structs for more /// information. pub struct Config { + /// The "version" of the configuration, corresponding to the MAJOR in + /// semantic versioning. Should be increased every time the + /// configuration structure suffers breaking changes. + /// This value is optional because sufficiently old configuration files + /// may not have a version field. + pub version: Option, /// Configuration regarding the Redis database. pub db: DbConfig, /// Configuration regarding the types of (URL shorten) slugs produced. @@ -153,6 +159,32 @@ mod conf { pub serve_rules: ServeRules, } + /// Get the configuration version field that this version of lonk expects. + pub fn config_version() -> usize { + usize::from_str(env!("CARGO_PKG_VERSION_MAJOR")).unwrap() + } + + pub enum ConfigParseError { + SerdeError(serde_json::error::Error), + OldVersion(usize), + } + + impl Config { + pub fn from_sync_buffer( + buffer: std::io::BufReader, + ) -> Result { + let parsed: Config = + serde_json::from_reader(buffer).map_err(|err| ConfigParseError::SerdeError(err))?; + + let parsed_version = parsed.version.unwrap_or(0); + if parsed_version != config_version() { + return Err(ConfigParseError::OldVersion(parsed_version)); + } + + Ok(parsed) + } + } + // Default implementations impl Default for DbConfig { @@ -210,6 +242,7 @@ mod conf { impl Default for Config { fn default() -> Self { Self { + version: Some(config_version()), db: Default::default(), slug_rules: Default::default(), serve_rules: Default::default(), @@ -824,27 +857,45 @@ async fn serve() { ), }; }); - let config_buf = std::io::BufReader::new(config_file); - serde_json::from_reader(config_buf).unwrap_or_else(|err| match err.classify() { - serde_json::error::Category::Io => panic!("IO error when reading configuration file."), - serde_json::error::Category::Syntax => panic!( - "Configuration file is syntactically incorrect. - See {}:line {}, column {}.", - &config_file_name, - err.line(), - err.column() - ), - serde_json::error::Category::Data => panic!( - "Error deserializing configuration file; expected different data type. - See {}:line {}, column {}.", - &config_file_name, - err.line(), - err.column() - ), - serde_json::error::Category::Eof => { - panic!("Unexpected end of file when reading configuration file.") - } + let parse_result = tokio::task::spawn_blocking(move || { + conf::Config::from_sync_buffer(std::io::BufReader::new(config_file)) }) + .await + .expect("Tokio error from blocking task."); + + match parse_result { + Err(conf::ConfigParseError::SerdeError(err)) => match err.classify() { + serde_json::error::Category::Io => { + panic!("IO error when reading configuration file.") + } + serde_json::error::Category::Syntax => panic!( + "Configuration file is syntactically incorrect. + See {}:line {}, column {}.", + &config_file_name, + err.line(), + err.column() + ), + serde_json::error::Category::Data => panic!( + "Error deserializing configuration file; expected different data type. + See {}:line {}, column {}.", + &config_file_name, + err.line(), + err.column() + ), + serde_json::error::Category::Eof => { + panic!("Unexpected end of file when reading configuration file.") + } + }, + Err(conf::ConfigParseError::OldVersion(old_version)) => { + panic!( + "Configuration file has outdated version. + Expected version field to be {} but got {}.", + old_version, + conf::config_version() + ); + } + Ok(config) => config, + } }; // Create slug factory @@ -934,7 +985,8 @@ fn main() { serde_json::to_string_pretty(&conf::Config::default()) .expect("Default configuration should always be JSON serializable") ); - } else { - serve(); + std::process::exit(0); } + + serve(); }