Skip to content

Commit

Permalink
feat(config): deserialization errors
Browse files Browse the repository at this point in the history
  • Loading branch information
snormore committed Oct 4, 2024
1 parent 99f8faf commit eee8ad2
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 6 deletions.
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6778,6 +6778,7 @@ dependencies = [
"rand 0.8.5",
"resolved-pathbuf",
"serde",
"serde_json",
"serde_with 3.9.0",
"tempfile",
"tokio",
Expand Down
1 change: 1 addition & 0 deletions core/application/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
84 changes: 84 additions & 0 deletions core/application/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self>;
ApplicationInterface = Application<Self>;
});

partial_node_components!(JsonConfigTestNodeComponents {
ConfigProviderInterface = JsonConfigProvider;
ApplicationInterface = Application<Self>;
});

#[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::<TomlConfigTestNodeComponents>::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::<Application<TomlConfigTestNodeComponents>>());
assert!(result.is_err());
if let Err(err) = result {
let panic_message = err
.downcast_ref::<String>()
.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(|| {
<JsonConfigProvider as ConfigProviderInterface<JsonConfigTestNodeComponents>>::get::<
Application<JsonConfigTestNodeComponents>,
>(&provider)
});
assert!(result.is_err());
if let Err(err) = result {
let panic_message = err
.downcast_ref::<String>()
.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() {
Expand Down
16 changes: 10 additions & 6 deletions core/utils/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,16 +104,20 @@ impl<C: NodeComponents> TomlConfigProvider<C> {
}

impl<C: NodeComponents> ConfigProviderInterface<C> for TomlConfigProvider<C> {
fn get<S: lightning_interfaces::ConfigConsumer>(&self) -> S::Config {
fn get<S: ConfigConsumer>(&self) -> S::Config {
debug!("Getting the config for {}", std::any::type_name::<S>());

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());
Expand Down

0 comments on commit eee8ad2

Please sign in to comment.