diff --git a/src/action.rs b/src/action.rs new file mode 100644 index 0000000..ac0d04b --- /dev/null +++ b/src/action.rs @@ -0,0 +1,10 @@ +use state::State; +use dispatcher::response::ResponseSender; +use context::base::BaseContext; + +pub use config::action::message::Alert; + +pub trait Action { + fn on_opened(&self, state: &State, context: &BaseContext, &mut ResponseSender); + fn on_closed(&self, state: &State, context: &BaseContext, &mut ResponseSender); +} diff --git a/src/action/message/mod.rs b/src/action/message/mod.rs deleted file mode 100644 index ce02a78..0000000 --- a/src/action/message/mod.rs +++ /dev/null @@ -1,151 +0,0 @@ -use action::Action; -use config; -use config::action::ExecCondition; -use config::action::message::InjectMode; -use context::base::BaseContext; -use dispatcher::Response; -use dispatcher::response::ResponseSender; -use message::{Message, MessageBuilder}; - -use handlebars::{Context, Handlebars}; -use std::borrow::Borrow; -use std::collections::BTreeMap; -use state::State; -use self::error::Error; -use self::renderer_context::RendererContext; - -mod error; -mod renderer_context; -#[cfg(test)] -mod test; - -pub const CONTEXT_UUID: &'static str = "context_uuid"; -pub const CONTEXT_NAME: &'static str = "context_name"; -pub const CONTEXT_LEN: &'static str = "context_len"; -pub const MESSAGES: &'static str = "messages"; -const MESSAGE: &'static str = "MESSAGE"; - -pub struct MessageAction { - sender: Box>, - uuid: String, - name: Option, - values: Handlebars, - when: ExecCondition, - inject_mode: InjectMode, -} - -impl MessageAction { - pub fn new(sender: Box>, - action: config::action::MessageAction) - -> MessageAction { - let config::action::MessageAction { uuid, name, message, values, when, inject_mode } = - action; - let mut handlebars = Handlebars::new(); - for (name, template) in values.into_iter() { - handlebars.register_template(&name, template); - } - handlebars.register_template(MESSAGE, message); - - MessageAction { - sender: sender, - uuid: uuid, - name: name, - values: handlebars, - when: when, - inject_mode: inject_mode, - } - } - - fn render_value(&self, key: &String, template_context: &Context) -> Result { - let mut writer = Vec::new(); - { - try!(self.values.renderw(key, &template_context, &mut writer)); - } - let string = try!(String::from_utf8(writer)); - Ok(string) - } - - fn render_values(&self, template_context: &Context) -> Result, Error> { - let mut rendered_values = BTreeMap::new(); - for (key, _) in self.values.get_templates() { - let rendered_value = try!(self.render_value(key, &template_context)); - rendered_values.insert(key.to_string(), rendered_value); - } - Ok(rendered_values) - } - - fn render_message(&self, state: &State, context: &BaseContext) -> Result { - let template_context = { - use handlebars::Context; - let context = RendererContext::new(state, context); - Context::wraps(&context) - }; - - let mut rendered_values = try!(self.render_values(&template_context)); - MessageAction::extend_with_context_information(&mut rendered_values, state, context); - let message = rendered_values.remove(MESSAGE) - .expect(&format!("There is no '{}' key in the renderer \ - key-value pairs", - MESSAGE)); - let name = self.name.as_ref().map(|name| name.borrow()); - let message = MessageBuilder::new(&self.uuid, message) - .name(name) - .values(rendered_values) - .build(); - Ok(message) - } - - fn extend_with_context_information(values: &mut BTreeMap, - state: &State, - context: &BaseContext) { - values.insert(CONTEXT_UUID.to_string(), - context.uuid().to_hyphenated_string()); - values.insert(CONTEXT_LEN.to_string(), state.messages().len().to_string()); - if let Some(name) = context.name() { - values.insert(CONTEXT_NAME.to_string(), name.to_string()); - } - } - - fn execute(&self, _state: &State, _context: &BaseContext) { - match self.render_message(_state, _context) { - Ok(message) => { - let response = Alert { - message: message, - inject_mode: self.inject_mode.clone(), - }; - self.sender.send_response(Response::Alert(response)); - } - Err(error) => { - error!("{}", error); - } - } - } -} - -#[derive(Debug)] -pub struct Alert { - message: Message, - inject_mode: InjectMode, -} - -impl Alert { - pub fn message(&self) -> &Message { - &self.message - } -} - -impl Action for MessageAction { - fn on_opened(&self, _state: &State, _context: &BaseContext) { - if self.when.on_opened { - trace!("MessageAction: on_opened()"); - self.execute(_state, _context); - } - } - - fn on_closed(&self, _state: &State, _context: &BaseContext) { - if self.when.on_closed { - trace!("MessageAction: on_closed()"); - self.execute(_state, _context); - } - } -} diff --git a/src/action/message/test.rs b/src/action/message/test.rs deleted file mode 100644 index fe8de11..0000000 --- a/src/action/message/test.rs +++ /dev/null @@ -1,127 +0,0 @@ -use context::base::BaseContextBuilder; -use super::{CONTEXT_LEN, CONTEXT_NAME, CONTEXT_UUID, MessageAction}; - -use conditions::ConditionsBuilder; -use config; -use dispatcher::Response; -use dispatcher::response::ResponseSender; -use message::MessageBuilder; -use state::State; - -use env_logger; -use handlebars::Template; -use std::cell::RefCell; -use std::time::Duration; -use std::rc::Rc; -use uuid::Uuid; - -#[derive(Clone)] -struct DummyResponseSender { - responses: Rc>>, -} - -impl ResponseSender for DummyResponseSender { - fn send_response(&self, response: Response) { - self.responses.borrow_mut().push(response); - } - - fn boxed_clone(&self) -> Box> { - Box::new(self.clone()) - } -} - -#[test] -fn test_given_dummy_response_handler_can_be_cloned() { - let responses = Rc::new(RefCell::new(Vec::new())); - let response_sender = DummyResponseSender { responses: responses.clone() }; - let _ = response_sender.boxed_clone(); -} - -#[test] -fn test_given_a_message_action_when_it_is_executed_then_it_adds_the_name_and_uuid_of_the_context_to_the_message - () { - let name = Some("name".to_string()); - let base_context = { - let conditions = ConditionsBuilder::new(Duration::from_millis(100)).build(); - let uuid = Uuid::new_v4(); - BaseContextBuilder::new(uuid, conditions).name(name.clone()).build() - }; - let state = State::new(); - let responses = Rc::new(RefCell::new(Vec::new())); - let message_action = { - let response_sender = DummyResponseSender { responses: responses.clone() }; - let message = Template::compile("message".to_string()) - .ok() - .expect("Failed to compile a handlebars template"); - let config_action = config::action::message::MessageActionBuilder::new("uuid", message) - .build(); - MessageAction::new(Box::new(response_sender), config_action) - }; - - message_action.execute(&state, &base_context); - assert_eq!(1, responses.borrow().len()); - let responses = responses.borrow(); - if let &Response::Alert(ref response) = responses.get(0).unwrap() { - assert_eq!(name.as_ref().unwrap(), - response.message().get(CONTEXT_NAME).unwrap()); - assert_eq!(&base_context.uuid().to_hyphenated_string(), - response.message().get(CONTEXT_UUID).unwrap()); - assert_eq!("0", response.message().get(CONTEXT_LEN).unwrap()); - } else { - unreachable!(); - } -} - -#[test] -fn test_given_message_action_when_it_is_executed_then_it_uses_the_messages_to_render_the_message_and_additonal_templated_values - () { - let _ = env_logger::init(); - let name = Some("name".to_string()); - let base_context = { - let conditions = ConditionsBuilder::new(Duration::from_millis(100)).build(); - let uuid = Uuid::new_v4(); - BaseContextBuilder::new(uuid, conditions).name(name.clone()).build() - }; - let state = { - let messages = vec![Rc::new(MessageBuilder::new("uuid1", "message1") - .pair("key1", "value1") - .build()), - Rc::new(MessageBuilder::new("uuid2", "message2") - .pair("key2", "value2") - .build())]; - State::with_messages(messages) - }; - let responses = Rc::new(RefCell::new(Vec::new())); - let message_action = { - let response_sender = DummyResponseSender { responses: responses.clone() }; - let message = Template::compile("key1={{{messages.[0].values.key1}}} \ - key2={{{messages.[1].values.key2}}}" - .to_string()) - .ok() - .expect("Failed to compile a handlebars template"); - let config_action = config::action::message::MessageActionBuilder::new("uuid", message) - .pair("message_num", - Template::compile("we have {{context_len}} messages" - .to_string()) - .ok() - .expect("Failed to compile a handlebars template")) - .build(); - MessageAction::new(Box::new(response_sender), config_action) - }; - - message_action.execute(&state, &base_context); - assert_eq!(1, responses.borrow().len()); - let responses = responses.borrow(); - if let &Response::Alert(ref response) = responses.get(0).unwrap() { - assert_eq!(name.as_ref().unwrap(), - response.message().get(CONTEXT_NAME).unwrap()); - assert_eq!(&base_context.uuid().to_hyphenated_string(), - response.message().get(CONTEXT_UUID).unwrap()); - let message = response.message(); - assert_eq!("we have 2 messages", - message.get("message_num").expect("Failed to get an inserted key from a map")); - assert_eq!("key1=value1 key2=value2", message.message()); - } else { - unreachable!(); - } -} diff --git a/src/action/mod.rs b/src/action/mod.rs deleted file mode 100644 index 1ab83da..0000000 --- a/src/action/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -use config::action::ActionType; -use state::State; -use dispatcher::Response; -use dispatcher::response::ResponseSender; -use context::base::BaseContext; - -pub mod message; - -pub use self::message::Alert; - -pub trait Action { - fn on_opened(&self, state: &State, context: &BaseContext); - fn on_closed(&self, state: &State, context: &BaseContext); -} - -pub fn from_config(config: ActionType, _sender: Box>) -> Box { - match config { - ActionType::Message(action) => Box::new(self::message::MessageAction::new(_sender, action)), - } -} diff --git a/src/bin/test.rs b/src/bin/test.rs index 01b0349..c836370 100644 --- a/src/bin/test.rs +++ b/src/bin/test.rs @@ -3,7 +3,8 @@ extern crate maplit; extern crate correlation; extern crate uuid; -use correlation::{config, Correlator}; +use correlation::Correlator; +use correlation::config::ContextConfigBuilder; use correlation::conditions::ConditionsBuilder; use correlation::message::MessageBuilder; use uuid::Uuid; @@ -25,11 +26,10 @@ fn main() { .first_opens(true) .last_closes(true) .build(); - let actions = vec![]; let contexts = vec![ - config::ContextBuilder::new(Uuid::new_v4(), condition.clone()).actions(actions.clone()).build(), - config::ContextBuilder::new(Uuid::new_v4(), condition.clone()).actions(actions.clone()).build(), - config::ContextBuilder::new(Uuid::new_v4(), condition.clone()).actions(actions.clone()).build(), + ContextConfigBuilder::new(Uuid::new_v4(), condition.clone()).actions(Vec::new()).build(), + ContextConfigBuilder::new(Uuid::new_v4(), condition.clone()).actions(Vec::new()).build(), + ContextConfigBuilder::new(Uuid::new_v4(), condition.clone()).actions(Vec::new()).build(), ]; let mut correlator = Correlator::new(contexts); let _ = correlator.push_message(MessageBuilder::new(&uuid1, "message").build()); diff --git a/src/condition.rs b/src/condition.rs deleted file mode 100644 index 2d50368..0000000 --- a/src/condition.rs +++ /dev/null @@ -1,23 +0,0 @@ -use std::cell::RefCell; -use std::rc::Rc; - -#[derive(Clone, Debug)] -pub struct Condition(Rc>); - -impl Condition { - pub fn new(state: bool) -> Condition { - Condition(Rc::new(RefCell::new(state))) - } - - pub fn is_active(&self) -> bool { - *self.0.borrow() - } - - pub fn activate(&mut self) { - *self.0.borrow_mut() = true; - } - - pub fn deactivate(&mut self) { - *self.0.borrow_mut() = false; - } -} diff --git a/src/conditions.rs b/src/conditions.rs index 4af395e..54da593 100644 --- a/src/conditions.rs +++ b/src/conditions.rs @@ -29,7 +29,7 @@ impl Conditions { pub fn is_opening(&self, message: &Message) -> bool { if self.first_opens { - message.ids().any(|x| x == self.patterns.first().unwrap()) + self.patterns.first().iter().any(|first| message.ids().any(|id| &id == first)) } else { true } @@ -51,8 +51,9 @@ impl Conditions { fn is_closing_message(&self, state: &State) -> bool { if self.last_closes { - let last_message = state.messages().last().unwrap(); - last_message.ids().any(|x| x == self.patterns.last().unwrap()) + state.messages().last().iter().any(|last_message| { + self.patterns.last().iter().any(|last| last_message.ids().any(|id| &id == last)) + }) } else { false } @@ -115,7 +116,7 @@ impl ConditionsBuilder { mod test { use serde_json::from_str; use super::Conditions; - use std::rc::Rc; + use std::sync::Arc; use message::MessageBuilder; use state::State; @@ -123,6 +124,7 @@ mod test { use context::BaseContextBuilder; use uuid::Uuid; use std::time::Duration; + use dispatcher::response::MockResponseSender; #[test] fn test_given_condition_when_an_opening_message_is_received_then_the_state_becomes_opened() { @@ -144,6 +146,7 @@ mod test { #[test] fn test_given_condition_when_a_closing_message_is_received_then_the_state_becomes_closed() { + let mut responder = MockResponseSender::new(); let timeout = Duration::from_millis(100); let msg_id1 = "11eaf6f8-0640-460f-aee2-a72d2f2ab258".to_string(); let msg_id2 = "21eaf6f8-0640-460f-aee2-a72d2f2ab258".to_string(); @@ -157,12 +160,12 @@ mod test { .last_closes(true) .build(); let context = BaseContextBuilder::new(Uuid::new_v4(), conditions).build(); - let msg_opening = Rc::new(MessageBuilder::new(&msg_id1, "message").build()); - let msg_closing = Rc::new(MessageBuilder::new(&msg_id2, "message").build()); + let msg_opening = Arc::new(MessageBuilder::new(&msg_id1, "message").build()); + let msg_closing = Arc::new(MessageBuilder::new(&msg_id2, "message").build()); assert_false!(state.is_open()); - state.on_message(msg_opening, &context); + state.on_message(msg_opening, &context, &mut responder); assert_true!(state.is_open()); - state.on_message(msg_closing, &context); + state.on_message(msg_closing, &context, &mut responder); assert_false!(state.is_open()); } @@ -240,13 +243,14 @@ mod test { .patterns(patterns) .first_opens(true) .build(); - let msg = MessageBuilder::new(&uuid, "message").name(Some(&msg_id)).build(); + let msg = MessageBuilder::new(&uuid, "message").name(Some(msg_id)).build(); assert_true!(condition.is_opening(&msg)); } #[test] fn test_given_conditions_when_last_closes_is_set_and_the_message_has_a_name_then_we_check_that_name () { + let mut responder = MockResponseSender::new(); let timeout = Duration::from_millis(100); let patterns = vec!["p1".to_string(), "p2".to_string()]; let p1_uuid = "e4f3f8b2-3135-4916-a5ea-621a754dab0d".to_string(); @@ -259,14 +263,46 @@ mod test { .first_opens(true) .last_closes(true) .build(); - let p1_msg = MessageBuilder::new(&p1_uuid, "message").name(Some(&p1)).build(); - let p2_msg = MessageBuilder::new(&p2_uuid, "message").name(Some(&p2)).build(); + let p1_msg = MessageBuilder::new(&p1_uuid, "message").name(Some(p1)).build(); + let p2_msg = MessageBuilder::new(&p2_uuid, "message").name(Some(p2)).build(); let context = BaseContextBuilder::new(Uuid::new_v4(), conditions).build(); assert_false!(state.is_open()); - state.on_message(Rc::new(p1_msg), &context); - state.on_message(Rc::new(p2_msg), &context); + state.on_message(Arc::new(p1_msg), &context, &mut responder); + state.on_message(Arc::new(p2_msg), &context, &mut responder); assert_false!(state.is_open()); } + + #[test] + fn test_given_condition_when_first_opens_is_set_but_there_are_no_patterns_then_we_do_not_panic + () { + let mut responder = MockResponseSender::new(); + let msg = MessageBuilder::new("e4f3f8b2-3135-4916-a5ea-621a754dab0d", "message") + .name(Some("p1")) + .build(); + let conditions = ConditionsBuilder::new(Duration::from_millis(100)) + .patterns(Vec::new()) + .first_opens(true) + .build(); + let context = BaseContextBuilder::new(Uuid::new_v4(), conditions).build(); + let mut state = State::new(); + state.on_message(Arc::new(msg), &context, &mut responder); + } + + #[test] + fn test_given_condition_when_last_closes_is_set_but_there_are_no_patterns_then_we_do_not_panic + () { + let mut responder = MockResponseSender::new(); + let msg = MessageBuilder::new("e4f3f8b2-3135-4916-a5ea-621a754dab0d", "message") + .name(Some("p1")) + .build(); + let conditions = ConditionsBuilder::new(Duration::from_millis(100)) + .patterns(Vec::new()) + .last_closes(true) + .build(); + let context = BaseContextBuilder::new(Uuid::new_v4(), conditions).build(); + let mut state = State::new(); + state.on_message(Arc::new(msg), &context, &mut responder); + } } mod deser { diff --git a/src/config/action/deser/test.rs b/src/config/action/deser/test.rs index 5d3c831..a066812 100644 --- a/src/config/action/deser/test.rs +++ b/src/config/action/deser/test.rs @@ -13,7 +13,6 @@ fn test_given_action_when_it_is_deserialized_then_we_get_the_right_result() { "#; let result = from_str::(text); - println!("{:?}", &result); let action = result.ok().expect("Failed to deserialize a valid ActionType"); match action { ActionType::Message(message) => { @@ -26,7 +25,6 @@ fn test_given_action_when_it_is_deserialized_then_we_get_the_right_result() { fn test_given_unknown_action_when_it_is_deserialized_then_we_get_an_error() { let text = r#"{ "unknown": {} }"#; let result = from_str::(text); - println!("{:?}", &result); let _ = result.err().expect("Successfully deserialized an unknown action"); } diff --git a/src/config/action/message/builder.rs b/src/config/action/message/builder.rs index 22594cc..0a42499 100644 --- a/src/config/action/message/builder.rs +++ b/src/config/action/message/builder.rs @@ -1,63 +1,67 @@ use handlebars::Template; -use std::collections::BTreeMap; +use handlebars::Handlebars; use super::MessageAction; use super::InjectMode; +use super::MESSAGE; use config::action::ExecCondition; pub struct MessageActionBuilder { uuid: String, name: Option, message: Template, - values: BTreeMap, + values: Handlebars, when: ExecCondition, inject_mode: InjectMode, } impl MessageActionBuilder { pub fn new>(uuid: S, message: Template) -> MessageActionBuilder { + let mut values = Handlebars::new(); + values.register_template(MESSAGE, message.clone()); MessageActionBuilder { uuid: uuid.into(), name: None, message: message, - values: BTreeMap::new(), + values: values, when: ExecCondition::new(), inject_mode: Default::default(), } } - pub fn name>(&mut self, name: Option) -> &mut MessageActionBuilder { + pub fn name>(mut self, name: Option) -> MessageActionBuilder { self.name = name.map(|name| name.into()); self } - pub fn when(&mut self, when: ExecCondition) -> &mut MessageActionBuilder { + pub fn when(mut self, when: ExecCondition) -> MessageActionBuilder { self.when = when; self } - pub fn values(&mut self, values: BTreeMap) -> &mut MessageActionBuilder { + pub fn values(mut self, values: Handlebars) -> MessageActionBuilder { self.values = values; + self.values.register_template(MESSAGE, self.message.clone()); self } - pub fn pair>(&mut self, key: S, value: Template) -> &mut MessageActionBuilder { - self.values.insert(key.into(), value); + pub fn pair>(mut self, key: S, value: Template) -> MessageActionBuilder { + self.values.register_template(key.as_ref(), value); self } - pub fn inject_mode(&mut self, mode: InjectMode) -> &mut MessageActionBuilder { + pub fn inject_mode(mut self, mode: InjectMode) -> MessageActionBuilder { self.inject_mode = mode; self } - pub fn build(&self) -> MessageAction { + pub fn build(self) -> MessageAction { MessageAction { - uuid: self.uuid.clone(), - name: self.name.clone(), - message: self.message.clone(), - values: self.values.clone(), - when: self.when.clone(), - inject_mode: self.inject_mode.clone(), + uuid: self.uuid, + name: self.name, + message: self.message, + values: self.values, + when: self.when, + inject_mode: self.inject_mode, } } } diff --git a/src/config/action/message/deser.rs b/src/config/action/message/deser.rs index 820039a..527abaf 100644 --- a/src/config/action/message/deser.rs +++ b/src/config/action/message/deser.rs @@ -1,11 +1,12 @@ use super::MessageAction; -use super::MessageActionBuilder; use super::InjectMode; use config::action::ExecCondition; use handlebars::Template; +use handlebars::Handlebars; use serde::de::{Deserialize, Deserializer, Error, MapVisitor, Visitor}; use std::collections::BTreeMap; +use super::MESSAGE; impl Deserialize for MessageAction { fn deserialize(deserializer: &mut D) -> Result @@ -107,26 +108,30 @@ impl Visitor for MessageActionVisitor { } }; - let values = match values { + let mut values = match values { Some(values) => { - let mut converted_values = BTreeMap::new(); + let mut registry = Handlebars::new(); for (key, value) in values.into_iter() { let template = try!(MessageActionVisitor::compile_template::(value, &uuid)); - converted_values.insert(key, template); + registry.register_template(&key, template); } - converted_values + registry } - None => BTreeMap::new(), + None => Handlebars::new(), }; try!(visitor.end()); - Ok(MessageActionBuilder::new(uuid, message) - .name(name) - .values(values) - .when(when) - .inject_mode(inject_mode) - .build()) + values.register_template(MESSAGE, message.clone()); + + Ok(MessageAction { + uuid: uuid, + message: message, + name: name, + values: values, + when: when, + inject_mode: inject_mode, + }) } } @@ -162,6 +167,12 @@ mod test { use handlebars::Template; use serde_json::from_str; + fn assert_message_action_eq(expected: &MessageAction, actual: &MessageAction) { + assert_eq!(expected.uuid(), actual.uuid()); + assert_eq!(expected.name(), actual.name()); + assert_eq!(expected.message(), actual.message()); + } + #[test] fn test_given_message_as_a_json_string_when_it_is_deserialized_then_we_get_the_expected_message () { @@ -192,9 +203,8 @@ mod test { .pair("key2", value2) .build(); let result = from_str::(text); - println!("{:?}", &result); let message = result.ok().expect("Failed to deserialize a valid MessageAction object"); - assert_eq!(expected_message, message); + assert_message_action_eq(&expected_message, &message); } #[test] @@ -212,9 +222,8 @@ mod test { .expect("Failed to compile a handlebars template"); let expected_message = MessageActionBuilder::new("UUID", message).build(); let result = from_str::(text); - println!("{:?}", &result); let message = result.ok().expect("Failed to deserialize a valid MessageAction object"); - assert_eq!(expected_message, message); + assert_message_action_eq(&expected_message, &message); } #[test] @@ -226,7 +235,6 @@ mod test { "#; let result = from_str::(text); - println!("{:?}", &result); let _ = result.err().expect("Successfully deserialized an invalid MessageAction object"); } @@ -274,8 +282,7 @@ mod test { .inject_mode(InjectMode::Forward) .build(); let result = from_str::(text); - println!("{:?}", &result); let message = result.ok().expect("Failed to deserialize a valid MessageAction object"); - assert_eq!(expected_message, message); + assert_message_action_eq(&expected_message, &message); } } diff --git a/src/action/message/error.rs b/src/config/action/message/error.rs similarity index 100% rename from src/action/message/error.rs rename to src/config/action/message/error.rs diff --git a/src/config/action/message/mod.rs b/src/config/action/message/mod.rs index 420c4ee..53accba 100644 --- a/src/config/action/message/mod.rs +++ b/src/config/action/message/mod.rs @@ -1,5 +1,30 @@ -use handlebars::Template; +use action::Action; +use context::base::BaseContext; +use dispatcher::Response; +use dispatcher::response::ResponseSender; +use message::{Message, MessageBuilder}; + +use handlebars::Context; +use std::borrow::Borrow; use std::collections::BTreeMap; +use state::State; +use self::error::Error; +use self::renderer_context::RendererContext; + +mod error; +mod renderer_context; +#[cfg(test)] +mod test; + +pub const CONTEXT_UUID: &'static str = "context_uuid"; +pub const CONTEXT_NAME: &'static str = "context_name"; +pub const CONTEXT_LEN: &'static str = "context_len"; +pub const MESSAGES: &'static str = "messages"; +const MESSAGE: &'static str = "MESSAGE"; + +use handlebars::Template; +use handlebars::Handlebars; + use super::ActionType; use super::ExecCondition; @@ -9,14 +34,13 @@ mod builder; pub use self::builder::MessageActionBuilder; -#[derive(Clone, Debug, PartialEq)] pub struct MessageAction { - pub uuid: String, - pub name: Option, - pub message: Template, - pub values: BTreeMap, - pub when: ExecCondition, - pub inject_mode: InjectMode, + uuid: String, + name: Option, + message: Template, + values: Handlebars, + when: ExecCondition, + inject_mode: InjectMode, } impl MessageAction { @@ -29,12 +53,77 @@ impl MessageAction { pub fn message(&self) -> &Template { &self.message } - pub fn values(&self) -> &BTreeMap { + pub fn values(&self) -> &Handlebars { &self.values } pub fn inject_mode(&self) -> &InjectMode { &self.inject_mode } + + fn render_value(&self, key: &String, template_context: &Context) -> Result { + let mut writer = Vec::new(); + { + try!(self.values.renderw(key, &template_context, &mut writer)); + } + let string = try!(String::from_utf8(writer)); + Ok(string) + } + + fn render_values(&self, template_context: &Context) -> Result, Error> { + let mut rendered_values = BTreeMap::new(); + for (key, _) in self.values.get_templates() { + let rendered_value = try!(self.render_value(key, &template_context)); + rendered_values.insert(key.to_string(), rendered_value); + } + Ok(rendered_values) + } + + fn render_message(&self, state: &State, context: &BaseContext) -> Result { + let template_context = { + use handlebars::Context; + let context = RendererContext::new(state, context); + Context::wraps(&context) + }; + + let mut rendered_values = try!(self.render_values(&template_context)); + MessageAction::extend_with_context_information(&mut rendered_values, state, context); + let message = rendered_values.remove(MESSAGE) + .expect(&format!("There is no '{}' key in the renderer \ + key-value pairs", + MESSAGE)); + let name = self.name.as_ref().map(|name| name.borrow()); + let message = MessageBuilder::new(&self.uuid, message) + .name(name) + .values(rendered_values) + .build(); + Ok(message) + } + + fn extend_with_context_information(values: &mut BTreeMap, + state: &State, + context: &BaseContext) { + values.insert(CONTEXT_UUID.to_string(), + context.uuid().to_hyphenated_string()); + values.insert(CONTEXT_LEN.to_string(), state.messages().len().to_string()); + if let Some(name) = context.name() { + values.insert(CONTEXT_NAME.to_string(), name.to_string()); + } + } + + fn execute(&self, _state: &State, _context: &BaseContext, responder: &mut ResponseSender) { + match self.render_message(_state, _context) { + Ok(message) => { + let response = Alert { + message: message, + inject_mode: self.inject_mode.clone(), + }; + responder.send_response(Response::Alert(response)); + } + Err(error) => { + error!("{}", error); + } + } + } } impl From for super::ActionType { @@ -55,3 +144,31 @@ impl Default for InjectMode { InjectMode::Log } } + +#[derive(Debug, Clone)] +pub struct Alert { + message: Message, + inject_mode: InjectMode, +} + +impl Alert { + pub fn message(&self) -> &Message { + &self.message + } +} + +impl Action for MessageAction { + fn on_opened(&self, _state: &State, _context: &BaseContext, responder: &mut ResponseSender) { + if self.when.on_opened { + trace!("MessageAction: on_opened()"); + self.execute(_state, _context, responder); + } + } + + fn on_closed(&self, _state: &State, _context: &BaseContext, responder: &mut ResponseSender) { + if self.when.on_closed { + trace!("MessageAction: on_closed()"); + self.execute(_state, _context, responder); + } + } +} diff --git a/src/action/message/renderer_context.rs b/src/config/action/message/renderer_context.rs similarity index 92% rename from src/action/message/renderer_context.rs rename to src/config/action/message/renderer_context.rs index 31b736f..929b4f2 100644 --- a/src/action/message/renderer_context.rs +++ b/src/config/action/message/renderer_context.rs @@ -5,12 +5,12 @@ use context::BaseContext; use uuid::Uuid; use rustc_serialize::json::{Json, ToJson}; use std::collections::BTreeMap; -use std::rc::Rc; +use std::sync::Arc; use super::{CONTEXT_LEN, CONTEXT_NAME, CONTEXT_UUID, MESSAGES}; pub struct RendererContext<'m, 'c> { - messages: &'m [Rc], + messages: &'m [Arc], context_name: Option<&'c String>, context_uuid: &'c Uuid, } @@ -39,7 +39,7 @@ impl<'m, 'c> ToJson for RendererContext<'m, 'c> { } } -fn rc_message_to_json(messages: &[Rc]) -> Json { +fn rc_message_to_json(messages: &[Arc]) -> Json { let mut array: Vec<&Message> = Vec::new(); for i in messages { array.push(i); diff --git a/src/config/action/message/test.rs b/src/config/action/message/test.rs index 77c6c4a..ef90f6b 100644 --- a/src/config/action/message/test.rs +++ b/src/config/action/message/test.rs @@ -1,21 +1,100 @@ -use message::Message; use config::action::message::MessageActionBuilder; use message::MessageBuilder; +use context::base::BaseContextBuilder; +use super::{CONTEXT_LEN, CONTEXT_NAME, CONTEXT_UUID}; + +use conditions::ConditionsBuilder; +use config; +use dispatcher::Response; +use dispatcher::response::MockResponseSender; +use state::State; +use action::Action; + +use env_logger; +use handlebars::Template; +use std::time::Duration; +use std::sync::Arc; +use uuid::Uuid; #[test] -fn test_given_message_when_it_is_created_from_a_message_action_then_every_fields_are_copied_into_it() { - let uuid = "uuid"; - let name = "name"; - let expected_message = MessageBuilder::new(uuid) - .name(Some(name)) - .pair("key1", "value1") - .pair("key2", "value1") - .build(); - let action = MessageActionBuilder::new(uuid) - .name(name) - .pair("key1", "value1") - .pair("key2", "value1") - .build(); - let message = Message::from(&action); - assert_eq!(&expected_message, &message); +fn test_given_a_message_action_when_it_is_executed_then_it_adds_the_name_and_uuid_of_the_context_to_the_message + () { + let mut responder = MockResponseSender::new(); + let name = Some("name".to_string()); + let base_context = { + let conditions = ConditionsBuilder::new(Duration::from_millis(100)).build(); + let uuid = Uuid::new_v4(); + BaseContextBuilder::new(uuid, conditions).name(name.clone()).build() + }; + let state = State::new(); + let message_action = { + let message = Template::compile("message".to_string()) + .ok() + .expect("Failed to compile a handlebars template"); + config::action::message::MessageActionBuilder::new("uuid", message).build() + }; + + message_action.on_closed(&state, &base_context, &mut responder); + assert_eq!(1, responder.0.len()); + let responses = responder.0; + if let &Response::Alert(ref response) = responses.get(0).unwrap() { + assert_eq!(name.as_ref().unwrap(), + response.message().get(CONTEXT_NAME).unwrap()); + assert_eq!(&base_context.uuid().to_hyphenated_string(), + response.message().get(CONTEXT_UUID).unwrap()); + assert_eq!("0", response.message().get(CONTEXT_LEN).unwrap()); + } else { + unreachable!(); + } +} + +#[test] +fn test_given_message_action_when_it_is_executed_then_it_uses_the_messages_to_render_the_message_and_additonal_templated_values + () { + let mut responder = MockResponseSender::new(); + let _ = env_logger::init(); + let name = Some("name".to_string()); + let base_context = { + let conditions = ConditionsBuilder::new(Duration::from_millis(100)).build(); + let uuid = Uuid::new_v4(); + BaseContextBuilder::new(uuid, conditions).name(name.clone()).build() + }; + let state = { + let messages = vec![Arc::new(MessageBuilder::new("uuid1", "message1") + .pair("key1", "value1") + .build()), + Arc::new(MessageBuilder::new("uuid2", "message2") + .pair("key2", "value2") + .build())]; + State::with_messages(messages) + }; + let message_action = { + let message = Template::compile("key1={{{messages.[0].values.key1}}} \ + key2={{{messages.[1].values.key2}}}" + .to_string()) + .ok() + .expect("Failed to compile a handlebars template"); + config::action::message::MessageActionBuilder::new("uuid", message) + .pair("message_num", + Template::compile("we have {{context_len}} messages".to_string()) + .ok() + .expect("Failed to compile a handlebars template")) + .build() + }; + + message_action.on_closed(&state, &base_context, &mut responder); + assert_eq!(1, responder.0.len()); + let responses = responder.0; + if let &Response::Alert(ref response) = responses.get(0).unwrap() { + assert_eq!(name.as_ref().unwrap(), + response.message().get(CONTEXT_NAME).unwrap()); + assert_eq!(&base_context.uuid().to_hyphenated_string(), + response.message().get(CONTEXT_UUID).unwrap()); + let message = response.message(); + assert_eq!("we have 2 messages", + message.get("message_num").expect("Failed to get an inserted key from a map")); + assert_eq!("key1=value1 key2=value2", message.message()); + } else { + unreachable!(); + } } diff --git a/src/config/action/mod.rs b/src/config/action/mod.rs index e51a52b..7d725a1 100644 --- a/src/config/action/mod.rs +++ b/src/config/action/mod.rs @@ -1,13 +1,30 @@ +use action::Action; +use state::State; +use context::BaseContext; +use dispatcher::response::ResponseSender; + pub use self::message::MessageAction; pub mod message; mod deser; -#[derive(Clone, Debug, PartialEq)] pub enum ActionType { Message(self::message::MessageAction), } +impl Action for ActionType { + fn on_opened(&self, state: &State, context: &BaseContext, responder: &mut ResponseSender) { + match *self { + ActionType::Message(ref action) => action.on_opened(state, context, responder), + } + } + fn on_closed(&self, state: &State, context: &BaseContext, responder: &mut ResponseSender) { + match *self { + ActionType::Message(ref action) => action.on_closed(state, context, responder), + } + } +} + #[derive(Clone, Debug, PartialEq)] pub struct ExecCondition { pub on_opened: bool, diff --git a/src/config/deser.rs b/src/config/deser.rs index 9e1bf0a..ac295ee 100644 --- a/src/config/deser.rs +++ b/src/config/deser.rs @@ -1,4 +1,4 @@ -use config::Context; +use config::ContextConfig; use serde::de::{Deserialize, Deserializer, MapVisitor, Error, Visitor}; use handlebars::Template; @@ -6,8 +6,8 @@ use uuid::Uuid; const FIELDS: &'static [&'static str] = &["name", "uuid", "conditions", "actions"]; -impl Deserialize for Context { - fn deserialize(deserializer: &mut D) -> Result +impl Deserialize for ContextConfig { + fn deserialize(deserializer: &mut D) -> Result where D: Deserializer { deserializer.visit_struct("Context", FIELDS, ContextVisitor) @@ -102,9 +102,9 @@ impl ContextVisitor { } impl Visitor for ContextVisitor { - type Value = Context; + type Value = ContextConfig; - fn visit_map(&mut self, mut visitor: V) -> Result + fn visit_map(&mut self, mut visitor: V) -> Result where V: MapVisitor { let mut name = None; @@ -129,7 +129,7 @@ impl Visitor for ContextVisitor { try!(visitor.end()); - Ok(Context { + Ok(ContextConfig { name: name, uuid: uuid, conditions: conditions.unwrap(), @@ -144,7 +144,7 @@ mod test { use config::action::{ActionType, ExecCondition}; use config::action::message::MessageActionBuilder; use conditions::ConditionsBuilder; - use config::Context; + use config::ContextConfig; use handlebars::Template; use serde_json::from_str; use uuid::Uuid; @@ -180,8 +180,7 @@ mod test { } "#; - let result = from_str::(text); - println!("{:?}", &result); + let result = from_str::(text); let expected_name = "TEST_NAME".to_string(); let expected_uuid = Uuid::parse_str("86ca9f93-84fb-4813-b037-6526f7a585a3").ok().unwrap(); let expected_conditions = ConditionsBuilder::new(Duration::from_millis(100)) @@ -202,30 +201,31 @@ mod test { message) .when(expected_exec_cond) .build())]; - let context = result.ok().expect("Failed to deserialize a valid Context"); + let context = result.ok().expect("Failed to deserialize a valid ContextConfig"); assert_eq!(&Some(expected_name), &context.name); assert_eq!(&expected_uuid, &context.uuid); assert_eq!(&expected_conditions, &context.conditions); - assert_eq!(&expected_actions, &context.actions); + assert_eq!(&expected_actions.len(), &context.actions.len()); } #[test] fn test_given_config_context_when_it_does_not_have_uuid_then_it_cannot_be_deserialized() { let text = r#"{ "conditions": { "timeout": 100 }}"#; - let result = from_str::(text); - println!("{:?}", &result); - let _ = result.err().expect("Successfully deserialized a config context without an uuid key"); + let result = from_str::(text); + let _ = result.err() + .expect("Successfully deserialized a config context without an uuid key"); } #[test] - fn test_given_config_context_when_it_contains_an_unknown_key_then_it_cannot_be_deserialized() { + fn test_given_config_context_when_it_contains_an_unknown_key_then_it_cannot_be_deserialized + () { let text = r#" {"uuid": "86ca9f93-84fb-4813-b037-6526f7a585a3", "conditions": { "timeout": 100}, "unknown": "unknown" }"#; - let result = from_str::(text); - println!("{:?}", &result); - let _ = result.err().expect("Successfully deserialized a config context with an unknown key"); + let result = from_str::(text); + let _ = result.err() + .expect("Successfully deserialized a config context with an unknown key"); } #[test] @@ -240,11 +240,10 @@ mod test { } "#; - let result = from_str::(text); - println!("{:?}", &result); + let result = from_str::(text); let expected_uuid = Uuid::parse_str("86ca9f93-84fb-4813-b037-6526f7a585a3").ok().unwrap(); let expected_conditions = ConditionsBuilder::new(Duration::from_millis(100)).build(); - let context = result.ok().expect("Failed to deserialize a valid Context"); + let context = result.ok().expect("Failed to deserialize a valid ContextConfig"); assert_eq!(&expected_uuid, &context.uuid); assert_eq!(&expected_conditions, &context.conditions); } @@ -261,10 +260,10 @@ mod test { } "#; - let result = from_str::(text); - println!("{:?}", &result); + let result = from_str::(text); let _ = result.err() - .expect("Successfully deserialized an invalid Context (UUID is invalid)"); + .expect("Successfully deserialized an invalid ContextConfig (UUID is \ + invalid)"); } #[test] @@ -279,9 +278,8 @@ mod test { } "#; let expected_context_id = "{{HOST}}{{PROGRAM}}".to_string(); - let result = from_str::(text); - println!("{:?}", &result); - let context = result.ok().expect("Failed to deserialize a valid Context"); + let result = from_str::(text); + let context = result.ok().expect("Failed to deserialize a valid ContextConfig"); assert_eq!(&expected_context_id, &context.context_id.as_ref().unwrap().to_string()); } diff --git a/src/config/mod.rs b/src/config/mod.rs index d3b1696..b8d4ade 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -7,8 +7,7 @@ use conditions::Conditions; mod deser; pub mod action; -#[derive(Debug, Clone)] -pub struct Context { +pub struct ContextConfig { pub name: Option, pub uuid: Uuid, pub conditions: Conditions, @@ -16,7 +15,7 @@ pub struct Context { pub actions: Vec, } -pub struct ContextBuilder { +pub struct ContextConfigBuilder { name: Option, uuid: Uuid, conditions: Conditions, @@ -24,9 +23,9 @@ pub struct ContextBuilder { actions: Vec, } -impl ContextBuilder { - pub fn new(uuid: Uuid, conditions: Conditions) -> ContextBuilder { - ContextBuilder { +impl ContextConfigBuilder { + pub fn new(uuid: Uuid, conditions: Conditions) -> ContextConfigBuilder { + ContextConfigBuilder { name: None, uuid: uuid, conditions: conditions, @@ -35,28 +34,28 @@ impl ContextBuilder { } } - pub fn context_id(&mut self, context_id: Option