counters + singleton pattern implementation

rust-rewrite
Yves G 2021-03-13 19:37:19 +01:00
parent 14b62814dc
commit a844b32e27
10 changed files with 211 additions and 64 deletions

View File

@ -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()));

View File

@ -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,
}
}

View File

@ -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;

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -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)));
}
}

View File

@ -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))
};

View File

@ -1,2 +1,3 @@
pub mod config;
pub mod counter;
pub mod log;

View File

@ -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(),