From f32056f65c8f8660ed5b2adabe4018b66178ffe1 Mon Sep 17 00:00:00 2001 From: Steven Normore Date: Fri, 4 Oct 2024 16:04:46 -0400 Subject: [PATCH] feat(config): deserialization errors --- Cargo.lock | 1 + core/application/Cargo.toml | 1 + core/application/src/config.rs | 84 ++++++++++++++++++++++++++++++++++ core/utils/src/config.rs | 16 ++++--- 4 files changed, 96 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6a76dd0df..74929e5b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6778,6 +6778,7 @@ dependencies = [ "rand 0.8.5", "resolved-pathbuf", "serde", + "serde_json", "serde_with 3.9.0", "tempfile", "tokio", diff --git a/core/application/Cargo.toml b/core/application/Cargo.toml index 55eabaae0..cba46daa7 100644 --- a/core/application/Cargo.toml +++ b/core/application/Cargo.toml @@ -40,6 +40,7 @@ merklize.workspace = true lightning-test-utils = { path = "../test-utils" } tokio.workspace = true rand.workspace = true +serde_json.workspace = true tempfile.workspace = true [features] diff --git a/core/application/src/config.rs b/core/application/src/config.rs index 0ed670358..8c5b1e301 100644 --- a/core/application/src/config.rs +++ b/core/application/src/config.rs @@ -148,9 +148,93 @@ pub enum StorageConfig { #[cfg(test)] mod config_tests { + use std::fs::File; + use std::io::Write; + use std::panic; + + use lightning_interfaces::prelude::*; + use lightning_test_utils::json_config::JsonConfigProvider; + use lightning_utils::config::TomlConfigProvider; + use serde_json::json; use tempfile::tempdir; use super::*; + use crate::Application; + + partial_node_components!(TomlConfigTestNodeComponents { + ConfigProviderInterface = TomlConfigProvider; + ApplicationInterface = Application; + }); + + partial_node_components!(JsonConfigTestNodeComponents { + ConfigProviderInterface = JsonConfigProvider; + ApplicationInterface = Application; + }); + + #[test] + fn config_toml_fails_to_deserialize_unknown_network() { + let temp_dir = tempdir().unwrap(); + + // Write the configuration. + let config_path = temp_dir.path().join("config.toml"); + let config = r#" + [application] + network = "invalid" + "#; + let mut file = File::create(&config_path).unwrap(); + file.write_all(config.as_bytes()).unwrap(); + + // Load config into the provider. + let provider = + TomlConfigProvider::::load(&config_path).unwrap(); + + // Attempt to get the config. + // This should panic because of the invalid enum variant. + let result = + panic::catch_unwind(|| provider.get::>()); + assert!(result.is_err()); + if let Err(err) = result { + let panic_message = err + .downcast_ref::() + .map(|s| s.as_str()) + .or_else(|| err.downcast_ref::<&str>().copied()) + .unwrap(); + assert_eq!( + panic_message, + "Failed to deserialize 'application' config: unknown variant `invalid`, expected `localnet-example` or `testnet-stable`\nin `network`\n" + ) + } + } + + #[test] + fn config_json_fails_to_deserialize_unknown_network() { + let config_value = json!({ + "application": { + "network": "invalid" + } + }); + let provider: JsonConfigProvider = config_value.into(); + + // Attempt to get the config. + // This should panic because of the invalid enum variant. + let result = panic::catch_unwind(|| { + >::get::< + Application, + >(&provider) + }); + assert!(result.is_err()); + if let Err(err) = result { + let panic_message = err + .downcast_ref::() + .map(|s| s.as_str()) + .or_else(|| err.downcast_ref::<&str>().copied()) + .unwrap(); + assert_eq!( + panic_message, + "invalid value: Error(\"unknown variant `invalid`, expected `localnet-example` or `testnet-stable`\", line: 0, column: 0)" + ) + } + } #[test] fn genesis_with_network_without_genesis() { diff --git a/core/utils/src/config.rs b/core/utils/src/config.rs index 7f7f9103e..7239e997f 100644 --- a/core/utils/src/config.rs +++ b/core/utils/src/config.rs @@ -104,16 +104,20 @@ impl TomlConfigProvider { } impl ConfigProviderInterface for TomlConfigProvider { - fn get(&self) -> S::Config { + fn get(&self) -> S::Config { debug!("Getting the config for {}", std::any::type_name::()); let mut table = self.table.lock().expect("failed to acquire lock"); - // Parse the table value into S::Config, or use the default in the event it doesn't exist. - let item: S::Config = table - .get(S::KEY) - .and_then(|v| v.clone().try_into().ok()) - .unwrap_or_default(); + // Attempt to deserialize the configuration. + // Panic with an error message if deserialization fails. + let item: S::Config = match table.get(S::KEY) { + Some(v) => v + .clone() + .try_into() + .unwrap_or_else(|e| panic!("Failed to deserialize '{}' config: {}", S::KEY, e)), + None => S::Config::default(), + }; // Amend the internal table with the parsed or default item to be serialized later. table.insert(S::KEY.into(), Value::try_from(&item).unwrap());