Skip to content

Commit

Permalink
Introduce strongly-typed Context keys
Browse files Browse the repository at this point in the history
  • Loading branch information
matklad committed Jan 11, 2018
1 parent d12db23 commit 4c7baaf
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 28 deletions.
8 changes: 5 additions & 3 deletions exonum/src/helpers/fabric/builder.rs
Expand Up @@ -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.
Expand Down Expand Up @@ -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<Box<Service>> = self.service_factories
.into_iter()
.map(|mut factory| factory.make_service(ctx))
Expand Down
37 changes: 19 additions & 18 deletions exonum/src/helpers/fabric/details.rs
Expand Up @@ -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";

Expand Down Expand Up @@ -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
Expand All @@ -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)
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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::<String, Value>::default(),
);
context.set(
"services_secret_configs",
keys::SERVICES_SECRET_CONFIGS,
BTreeMap::<String, Value>::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();
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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.");
Expand Down Expand Up @@ -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<NodeConfig> = new_context.get("configs").expect(
let configs = new_context.get(keys::CONFIGS).expect(
"Couldn't read testnet configs after exts call.",
);

Expand Down
57 changes: 57 additions & 0 deletions 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<T> {
// 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<T>,
}

// We need explicit `impl Copy` because derive won't work if `T: !Copy`.
impl<T> Copy for Key<T> {}
#[allow(expl_impl_clone_on_copy)]
impl<T> Clone for Key<T> {
fn clone(&self) -> Self {
Key {
__name: self.__name,
__phantom: self.__phantom,
}
}
}

impl<T> fmt::Debug for Key<T> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "Key({:?})", self.__name)
}
}

impl<T> Key<T> {
/// 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<Vec<String>> = config_key!("k");
let x = K;
let y = x;
assert_eq!(x.name(), y.name());
}
59 changes: 52 additions & 7 deletions exonum/src/helpers/fabric/mod.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -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<NodeConfig> = config_key!("node_config");
/// TODO
pub const CONFIGS: Key<Vec<NodeConfig>> = config_key!("configs");
/// TODO
pub const PUBLIC_CONFIG_LIST: Key<Vec<NodePublicConfig>> = config_key!("public_config_list");

/// TODO
pub const SERVICES_CONFIG: Key<AbstractConfig> = config_key!("services_config");
/// TODO
pub const COMMON_CONFIG: Key<CommonConfigTemplate> = config_key!("common_config");

/// TODO
pub const SERVICES_PUBLIC_CONFIGS: Key<BTreeMap<String, toml::Value>> =
config_key!("services_public_configs");
/// TODO
pub const SERVICES_SECRET_CONFIGS: Key<BTreeMap<String, toml::Value>> =
config_key!("services_secret_configs");

/// TODO
pub const AUDITOR_MODE: Key<bool> = 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)]
Expand Down Expand Up @@ -183,20 +220,28 @@ impl Context {
}

/// Gets the variable from the context.
pub fn get<'de, T: Deserialize<'de>>(&self, key: &str) -> Result<T, Box<Error>> {
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<T>) -> Result<T, Box<Error>> {
self.get_raw(key.name())
}

/// Sets the variable in the context and returns the previous value.
///
/// # Panic
///
/// Panics if value could not be serialized as TOML.
pub fn set<T: Serialize>(&mut self, key: &'static str, value: T) -> Option<Value> {
pub fn set<T: Serialize>(&mut self, key: Key<T>, value: T) -> Option<Value> {
self.set_raw(key.name(), value)
}

fn get_raw<'de, T: Deserialize<'de>>(&self, key: &str) -> Result<T, Box<Error>> {
if let Some(v) = self.variables.get(key) {
Ok(v.clone().try_into()?)
} else {
Err(Box::new(NotFoundInMap))
}
}

fn set_raw<T: Serialize>(&mut self, key: &str, value: T) -> Option<Value> {
let value: Value = Value::try_from(value).expect("could not convert value into toml");
self.variables.insert(key.to_owned(), value)
}
Expand Down

0 comments on commit 4c7baaf

Please sign in to comment.