From 4c7baaf75933ae12f9d8e639e7c1e55955485f21 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 9 Jan 2018 18:50:58 +0300 Subject: [PATCH] Introduce strongly-typed Context keys --- exonum/src/helpers/fabric/builder.rs | 8 ++-- exonum/src/helpers/fabric/details.rs | 37 ++++++++--------- exonum/src/helpers/fabric/key.rs | 57 +++++++++++++++++++++++++++ exonum/src/helpers/fabric/mod.rs | 59 ++++++++++++++++++++++++---- 4 files changed, 133 insertions(+), 28 deletions(-) create mode 100644 exonum/src/helpers/fabric/key.rs diff --git a/exonum/src/helpers/fabric/builder.rs b/exonum/src/helpers/fabric/builder.rs index ae2de9c3e8..f048165095 100644 --- a/exonum/src/helpers/fabric/builder.rs +++ b/exonum/src/helpers/fabric/builder.rs @@ -17,12 +17,13 @@ use std::panic::{self, PanicInfo}; use std::ffi::OsString; use blockchain::Service; -use node::{NodeConfig, Node}; +use node::Node; use super::internal::{CollectedCommand, Feedback}; use super::clap_backend::ClapBackend; use super::ServiceFactory; use super::details::{Run, Finalize, GenerateNodeConfig, GenerateCommonConfig, GenerateTestnet}; +use super::keys; /// `NodeBuilder` is a high level object, /// usable for fast prototyping and creating app from services list. @@ -73,8 +74,9 @@ impl NodeBuilder { match ClapBackend::execute(self.commands.as_slice()) { Feedback::RunNode(ref ctx) => { let db = Run::db_helper(ctx); - let config: NodeConfig = - ctx.get("node_config").expect("could not find node_config"); + let config = ctx.get(keys::NODE_CONFIG).expect( + "could not find node_config", + ); let services: Vec> = self.service_factories .into_iter() .map(|mut factory| factory.make_service(ctx)) diff --git a/exonum/src/helpers/fabric/details.rs b/exonum/src/helpers/fabric/details.rs index 389ac98500..161cfdb470 100644 --- a/exonum/src/helpers/fabric/details.rs +++ b/exonum/src/helpers/fabric/details.rs @@ -35,6 +35,7 @@ use super::{Argument, Context, CommandName}; use super::shared::{AbstractConfig, NodePublicConfig, SharedConfig, NodePrivateConfig, CommonConfigTemplate}; use super::DEFAULT_EXONUM_LISTEN_PORT; +use super::keys; const DATABASE_PATH: &str = "DATABASE_PATH"; @@ -127,9 +128,9 @@ impl Command for Run { let public_addr = Self::public_api_address(&context); let private_addr = Self::private_api_address(&context); - context.set("node_config", config); + context.set(keys::NODE_CONFIG, config); let mut new_context = exts(context); - let mut config: NodeConfig = new_context.get("node_config").expect( + let mut config = new_context.get(keys::NODE_CONFIG).expect( "cant load node_config", ); // Override api options @@ -141,7 +142,7 @@ impl Command for Run { config.api.private_api_address = Some(private_api_address); } - new_context.set("node_config", config); + new_context.set(keys::NODE_CONFIG, config); Feedback::RunNode(new_context) } @@ -175,9 +176,9 @@ impl Command for GenerateCommonConfig { "COMMON_CONFIG not found", ); - context.set("services_config", AbstractConfig::default()); + context.set(keys::SERVICES_CONFIG, AbstractConfig::default()); let new_context = exts(context); - let services_config = new_context.get("services_config").unwrap_or_default(); + let services_config = new_context.get(keys::SERVICES_CONFIG).unwrap_or_default(); let template = CommonConfigTemplate { services_config, @@ -254,18 +255,18 @@ impl Command for GenerateNodeConfig { let addr = Self::addr(&context); let common: CommonConfigTemplate = ConfigFile::load(&common_config_path).expect("Could not load common config"); - context.set("common_config", common.clone()); + context.set(keys::COMMON_CONFIG, common.clone()); context.set( - "services_public_configs", + keys::SERVICES_PUBLIC_CONFIGS, BTreeMap::::default(), ); context.set( - "services_secret_configs", + keys::SERVICES_SECRET_CONFIGS, BTreeMap::::default(), ); let new_context = exts(context); - let services_public_configs = new_context.get("services_public_configs").unwrap(); - let services_secret_configs = new_context.get("services_secret_configs"); + let services_public_configs = new_context.get(keys::SERVICES_PUBLIC_CONFIGS).unwrap(); + let services_secret_configs = new_context.get(keys::SERVICES_SECRET_CONFIGS); let (consensus_public_key, consensus_secret_key) = crypto::gen_keypair(); let (service_public_key, service_secret_key) = crypto::gen_keypair(); @@ -421,7 +422,7 @@ impl Command for Finalize { .collect(); let (common, list, our) = Self::reduce_configs(public_configs, &secret_config); - context.set("auditor_mode", our.is_none()); + context.set(keys::AUDITOR_MODE, our.is_none()); let peers = list.iter().map(|c| c.addr).collect(); @@ -449,17 +450,17 @@ impl Command for Finalize { } }; - context.set("public_config_list", list); - context.set("node_config", config); - context.set("common_config", common); + context.set(keys::PUBLIC_CONFIG_LIST, list); + context.set(keys::NODE_CONFIG, config); + context.set(keys::COMMON_CONFIG, common); context.set( - "services_secret_configs", + keys::SERVICES_SECRET_CONFIGS, secret_config.services_secret_configs, ); let new_context = exts(context); - let config: NodeConfig = new_context.get("node_config").expect( + let config = new_context.get(keys::NODE_CONFIG).expect( "Could not create config from template, services return error", ); ConfigFile::save(&config, output_config_path).expect("Could not write config file."); @@ -528,9 +529,9 @@ impl Command for GenerateTestnet { } let configs = generate_testnet_config(count, start_port); - context.set("configs", configs); + context.set(keys::CONFIGS, configs); let new_context = exts(context); - let configs: Vec = new_context.get("configs").expect( + let configs = new_context.get(keys::CONFIGS).expect( "Couldn't read testnet configs after exts call.", ); diff --git a/exonum/src/helpers/fabric/key.rs b/exonum/src/helpers/fabric/key.rs new file mode 100644 index 0000000000..cea033e9c4 --- /dev/null +++ b/exonum/src/helpers/fabric/key.rs @@ -0,0 +1,57 @@ +use std::marker::PhantomData; +use std::fmt; + +/// `Key` provides strongly typed access to data +/// inside `Context`. +pub struct Key { + // These fields are public so that `config_key` + // macro works outside of this crate. It should be + // replaced with `const fn`, once it is stable. + #[doc(hidden)] + pub __name: &'static str, + #[doc(hidden)] + pub __phantom: PhantomData, +} + +// We need explicit `impl Copy` because derive won't work if `T: !Copy`. +impl Copy for Key {} +#[allow(expl_impl_clone_on_copy)] +impl Clone for Key { + fn clone(&self) -> Self { + Key { + __name: self.__name, + __phantom: self.__phantom, + } + } +} + +impl fmt::Debug for Key { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "Key({:?})", self.__name) + } +} + +impl Key { + /// Name of this key. + pub fn name(&self) -> &str { + self.__name + } +} + +#[macro_export] +macro_rules! config_key { + ($name:expr) => {{ + Key { + __name: $name, + __phantom: ::std::marker::PhantomData + } + }} +} + +#[test] +fn key_is_really_copy() { + const K: Key> = config_key!("k"); + let x = K; + let y = x; + assert_eq!(x.name(), y.name()); +} diff --git a/exonum/src/helpers/fabric/mod.rs b/exonum/src/helpers/fabric/mod.rs index 525971647a..d00c096663 100644 --- a/exonum/src/helpers/fabric/mod.rs +++ b/exonum/src/helpers/fabric/mod.rs @@ -28,12 +28,15 @@ use self::internal::NotFoundInMap; pub use self::builder::NodeBuilder; pub use self::details::{Run, Finalize, GenerateNodeConfig, GenerateCommonConfig, GenerateTestnet}; pub use self::shared::{AbstractConfig, NodePublicConfig, CommonConfigTemplate, NodePrivateConfig}; +pub use self::key::Key; mod shared; mod builder; mod details; mod internal; mod clap_backend; +#[macro_use] +mod key; /// Default port value. pub const DEFAULT_EXONUM_LISTEN_PORT: u16 = 6333; @@ -110,6 +113,40 @@ impl Argument { } } +/// Keys describing various pieces of data one can get from `Context`. +pub mod keys { + use std::collections::BTreeMap; + + use toml; + + use node::NodeConfig; + use super::shared::{AbstractConfig, CommonConfigTemplate, NodePublicConfig}; + use super::Key; + + /// TODO + pub const NODE_CONFIG: Key = config_key!("node_config"); + /// TODO + pub const CONFIGS: Key> = config_key!("configs"); + /// TODO + pub const PUBLIC_CONFIG_LIST: Key> = config_key!("public_config_list"); + + /// TODO + pub const SERVICES_CONFIG: Key = config_key!("services_config"); + /// TODO + pub const COMMON_CONFIG: Key = config_key!("common_config"); + + /// TODO + pub const SERVICES_PUBLIC_CONFIGS: Key> = + config_key!("services_public_configs"); + /// TODO + pub const SERVICES_SECRET_CONFIGS: Key> = + config_key!("services_secret_configs"); + + /// TODO + pub const AUDITOR_MODE: Key = config_key!("auditor_mode"); +} + + /// `Context` is a type, used to keep some values from `Command` into /// `CommandExtension` and vice verse. #[derive(PartialEq, Debug, Clone, Default)] @@ -183,12 +220,8 @@ impl Context { } /// Gets the variable from the context. - pub fn get<'de, T: Deserialize<'de>>(&self, key: &str) -> Result> { - if let Some(v) = self.variables.get(key) { - Ok(v.clone().try_into()?) - } else { - Err(Box::new(NotFoundInMap)) - } + pub fn get<'de, T: Deserialize<'de>>(&self, key: Key) -> Result> { + self.get_raw(key.name()) } /// Sets the variable in the context and returns the previous value. @@ -196,7 +229,19 @@ impl Context { /// # Panic /// /// Panics if value could not be serialized as TOML. - pub fn set(&mut self, key: &'static str, value: T) -> Option { + pub fn set(&mut self, key: Key, value: T) -> Option { + self.set_raw(key.name(), value) + } + + fn get_raw<'de, T: Deserialize<'de>>(&self, key: &str) -> Result> { + if let Some(v) = self.variables.get(key) { + Ok(v.clone().try_into()?) + } else { + Err(Box::new(NotFoundInMap)) + } + } + + fn set_raw(&mut self, key: &str, value: T) -> Option { let value: Value = Value::try_from(value).expect("could not convert value into toml"); self.variables.insert(key.to_owned(), value) }