counters + singleton pattern implementation
parent
14b62814dc
commit
a844b32e27
|
@ -1,8 +1,7 @@
|
|||
use crate::domain::{Action, LogMessage, LogPort, ModuleArgs, Record, Value};
|
||||
use crate::domain::{Action, LogMessage, LogPort, ModuleArgs, Record, Singleton, Value};
|
||||
use crate::singleton_borrow;
|
||||
use regex::Regex;
|
||||
use std::cell::RefCell;
|
||||
use std::ops::{Index, Range};
|
||||
use std::rc::Rc;
|
||||
|
||||
type LogFormat = fn(&str) -> LogMessage;
|
||||
|
||||
|
@ -16,14 +15,14 @@ const INFO_LOG_FORMAT: LogFormat = |s| LogMessage::INFO(&s);
|
|||
const DEBUG_LOG_FORMAT: LogFormat = |s| LogMessage::DEBUG(&s);
|
||||
|
||||
pub struct Log {
|
||||
logger: Rc<RefCell<dyn LogPort>>,
|
||||
logger: Singleton<dyn LogPort>,
|
||||
log_format: LogFormat,
|
||||
template: String,
|
||||
var_locations: Vec<Range<usize>>,
|
||||
}
|
||||
|
||||
impl Log {
|
||||
pub fn from_args(mut args: ModuleArgs, logger: Rc<RefCell<dyn LogPort>>) -> Log {
|
||||
pub fn from_args(mut args: ModuleArgs, logger: Singleton<dyn LogPort>) -> Log {
|
||||
let log_format = match args.remove("level") {
|
||||
Some(Value::Str(l)) => match l.as_ref() {
|
||||
"EMERG" => EMERG_LOG_FORMAT,
|
||||
|
@ -82,28 +81,25 @@ impl Log {
|
|||
impl Action for Log {
|
||||
fn act(&mut self, record: &mut Record) -> Result<(), ()> {
|
||||
let message = self.message_with_variables_from_record(record);
|
||||
(self.logger.borrow_mut()).write((self.log_format)(&message))
|
||||
singleton_borrow!(self.logger).write((self.log_format)(&message))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
mod tests {
|
||||
use super::Log;
|
||||
use crate::assert_log_match;
|
||||
use crate::domain::test_util::*;
|
||||
use crate::domain::{Action, ModuleArgs, Record, Value};
|
||||
use crate::domain::test_util::FakeLog;
|
||||
use crate::domain::{Action, ModuleArgs, Record, Singleton, Value};
|
||||
use crate::{assert_log_match, singleton_new, singleton_share};
|
||||
use core::panic;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "The Log action needs a message template in “message”")]
|
||||
fn arg_message_is_mandatory() {
|
||||
let args: ModuleArgs = HashMap::new();
|
||||
let logger = Rc::new(RefCell::new(FakeLog::new(Vec::new())));
|
||||
Log::from_args(args, logger.clone());
|
||||
let logger = singleton_new!(FakeLog::new(Vec::new()));
|
||||
Log::from_args(args, singleton_share!(logger));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -166,14 +162,14 @@ mod tests {
|
|||
level: Option<&str>,
|
||||
logs: Vec<Result<Record, ()>>,
|
||||
vars: Vec<(&str, &str)>,
|
||||
) -> (Log, Rc<RefCell<FakeLog>>, Record) {
|
||||
) -> (Log, Singleton<FakeLog>, Record) {
|
||||
let mut args: ModuleArgs = HashMap::new();
|
||||
args.insert("message".to_string(), Value::Str(template.to_string()));
|
||||
if let Some(l) = level {
|
||||
args.insert("level".to_string(), Value::Str(l.to_string()));
|
||||
}
|
||||
let logger = Rc::new(RefCell::new(FakeLog::new(logs)));
|
||||
let log = Log::from_args(args, logger.clone());
|
||||
let logger = singleton_new!(FakeLog::new(logs));
|
||||
let log = Log::from_args(args, singleton_share!(logger));
|
||||
let mut record: Record = HashMap::new();
|
||||
for (name, value) in vars {
|
||||
record.insert(name.to_string(), Value::Str(value.to_string()));
|
||||
|
|
|
@ -1,4 +1,63 @@
|
|||
mod counter_raise;
|
||||
pub use self::counter_raise::*;
|
||||
mod counter_reset;
|
||||
pub use self::counter_reset::*;
|
||||
mod log;
|
||||
pub use self::log::*;
|
||||
mod noop;
|
||||
pub use self::noop::*;
|
||||
|
||||
use crate::domain::{Counters, CountersPort, ModuleArgs, Singleton, Value};
|
||||
use chrono::Duration;
|
||||
|
||||
pub struct CounterAction<C: CountersPort> {
|
||||
counters: Singleton<Counters<C>>,
|
||||
counter_name: String,
|
||||
counter_key: String,
|
||||
save_into: Option<String>,
|
||||
duration: Option<Duration>,
|
||||
}
|
||||
|
||||
impl<C: CountersPort> CounterAction<C> {
|
||||
pub fn from_args<X: CountersPort>(
|
||||
mut args: ModuleArgs,
|
||||
counters: Singleton<Counters<X>>,
|
||||
action_name: &str,
|
||||
duration_name: &str,
|
||||
) -> CounterAction<X> {
|
||||
let counter_name = remove_acceptable_key(&mut args, "counter").expect(&format!(
|
||||
"The {} action needs a counter name in “counter”",
|
||||
action_name
|
||||
));
|
||||
let counter_key = remove_acceptable_key(&mut args, "for").expect(&format!(
|
||||
"The {} action needs a counter key in “for”",
|
||||
action_name
|
||||
));
|
||||
let save_into = remove_acceptable_key(&mut args, "save");
|
||||
let duration = match args.remove(duration_name) {
|
||||
None => None,
|
||||
Some(Value::Int(i)) => Some(Duration::seconds(i as i64)),
|
||||
_ => panic!(format!(
|
||||
"The {} only accepts a number of seconds in “{}”",
|
||||
action_name, duration_name
|
||||
)),
|
||||
};
|
||||
CounterAction {
|
||||
counters,
|
||||
counter_name,
|
||||
counter_key,
|
||||
save_into,
|
||||
duration,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_acceptable_key(args: &mut ModuleArgs, key: &str) -> Option<String> {
|
||||
match args.remove(key) {
|
||||
None => None,
|
||||
Some(Value::Str(s)) => Some(s),
|
||||
Some(Value::Int(i)) => Some(format!("{}", i)),
|
||||
Some(Value::Date(d)) => Some(format!("{}", d.timestamp())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,21 +9,59 @@ mod module;
|
|||
pub use self::module::*;
|
||||
mod workflow;
|
||||
pub use self::workflow::*;
|
||||
mod counter;
|
||||
pub use self::counter::*;
|
||||
|
||||
use chrono::DateTime;
|
||||
use chrono::{DateTime, Utc};
|
||||
use std::collections::HashMap;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum Value {
|
||||
Bool(bool),
|
||||
Str(String),
|
||||
Int(isize),
|
||||
Date(DateTime<chrono::Utc>),
|
||||
Date(DateTime<Utc>),
|
||||
Map(HashMap<String, Value>),
|
||||
List(Vec<Value>),
|
||||
}
|
||||
|
||||
impl Hash for Value {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
match self {
|
||||
Value::Bool(b) => b.hash(state),
|
||||
Value::Str(s) => s.hash(state),
|
||||
Value::Int(i) => i.hash(state),
|
||||
Value::Date(d) => d.hash(state),
|
||||
Value::Map(h) => h.keys().collect::<Vec<&String>>().sort().hash(state),
|
||||
Value::List(v) => v.hash(state),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub type Record = HashMap<String, Value>;
|
||||
pub type Singleton<T> = std::rc::Rc<std::cell::RefCell<T>>;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! singleton_new {
|
||||
( $s:expr ) => {
|
||||
std::rc::Rc::new(std::cell::RefCell::new($s))
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! singleton_share {
|
||||
( $s:expr ) => {
|
||||
$s.clone()
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! singleton_borrow {
|
||||
( $s:expr ) => {
|
||||
(*$s).borrow_mut()
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_util;
|
||||
|
|
|
@ -66,7 +66,7 @@ pub type ModuleArgs = HashMap<String, Value>;
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Module, Modules, Record, Value};
|
||||
use crate::domain::test_util::*;
|
||||
use crate::domain::test_util::{FakeAction, FakeFilter, ACT_NAME, FLT_NAME};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
|
@ -80,9 +80,9 @@ mod tests {
|
|||
let mut module = Module::new(ACT_NAME.to_string(), HashMap::new(), &mods).unwrap();
|
||||
|
||||
// Then
|
||||
assert!(module.run(&mut record) == Ok(true));
|
||||
assert_eq!(module.run(&mut record), Ok(true));
|
||||
assert!(record.contains_key(ACT_NAME));
|
||||
assert!(record[ACT_NAME] == Value::Int(1));
|
||||
assert_eq!(record[ACT_NAME], Value::Int(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -96,8 +96,8 @@ mod tests {
|
|||
let mut module = Module::new(FLT_NAME.to_string(), HashMap::new(), &mods).unwrap();
|
||||
|
||||
// Then
|
||||
assert!(module.run(&mut record) == Ok(false));
|
||||
assert_eq!(module.run(&mut record), Ok(false));
|
||||
assert!(record.contains_key(FLT_NAME));
|
||||
assert!(record[FLT_NAME] == Value::Int(1));
|
||||
assert_eq!(record[FLT_NAME], Value::Int(1));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::domain::{Action, Filter, LogMessage, LogPort, Record, Value};
|
||||
use crate::domain::{Action, CounterData, CounterRef, CountersPort, Filter, LogMessage, LogPort, Record, Singleton, Value};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub const ACT_NAME: &str = "fake_action";
|
||||
pub const FLT_NAME: &str = "fake_filter";
|
||||
|
@ -66,3 +67,28 @@ impl LogPort for FakeLog {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FakeCountersAdapter {
|
||||
pub counters: Singleton<HashMap<(String, Value), CounterData>>,
|
||||
}
|
||||
impl CountersPort for FakeCountersAdapter {
|
||||
fn modify(
|
||||
&mut self,
|
||||
entry: CounterRef,
|
||||
data: CounterData,
|
||||
mut f: impl FnMut(&mut CounterData, CounterData) -> usize,
|
||||
) -> usize {
|
||||
let k = (entry.0.to_string(), entry.1.clone());
|
||||
if !singleton_borrow!(self.counters).contains_key(&k) {
|
||||
singleton_borrow!(self.counters).insert(k.clone(), (0, None));
|
||||
}
|
||||
f(singleton_borrow!(self.counters).get_mut(&k).unwrap(), data)
|
||||
}
|
||||
fn remove(&mut self, entry: CounterRef) -> Option<CounterData> {
|
||||
let k = (entry.0.to_string(), entry.1.clone());
|
||||
singleton_borrow!(self.counters).remove(&k)
|
||||
}
|
||||
fn remove_if(&mut self, predicate: impl Fn(&CounterData) -> bool) {
|
||||
singleton_borrow!(self.counters).retain(|_, v| !predicate(v));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -163,7 +163,7 @@ fn node_wants_chain(
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::domain::test_util::*;
|
||||
use crate::domain::test_util::{FakeAction, FakeFilter, ACT_NAME, FLT_NAME};
|
||||
use crate::domain::{Chain, Config, Modules, Record, Step, Value, Workflow};
|
||||
use indexmap::IndexMap;
|
||||
use std::collections::HashMap;
|
||||
|
@ -208,12 +208,12 @@ mod tests {
|
|||
wf.run(&mut record);
|
||||
|
||||
// Then
|
||||
assert!(wf.nodes.len() == 1);
|
||||
assert!(wf.nodes[0].name == "chain1[0]:fake_action");
|
||||
assert_eq!(wf.nodes.len(), 1);
|
||||
assert_eq!(wf.nodes[0].name, "chain1[0]:fake_action");
|
||||
assert!(wf.nodes[0].then_dest < 0);
|
||||
assert!(wf.nodes[0].else_dest < 0);
|
||||
assert!(record[ACT_NAME] == Value::Int(1));
|
||||
assert!(record.get(FLT_NAME) == None);
|
||||
assert_eq!(record[ACT_NAME], Value::Int(1));
|
||||
assert_eq!(record.get(FLT_NAME), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -252,15 +252,15 @@ mod tests {
|
|||
wf.run(&mut record);
|
||||
|
||||
// Then
|
||||
assert!(wf.nodes.len() == 2);
|
||||
assert!(wf.nodes[0].name == "chain1[0]:fake_filter");
|
||||
assert!(wf.nodes[1].name == "chain2[0]:fake_action");
|
||||
assert_eq!(wf.nodes.len(), 2);
|
||||
assert_eq!(wf.nodes[0].name, "chain1[0]:fake_filter");
|
||||
assert_eq!(wf.nodes[1].name, "chain2[0]:fake_action");
|
||||
assert!(wf.nodes[0].then_dest < 0);
|
||||
assert!(wf.nodes[0].else_dest == 1);
|
||||
assert_eq!(wf.nodes[0].else_dest, 1);
|
||||
assert!(wf.nodes[1].then_dest < 0);
|
||||
assert!(wf.nodes[1].else_dest < 0);
|
||||
assert!(record[ACT_NAME] == Value::Int(1));
|
||||
assert!(record[FLT_NAME] == Value::Int(1));
|
||||
assert_eq!(record[ACT_NAME], Value::Int(1));
|
||||
assert_eq!(record[FLT_NAME], Value::Int(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -299,14 +299,14 @@ mod tests {
|
|||
wf.run(&mut record);
|
||||
|
||||
// Then
|
||||
assert!(wf.nodes.len() == 2);
|
||||
assert!(wf.nodes[0].name == "chain1[0]:fake_filter");
|
||||
assert!(wf.nodes[1].name == "chain2[0]:fake_action");
|
||||
assert_eq!(wf.nodes.len(), 2);
|
||||
assert_eq!(wf.nodes[0].name, "chain1[0]:fake_filter");
|
||||
assert_eq!(wf.nodes[1].name, "chain2[0]:fake_action");
|
||||
assert!(wf.nodes[0].then_dest < 0);
|
||||
assert!(wf.nodes[0].else_dest == 1);
|
||||
assert_eq!(wf.nodes[0].else_dest, 1);
|
||||
assert!(wf.nodes[1].then_dest < 0);
|
||||
assert!(wf.nodes[1].else_dest < 0);
|
||||
assert!(record[ACT_NAME] == Value::Int(1));
|
||||
assert!(record[FLT_NAME] == Value::Int(1));
|
||||
assert_eq!(record[ACT_NAME], Value::Int(1));
|
||||
assert_eq!(record[FLT_NAME], Value::Int(1));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -324,14 +324,20 @@ mod tests {
|
|||
let conf = SerdeConfigAdapter::from_json(json).config;
|
||||
|
||||
// Then
|
||||
assert!(conf.actions.len() == 2);
|
||||
assert!(conf.actions.get_index(0).unwrap().0 == "Detect request errors with Nextcloud");
|
||||
assert!(
|
||||
conf.actions.get_index(1).unwrap().0
|
||||
== "… Report insufficient buffer-size for Nextcloud QUERY_STRING"
|
||||
assert_eq!(conf.actions.len(), 2);
|
||||
assert_eq!(
|
||||
conf.actions.get_index(0).unwrap().0,
|
||||
"Detect request errors with Nextcloud"
|
||||
);
|
||||
assert!(conf.actions.get_index(0).unwrap().1.len() == 3);
|
||||
assert!(conf.actions.get_index(0).unwrap().1[1].module == String::from("filter_pcre"));
|
||||
assert!(conf.options.get("debug") == Some(&Value::Bool(false)));
|
||||
assert_eq!(
|
||||
conf.actions.get_index(1).unwrap().0,
|
||||
"… Report insufficient buffer-size for Nextcloud QUERY_STRING"
|
||||
);
|
||||
assert_eq!(conf.actions.get_index(0).unwrap().1.len(), 3);
|
||||
assert_eq!(
|
||||
conf.actions.get_index(0).unwrap().1[1].module,
|
||||
String::from("filter_pcre")
|
||||
);
|
||||
assert_eq!(conf.options.get("debug"), Some(&Value::Bool(false)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::domain::{LogMessage, LogPort, Record, Value};
|
||||
use chrono::DateTime;
|
||||
use chrono::{DateTime, Utc};
|
||||
use std::collections::HashMap;
|
||||
use std::iter::FromIterator;
|
||||
use systemd::journal::{print, Journal, OpenOptions};
|
||||
|
@ -13,7 +13,7 @@ const INT_MAPPER: JournalFieldMapper = |s| {
|
|||
.unwrap_or(Value::Str(s))
|
||||
};
|
||||
const DATE_MAPPER: JournalFieldMapper = |s| {
|
||||
s.parse::<DateTime<chrono::Utc>>()
|
||||
s.parse::<DateTime<Utc>>()
|
||||
.map(|d| Value::Date(d))
|
||||
.unwrap_or(Value::Str(s))
|
||||
};
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
pub mod config;
|
||||
pub mod counter;
|
||||
pub mod log;
|
||||
|
|
43
src/main.rs
43
src/main.rs
|
@ -1,29 +1,50 @@
|
|||
#[macro_use]
|
||||
mod domain;
|
||||
mod infra;
|
||||
mod service;
|
||||
|
||||
use crate::domain::action::Log;
|
||||
use crate::domain::action::Noop;
|
||||
use crate::domain::action::{CounterRaise, CounterReset, Log, Noop};
|
||||
use crate::domain::filter::Equals;
|
||||
use crate::domain::{ConfigPort, Modules, Workflow};
|
||||
use crate::domain::{ConfigPort, Counters, Modules, Workflow};
|
||||
use crate::infra::config::ConfFile;
|
||||
use crate::infra::counter::InMemoryCounterAdapter;
|
||||
use crate::infra::log::SystemdLogAdapter;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
type CountersImpl = InMemoryCounterAdapter;
|
||||
type LogImpl = SystemdLogAdapter;
|
||||
|
||||
fn main() {
|
||||
let mut conf = ConfFile::from_filesystem().to_config();
|
||||
let log = Rc::new(RefCell::new(
|
||||
SystemdLogAdapter::open().expect("Error initializing systemd"),
|
||||
));
|
||||
let log = singleton_new!(LogImpl::open().expect("Error initializing systemd"));
|
||||
let mut modules = Modules::new();
|
||||
let counters = singleton_new!(Counters::<CountersImpl>::new(CountersImpl::new()));
|
||||
let gets_moved_into_closure = singleton_share!(counters);
|
||||
modules.register_action(
|
||||
"action_noop".to_string(),
|
||||
Box::new(move |a| Box::new(Noop::from_args(a))),
|
||||
"action_counterRaise".to_string(),
|
||||
Box::new(move |a| {
|
||||
Box::new(CounterRaise::<CountersImpl>::from_args(
|
||||
a,
|
||||
singleton_share!(gets_moved_into_closure), // clone for each call of the constructor
|
||||
))
|
||||
}),
|
||||
);
|
||||
let gets_moved_into_closure = singleton_share!(counters);
|
||||
modules.register_action(
|
||||
"action_counterReset".to_string(),
|
||||
Box::new(move |a| {
|
||||
Box::new(CounterReset::<CountersImpl>::from_args(
|
||||
a,
|
||||
singleton_share!(gets_moved_into_closure), // clone for each call of the constructor
|
||||
))
|
||||
}),
|
||||
);
|
||||
modules.register_action(
|
||||
"action_log".to_string(),
|
||||
Box::new(move |a| Box::new(Log::from_args(a, log.clone()))),
|
||||
Box::new(move |a| Box::new(Log::from_args(a, singleton_share!(log)))),
|
||||
);
|
||||
modules.register_action(
|
||||
"action_noop".to_string(),
|
||||
Box::new(move |a| Box::new(Noop::from_args(a))),
|
||||
);
|
||||
modules.register_filter(
|
||||
"filter_equals".to_string(),
|
||||
|
|
Loading…
Reference in New Issue