big cleanup + simplified dependency inversion + action_log
parent
5e120a05bb
commit
14b62814dc
|
@ -9,8 +9,8 @@ edition = "2018"
|
|||
[dependencies]
|
||||
chrono = "0.4"
|
||||
indexmap = { version = "1.3", features = ["serde-1"] }
|
||||
inventory = "0.1"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_yaml = "0.8"
|
||||
systemd = "0.8"
|
||||
regex = "1"
|
||||
|
|
|
@ -0,0 +1,195 @@
|
|||
use crate::domain::{Action, LogMessage, LogPort, ModuleArgs, Record, Value};
|
||||
use regex::Regex;
|
||||
use std::cell::RefCell;
|
||||
use std::ops::{Index, Range};
|
||||
use std::rc::Rc;
|
||||
|
||||
type LogFormat = fn(&str) -> LogMessage;
|
||||
|
||||
const EMERG_LOG_FORMAT: LogFormat = |s| LogMessage::EMERG(&s);
|
||||
const ALERT_LOG_FORMAT: LogFormat = |s| LogMessage::ALERT(&s);
|
||||
const CRIT_LOG_FORMAT: LogFormat = |s| LogMessage::CRIT(&s);
|
||||
const ERR_LOG_FORMAT: LogFormat = |s| LogMessage::ERR(&s);
|
||||
const WARNING_LOG_FORMAT: LogFormat = |s| LogMessage::WARNING(&s);
|
||||
const NOTICE_LOG_FORMAT: LogFormat = |s| LogMessage::NOTICE(&s);
|
||||
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>>,
|
||||
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 {
|
||||
let log_format = match args.remove("level") {
|
||||
Some(Value::Str(l)) => match l.as_ref() {
|
||||
"EMERG" => EMERG_LOG_FORMAT,
|
||||
"ALERT" => ALERT_LOG_FORMAT,
|
||||
"CRIT" => CRIT_LOG_FORMAT,
|
||||
"ERR" => ERR_LOG_FORMAT,
|
||||
"WARNING" => WARNING_LOG_FORMAT,
|
||||
"NOTICE" => NOTICE_LOG_FORMAT,
|
||||
"INFO" => INFO_LOG_FORMAT,
|
||||
"DEBUG" => DEBUG_LOG_FORMAT,
|
||||
_ => {
|
||||
eprintln!("Unknown error level: {}; Using INFO", l);
|
||||
INFO_LOG_FORMAT
|
||||
}
|
||||
},
|
||||
_ => INFO_LOG_FORMAT,
|
||||
};
|
||||
let template = match args.remove("message") {
|
||||
Some(Value::Str(s)) => s,
|
||||
_ => panic!("The Log action needs a message template in “message”"),
|
||||
};
|
||||
let var_locations = Regex::new(r"\{\w+\}")
|
||||
.unwrap()
|
||||
.find_iter(&template)
|
||||
.map(|m| m.range())
|
||||
.collect();
|
||||
Log {
|
||||
logger,
|
||||
log_format,
|
||||
template,
|
||||
var_locations,
|
||||
}
|
||||
}
|
||||
|
||||
fn message_with_variables_from_record(&self, record: &mut Record) -> String {
|
||||
let tpl = &self.template;
|
||||
if self.var_locations.len() == 0 {
|
||||
return tpl.clone();
|
||||
}
|
||||
let mut message = String::with_capacity(tpl.len() * 2);
|
||||
let mut last_index = 0;
|
||||
for Range { start, end } in self.var_locations.iter() {
|
||||
message.push_str(tpl.index(last_index..*start));
|
||||
if let Some(Value::Str(s)) = record.get(tpl.index((start + 1)..(end - 1))) {
|
||||
message.push_str(&s);
|
||||
} else {
|
||||
message.push_str(tpl.index(*start..*end));
|
||||
}
|
||||
last_index = *end;
|
||||
}
|
||||
message.push_str(tpl.index(last_index..tpl.len()));
|
||||
message
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
#[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 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());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_log_level_is_info() {
|
||||
let (mut log, logger, mut record) = create_log_logger_record("x", None, Vec::new(), Vec::new());
|
||||
log.act(&mut record).unwrap();
|
||||
assert_log_match!(logger, Some((ref l, _)), l == "INFO");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_levels_are_recognized() {
|
||||
let mut levels = vec![
|
||||
"EMERG", "ALERT", "CRIT", "ERR", "WARNING", "NOTICE", "INFO", "DEBUG",
|
||||
];
|
||||
levels.iter_mut().for_each(|level| {
|
||||
let (mut log, logger, mut record) =
|
||||
create_log_logger_record("x", Some(level), Vec::new(), Vec::new());
|
||||
log.act(&mut record).unwrap();
|
||||
assert_log_match!(logger, Some((ref l, _)), l == level);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn template_without_placeholder_is_rendered_as_is() {
|
||||
let (mut log, logger, mut record) = create_log_logger_record("x", None, Vec::new(), Vec::new());
|
||||
log.act(&mut record).unwrap();
|
||||
assert_log_match!(logger, Some((_, ref m)), m == "x");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn placeholder_without_variable_is_rendered_as_is() {
|
||||
let (mut log, logger, mut record) =
|
||||
create_log_logger_record("x{y}z", None, Vec::new(), Vec::new());
|
||||
log.act(&mut record).unwrap();
|
||||
assert_log_match!(logger, Some((_, ref m)), m == "x{y}z");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn placeholder_with_variable_is_replaced() {
|
||||
let (mut log, logger, mut record) =
|
||||
create_log_logger_record("x{y}z", None, Vec::new(), vec![("y", "y")]);
|
||||
log.act(&mut record).unwrap();
|
||||
assert_log_match!(logger, Some((_, ref m)), m == "xyz");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_variables_are_replaced() {
|
||||
let (mut log, logger, mut record) = create_log_logger_record(
|
||||
"{x}{a}{yy}-{zzz}",
|
||||
None,
|
||||
Vec::new(),
|
||||
vec![("x", "x"), ("yy", "y"), ("zzz", "z")],
|
||||
);
|
||||
log.act(&mut record).unwrap();
|
||||
assert_log_match!(logger, Some((_, ref m)), m == "x{a}y-z");
|
||||
}
|
||||
|
||||
fn create_log_logger_record(
|
||||
template: &str,
|
||||
level: Option<&str>,
|
||||
logs: Vec<Result<Record, ()>>,
|
||||
vars: Vec<(&str, &str)>,
|
||||
) -> (Log, Rc<RefCell<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 mut record: Record = HashMap::new();
|
||||
for (name, value) in vars {
|
||||
record.insert(name.to_string(), Value::Str(value.to_string()));
|
||||
}
|
||||
(log, logger, record)
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! assert_log_match {
|
||||
( $l:ident , $x:pat , $y:expr ) => {
|
||||
let logger_contents = (*$l).borrow_mut();
|
||||
if let $x = logger_contents.last_write {
|
||||
assert!($y);
|
||||
} else {
|
||||
panic!("There should be a log entry in this test.");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,2 +1,4 @@
|
|||
mod log;
|
||||
pub use self::log::*;
|
||||
mod noop;
|
||||
pub use self::noop::*;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::domain::{Action, ModuleArgs, Record};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Noop {}
|
||||
|
||||
impl Noop {
|
||||
|
@ -10,7 +9,7 @@ impl Noop {
|
|||
}
|
||||
|
||||
impl Action for Noop {
|
||||
fn act(&self, _record: &mut Record) -> Result<(), ()> {
|
||||
fn act(&mut self, _record: &mut Record) -> Result<(), ()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +30,7 @@ mod tests {
|
|||
fn noop_does_nothing() {
|
||||
// Given
|
||||
let (args, mut record) = generate_empty_args_record();
|
||||
let action = Noop::from_args(args);
|
||||
let mut action = Noop::from_args(args);
|
||||
|
||||
// Then
|
||||
assert_eq!((), action.act(&mut record).unwrap());
|
||||
|
|
|
@ -3,7 +3,7 @@ use indexmap::IndexMap;
|
|||
use std::collections::HashMap;
|
||||
|
||||
pub trait ConfigPort {
|
||||
fn get(&self) -> &Config;
|
||||
fn get(&mut self) -> &mut Config;
|
||||
}
|
||||
|
||||
pub struct Config {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::domain::{Filter, ModuleArgs, Record, Value};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Equals {
|
||||
field: String,
|
||||
value: Value,
|
||||
|
@ -21,7 +20,7 @@ impl Equals {
|
|||
}
|
||||
|
||||
impl Filter for Equals {
|
||||
fn filter(&self, record: &mut Record) -> bool {
|
||||
fn filter(&mut self, record: &mut Record) -> bool {
|
||||
match (record.get(&self.field), &self.value) {
|
||||
(Some(ref v1), ref v2) => v1 == v2,
|
||||
(None, _) => false,
|
||||
|
@ -36,6 +35,124 @@ mod tests {
|
|||
use chrono::Utc;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
fn filter_equals_returns_true_for_identical_bools() {
|
||||
// Given
|
||||
let (args, mut record) =
|
||||
generate_args_record_equal(String::from("a_boolean"), Value::Bool(false));
|
||||
let mut filter = Equals::from_args(args);
|
||||
|
||||
// Then
|
||||
assert!(filter.filter(&mut record));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_equals_returns_true_for_identical_strings() {
|
||||
// Given
|
||||
let (args, mut record) =
|
||||
generate_args_record_equal(String::from("a_string"), Value::Str(String::from("Hello!")));
|
||||
let mut filter = Equals::from_args(args);
|
||||
|
||||
// Then
|
||||
assert!(filter.filter(&mut record));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_equals_returns_true_for_identical_ints() {
|
||||
// Given
|
||||
let (args, mut record) = generate_args_record_equal(String::from("an_integer"), Value::Int(2));
|
||||
let mut filter = Equals::from_args(args);
|
||||
|
||||
// Then
|
||||
assert!(filter.filter(&mut record));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_equals_returns_true_for_identical_dates() {
|
||||
// Given
|
||||
let (args, mut record) =
|
||||
generate_args_record_equal(String::from("a_date"), Value::Date(Utc::now()));
|
||||
let mut filter = Equals::from_args(args);
|
||||
|
||||
// Then
|
||||
assert!(filter.filter(&mut record));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_equals_returns_false_for_different_bools() {
|
||||
// Given
|
||||
let (args, mut record) = generate_args_record_custom(
|
||||
String::from("a_boolean"),
|
||||
Value::Bool(true),
|
||||
String::from("a_boolean"),
|
||||
Value::Bool(false),
|
||||
);
|
||||
let mut filter = Equals::from_args(args);
|
||||
|
||||
// Then
|
||||
assert!(!filter.filter(&mut record));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_equals_returns_false_for_different_strings() {
|
||||
// Given
|
||||
let (args, mut record) = generate_args_record_custom(
|
||||
String::from("a_string"),
|
||||
Value::Str(String::from("Hello!")),
|
||||
String::from("a_string"),
|
||||
Value::Str(String::from("World!")),
|
||||
);
|
||||
let mut filter = Equals::from_args(args);
|
||||
|
||||
// Then
|
||||
assert!(!filter.filter(&mut record));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_equals_returns_false_for_different_ints() {
|
||||
// Given
|
||||
let (args, mut record) = generate_args_record_custom(
|
||||
String::from("an_integer"),
|
||||
Value::Int(2),
|
||||
String::from("an_integer"),
|
||||
Value::Int(3),
|
||||
);
|
||||
let mut filter = Equals::from_args(args);
|
||||
|
||||
// Then
|
||||
assert!(!filter.filter(&mut record));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_equals_returns_false_for_different_dates() {
|
||||
// Given
|
||||
let (args, mut record) = generate_args_record_custom(
|
||||
String::from("a_date"),
|
||||
Value::Date(Utc::now()),
|
||||
String::from("a_date"),
|
||||
Value::Date(Utc::now()),
|
||||
);
|
||||
let mut filter = Equals::from_args(args);
|
||||
|
||||
// Then
|
||||
assert!(!filter.filter(&mut record));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_equals_returns_false_for_non_matching_keys() {
|
||||
// Given
|
||||
let (args, mut record) = generate_args_record_custom(
|
||||
String::from("first_one"),
|
||||
Value::Int(1),
|
||||
String::from("second_one"),
|
||||
Value::Int(1),
|
||||
);
|
||||
let mut filter = Equals::from_args(args);
|
||||
|
||||
// Then
|
||||
assert!(!filter.filter(&mut record));
|
||||
}
|
||||
|
||||
fn generate_args_record_equal(name: String, value: Value) -> (ModuleArgs, Record) {
|
||||
let mut args = HashMap::with_capacity(2);
|
||||
args.insert(String::from("field"), Value::Str(name.clone()));
|
||||
|
@ -58,122 +175,4 @@ mod tests {
|
|||
record.insert(test_name, test_value);
|
||||
(args, record)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_equals_returns_true_for_identical_bools() {
|
||||
// Given
|
||||
let (args, mut record) =
|
||||
generate_args_record_equal(String::from("a_boolean"), Value::Bool(false));
|
||||
let filter = Equals::from_args(args);
|
||||
|
||||
// Then
|
||||
assert!(filter.filter(&mut record));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_equals_returns_true_for_identical_strings() {
|
||||
// Given
|
||||
let (args, mut record) =
|
||||
generate_args_record_equal(String::from("a_string"), Value::Str(String::from("Hello!")));
|
||||
let filter = Equals::from_args(args);
|
||||
|
||||
// Then
|
||||
assert!(filter.filter(&mut record));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_equals_returns_true_for_identical_ints() {
|
||||
// Given
|
||||
let (args, mut record) = generate_args_record_equal(String::from("an_integer"), Value::Int(2));
|
||||
let filter = Equals::from_args(args);
|
||||
|
||||
// Then
|
||||
assert!(filter.filter(&mut record));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_equals_returns_true_for_identical_dates() {
|
||||
// Given
|
||||
let (args, mut record) =
|
||||
generate_args_record_equal(String::from("a_date"), Value::Date(Utc::now()));
|
||||
let filter = Equals::from_args(args);
|
||||
|
||||
// Then
|
||||
assert!(filter.filter(&mut record));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_equals_returns_false_for_different_bools() {
|
||||
// Given
|
||||
let (args, mut record) = generate_args_record_custom(
|
||||
String::from("a_boolean"),
|
||||
Value::Bool(true),
|
||||
String::from("a_boolean"),
|
||||
Value::Bool(false),
|
||||
);
|
||||
let filter = Equals::from_args(args);
|
||||
|
||||
// Then
|
||||
assert!(!filter.filter(&mut record));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_equals_returns_false_for_different_strings() {
|
||||
// Given
|
||||
let (args, mut record) = generate_args_record_custom(
|
||||
String::from("a_string"),
|
||||
Value::Str(String::from("Hello!")),
|
||||
String::from("a_string"),
|
||||
Value::Str(String::from("World!")),
|
||||
);
|
||||
let filter = Equals::from_args(args);
|
||||
|
||||
// Then
|
||||
assert!(!filter.filter(&mut record));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_equals_returns_false_for_different_ints() {
|
||||
// Given
|
||||
let (args, mut record) = generate_args_record_custom(
|
||||
String::from("an_integer"),
|
||||
Value::Int(2),
|
||||
String::from("an_integer"),
|
||||
Value::Int(3),
|
||||
);
|
||||
let filter = Equals::from_args(args);
|
||||
|
||||
// Then
|
||||
assert!(!filter.filter(&mut record));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_equals_returns_false_for_different_dates() {
|
||||
// Given
|
||||
let (args, mut record) = generate_args_record_custom(
|
||||
String::from("a_date"),
|
||||
Value::Date(Utc::now()),
|
||||
String::from("a_date"),
|
||||
Value::Date(Utc::now()),
|
||||
);
|
||||
let filter = Equals::from_args(args);
|
||||
|
||||
// Then
|
||||
assert!(!filter.filter(&mut record));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_equals_returns_false_for_non_matching_keys() {
|
||||
// Given
|
||||
let (args, mut record) = generate_args_record_custom(
|
||||
String::from("first_one"),
|
||||
Value::Int(1),
|
||||
String::from("second_one"),
|
||||
Value::Int(1),
|
||||
);
|
||||
let filter = Equals::from_args(args);
|
||||
|
||||
// Then
|
||||
assert!(!filter.filter(&mut record));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::domain::Record;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LogMessage<'t> {
|
||||
EMERG(&'t str),
|
||||
ALERT(&'t str),
|
||||
|
@ -12,8 +11,7 @@ pub enum LogMessage<'t> {
|
|||
DEBUG(&'t str),
|
||||
}
|
||||
|
||||
pub trait LogPort: Sized {
|
||||
fn open() -> Result<Self, ()>;
|
||||
pub trait LogPort {
|
||||
fn read_next(&mut self) -> Result<Record, ()>;
|
||||
fn write(&mut self, message: LogMessage) -> Result<(), ()>;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
use chrono::DateTime;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub mod action;
|
||||
pub mod filter;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_util;
|
||||
|
||||
mod config;
|
||||
pub use self::config::*;
|
||||
mod log;
|
||||
|
@ -16,7 +10,10 @@ pub use self::module::*;
|
|||
mod workflow;
|
||||
pub use self::workflow::*;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
use chrono::DateTime;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub enum Value {
|
||||
Bool(bool),
|
||||
Str(String),
|
||||
|
@ -27,3 +24,6 @@ pub enum Value {
|
|||
}
|
||||
|
||||
pub type Record = HashMap<String, Value>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_util;
|
||||
|
|
|
@ -1,52 +1,48 @@
|
|||
use crate::domain::{Record, Value};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub struct AvailableAction {
|
||||
pub name: String,
|
||||
pub cons: fn(ModuleArgs) -> Box<dyn Action>,
|
||||
pub type ActionConstructor = Box<dyn Fn(ModuleArgs) -> Box<dyn Action>>;
|
||||
pub type FilterConstructor = Box<dyn Fn(ModuleArgs) -> Box<dyn Filter>>;
|
||||
|
||||
pub struct Modules {
|
||||
available_actions: HashMap<String, ActionConstructor>,
|
||||
available_filters: HashMap<String, FilterConstructor>,
|
||||
}
|
||||
|
||||
impl AvailableAction {
|
||||
pub fn new(name: String, cons: fn(ModuleArgs) -> Box<dyn Action>) -> Self {
|
||||
AvailableAction { name, cons }
|
||||
impl Modules {
|
||||
pub fn new() -> Modules {
|
||||
Modules {
|
||||
available_actions: HashMap::new(),
|
||||
available_filters: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_action(&mut self, name: String, cons: ActionConstructor) {
|
||||
self.available_actions.insert(name, cons);
|
||||
}
|
||||
|
||||
pub fn register_filter(&mut self, name: String, cons: FilterConstructor) {
|
||||
self.available_filters.insert(name, cons);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AvailableFilter {
|
||||
pub name: String,
|
||||
pub cons: fn(ModuleArgs) -> Box<dyn Filter>,
|
||||
}
|
||||
|
||||
impl AvailableFilter {
|
||||
pub fn new(name: String, cons: fn(ModuleArgs) -> Box<dyn Filter>) -> Self {
|
||||
AvailableFilter { name, cons }
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ModulesPort {
|
||||
fn available_actions(&self) -> HashMap<&String, &AvailableAction>;
|
||||
fn available_filters(&self) -> HashMap<&String, &AvailableFilter>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Module {
|
||||
Action(Box<dyn Action>),
|
||||
Filter(Box<dyn Filter>),
|
||||
}
|
||||
|
||||
impl Module {
|
||||
pub fn new(name: String, args: ModuleArgs, available: &dyn ModulesPort) -> Result<Module, ()> {
|
||||
if let Some(a) = available.available_actions().get(&name).map(|m| m.cons) {
|
||||
pub fn new(name: String, args: ModuleArgs, available: &Modules) -> Result<Module, ()> {
|
||||
if let Some(a) = available.available_actions.get(&name) {
|
||||
Ok(Module::Action(a(args)))
|
||||
} else if let Some(f) = available.available_filters().get(&name).map(|m| m.cons) {
|
||||
} else if let Some(f) = available.available_filters.get(&name) {
|
||||
Ok(Module::Filter(f(args)))
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&self, record: &mut Record) -> Result<bool, ()> {
|
||||
pub fn run(&mut self, record: &mut Record) -> Result<bool, ()> {
|
||||
match self {
|
||||
Module::Action(a) => match a.act(record) {
|
||||
Ok(()) => Ok(true),
|
||||
|
@ -57,34 +53,31 @@ impl Module {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait Action: Debug {
|
||||
fn act(&self, record: &mut Record) -> Result<(), ()>;
|
||||
pub trait Action {
|
||||
fn act(&mut self, record: &mut Record) -> Result<(), ()>;
|
||||
}
|
||||
|
||||
pub trait Filter: Debug {
|
||||
fn filter(&self, record: &mut Record) -> bool;
|
||||
pub trait Filter {
|
||||
fn filter(&mut self, record: &mut Record) -> bool;
|
||||
}
|
||||
|
||||
pub type ModuleArgs = HashMap<String, Value>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{AvailableAction, AvailableFilter, Module, Record, Value};
|
||||
use super::{Module, Modules, Record, Value};
|
||||
use crate::domain::test_util::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
fn available_action_can_be_generated_and_run() {
|
||||
// Given
|
||||
let aa = [AvailableAction {
|
||||
name: ACT_NAME.to_string(),
|
||||
cons: |_| Box::new(FakeAction {}),
|
||||
}];
|
||||
let mut mods = Modules::new();
|
||||
mods.register_action(ACT_NAME.to_string(), Box::new(|_| Box::new(FakeAction {})));
|
||||
let mut record: Record = HashMap::new();
|
||||
let mods = FakeModulesAdapter::new(&aa, &[]);
|
||||
|
||||
// When
|
||||
let module = Module::new(ACT_NAME.to_string(), HashMap::new(), &mods).unwrap();
|
||||
let mut module = Module::new(ACT_NAME.to_string(), HashMap::new(), &mods).unwrap();
|
||||
|
||||
// Then
|
||||
assert!(module.run(&mut record) == Ok(true));
|
||||
|
@ -95,15 +88,12 @@ mod tests {
|
|||
#[test]
|
||||
fn available_filter_can_be_generated_and_run() {
|
||||
// Given
|
||||
let af = [AvailableFilter {
|
||||
name: FLT_NAME.to_string(),
|
||||
cons: |_| Box::new(FakeFilter {}),
|
||||
}];
|
||||
let mut mods = Modules::new();
|
||||
mods.register_filter(FLT_NAME.to_string(), Box::new(|_| Box::new(FakeFilter {})));
|
||||
let mut record: Record = HashMap::new();
|
||||
let mods = FakeModulesAdapter::new(&[], &af);
|
||||
|
||||
// When
|
||||
let module = Module::new(FLT_NAME.to_string(), HashMap::new(), &mods).unwrap();
|
||||
let mut module = Module::new(FLT_NAME.to_string(), HashMap::new(), &mods).unwrap();
|
||||
|
||||
// Then
|
||||
assert!(module.run(&mut record) == Ok(false));
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
use crate::domain::{Action, AvailableAction, AvailableFilter, Filter, ModulesPort, Record, Value};
|
||||
use std::collections::HashMap;
|
||||
use crate::domain::{Action, Filter, LogMessage, LogPort, Record, Value};
|
||||
|
||||
pub const ACT_NAME: &str = "fake_action";
|
||||
pub const FLT_NAME: &str = "fake_filter";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FakeAction {}
|
||||
|
||||
impl Action for FakeAction {
|
||||
fn act(&self, record: &mut Record) -> Result<(), ()> {
|
||||
fn act(&mut self, record: &mut Record) -> Result<(), ()> {
|
||||
let v = record.get(ACT_NAME).unwrap_or(&Value::Int(0));
|
||||
match v {
|
||||
Value::Int(i) => record.insert(String::from(ACT_NAME), Value::Int(i + 1)),
|
||||
|
@ -18,11 +16,10 @@ impl Action for FakeAction {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FakeFilter {}
|
||||
|
||||
impl Filter for FakeFilter {
|
||||
fn filter(&self, record: &mut Record) -> bool {
|
||||
fn filter(&mut self, record: &mut Record) -> bool {
|
||||
let v = record.get(FLT_NAME).unwrap_or(&Value::Int(0));
|
||||
match v {
|
||||
Value::Int(i) => record.insert(String::from(FLT_NAME), Value::Int(i + 1)),
|
||||
|
@ -32,30 +29,40 @@ impl Filter for FakeFilter {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct FakeModulesAdapter<'a> {
|
||||
a: HashMap<&'a String, &'a AvailableAction>,
|
||||
f: HashMap<&'a String, &'a AvailableFilter>,
|
||||
pub struct FakeLog {
|
||||
pub wanted_next: Vec<Result<Record, ()>>,
|
||||
pub last_write: Option<(String, String)>,
|
||||
}
|
||||
|
||||
impl FakeModulesAdapter<'_> {
|
||||
pub fn new<'a>(act: &'a [AvailableAction], flt: &'a [AvailableFilter]) -> FakeModulesAdapter<'a> {
|
||||
let a = act
|
||||
.iter()
|
||||
.map(|m| (&m.name, m))
|
||||
.collect::<HashMap<&'a String, &'a AvailableAction>>();
|
||||
let f = flt
|
||||
.iter()
|
||||
.map(|m| (&m.name, m))
|
||||
.collect::<HashMap<&'a String, &'a AvailableFilter>>();
|
||||
FakeModulesAdapter { a, f }
|
||||
impl FakeLog {
|
||||
pub fn new(wanted_next: Vec<Result<Record, ()>>) -> FakeLog {
|
||||
FakeLog {
|
||||
wanted_next,
|
||||
last_write: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ModulesPort for FakeModulesAdapter<'_> {
|
||||
fn available_actions(&self) -> HashMap<&String, &AvailableAction> {
|
||||
self.a.clone()
|
||||
impl LogPort for FakeLog {
|
||||
fn read_next(&mut self) -> Result<Record, ()> {
|
||||
if self.wanted_next.is_empty() {
|
||||
Err(())
|
||||
} else {
|
||||
self.wanted_next.remove(0)
|
||||
}
|
||||
}
|
||||
fn available_filters(&self) -> HashMap<&String, &AvailableFilter> {
|
||||
self.f.clone()
|
||||
|
||||
fn write(&mut self, message: LogMessage) -> Result<(), ()> {
|
||||
self.last_write = match message {
|
||||
LogMessage::EMERG(m) => Some(("EMERG".to_string(), m.to_string())),
|
||||
LogMessage::ALERT(m) => Some(("ALERT".to_string(), m.to_string())),
|
||||
LogMessage::CRIT(m) => Some(("CRIT".to_string(), m.to_string())),
|
||||
LogMessage::ERR(m) => Some(("ERR".to_string(), m.to_string())),
|
||||
LogMessage::WARNING(m) => Some(("WARNING".to_string(), m.to_string())),
|
||||
LogMessage::NOTICE(m) => Some(("NOTICE".to_string(), m.to_string())),
|
||||
LogMessage::INFO(m) => Some(("INFO".to_string(), m.to_string())),
|
||||
LogMessage::DEBUG(m) => Some(("DEBUG".to_string(), m.to_string())),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use crate::domain::{Config, Module, ModulesPort, Record, Step};
|
||||
use crate::domain::{Config, Module, Modules, Record, Step};
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Add;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Node {
|
||||
name: String,
|
||||
module: Module,
|
||||
|
@ -11,7 +10,7 @@ struct Node {
|
|||
}
|
||||
|
||||
impl Node {
|
||||
fn run(&self, record: &mut Record) -> isize {
|
||||
fn run(&mut self, record: &mut Record) -> isize {
|
||||
if let Ok(b) = self.module.run(record) {
|
||||
if b {
|
||||
self.then_dest
|
||||
|
@ -29,7 +28,7 @@ pub struct Workflow {
|
|||
}
|
||||
|
||||
impl Workflow {
|
||||
pub fn run(&self, record: &mut Record) {
|
||||
pub fn run(&mut self, record: &mut Record) {
|
||||
let mut i = 0 as isize;
|
||||
while {
|
||||
i = self.nodes[i as usize].run(record);
|
||||
|
@ -37,7 +36,7 @@ impl Workflow {
|
|||
} {}
|
||||
}
|
||||
|
||||
pub fn build(conf: &mut Config, available: &dyn ModulesPort) -> Workflow {
|
||||
pub fn build(conf: &mut Config, available: &Modules) -> Workflow {
|
||||
let mut seen: Vec<String> = Vec::new();
|
||||
let mut nodes: Vec<Node> = Vec::new();
|
||||
let mut dangling: DanglingInfo = HashMap::new();
|
||||
|
@ -83,7 +82,7 @@ fn build_chain(
|
|||
nodes: &mut Vec<Node>,
|
||||
seen: &mut Vec<String>,
|
||||
dangling: &mut DanglingInfo,
|
||||
available: &dyn ModulesPort,
|
||||
available: &Modules,
|
||||
) {
|
||||
let mut index = 0;
|
||||
for step in chain {
|
||||
|
@ -165,7 +164,7 @@ fn node_wants_chain(
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::domain::test_util::*;
|
||||
use crate::domain::{AvailableAction, AvailableFilter, Chain, Config, Record, Step, Value, Workflow};
|
||||
use crate::domain::{Chain, Config, Modules, Record, Step, Value, Workflow};
|
||||
use indexmap::IndexMap;
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
@ -177,7 +176,7 @@ mod tests {
|
|||
actions: IndexMap::new(),
|
||||
options: HashMap::new(),
|
||||
};
|
||||
let mods = FakeModulesAdapter::new(&[], &[]);
|
||||
let mods = Modules::new();
|
||||
|
||||
// When
|
||||
let _wf = Workflow::build(&mut conf, &mods);
|
||||
|
@ -200,15 +199,12 @@ mod tests {
|
|||
actions,
|
||||
options: HashMap::new(),
|
||||
};
|
||||
let aa = [AvailableAction {
|
||||
name: ACT_NAME.to_string(),
|
||||
cons: |_| Box::new(FakeAction {}),
|
||||
}];
|
||||
let mods = FakeModulesAdapter::new(&aa, &[]);
|
||||
let mut mods = Modules::new();
|
||||
mods.register_action(ACT_NAME.to_string(), Box::new(|_| Box::new(FakeAction {})));
|
||||
let mut record: Record = HashMap::new();
|
||||
|
||||
// When
|
||||
let wf = Workflow::build(&mut conf, &mods);
|
||||
let mut wf = Workflow::build(&mut conf, &mods);
|
||||
wf.run(&mut record);
|
||||
|
||||
// Then
|
||||
|
@ -246,19 +242,13 @@ mod tests {
|
|||
actions,
|
||||
options: HashMap::new(),
|
||||
};
|
||||
let aa = [AvailableAction {
|
||||
name: ACT_NAME.to_string(),
|
||||
cons: |_| Box::new(FakeAction {}),
|
||||
}];
|
||||
let af = [AvailableFilter {
|
||||
name: FLT_NAME.to_string(),
|
||||
cons: |_| Box::new(FakeFilter {}),
|
||||
}];
|
||||
let mods = FakeModulesAdapter::new(&aa, &af);
|
||||
let mut mods = Modules::new();
|
||||
mods.register_action(ACT_NAME.to_string(), Box::new(|_| Box::new(FakeAction {})));
|
||||
mods.register_filter(FLT_NAME.to_string(), Box::new(|_| Box::new(FakeFilter {})));
|
||||
let mut record: Record = HashMap::new();
|
||||
|
||||
// When
|
||||
let wf = Workflow::build(&mut conf, &mods);
|
||||
let mut wf = Workflow::build(&mut conf, &mods);
|
||||
wf.run(&mut record);
|
||||
|
||||
// Then
|
||||
|
@ -299,19 +289,13 @@ mod tests {
|
|||
actions,
|
||||
options: HashMap::new(),
|
||||
};
|
||||
let aa = [AvailableAction {
|
||||
name: ACT_NAME.to_string(),
|
||||
cons: |_| Box::new(FakeAction {}),
|
||||
}];
|
||||
let af = [AvailableFilter {
|
||||
name: FLT_NAME.to_string(),
|
||||
cons: |_| Box::new(FakeFilter {}),
|
||||
}];
|
||||
let mods = FakeModulesAdapter::new(&aa, &af);
|
||||
let mut mods = Modules::new();
|
||||
mods.register_action(ACT_NAME.to_string(), Box::new(|_| Box::new(FakeAction {})));
|
||||
mods.register_filter(FLT_NAME.to_string(), Box::new(|_| Box::new(FakeFilter {})));
|
||||
let mut record: Record = HashMap::new();
|
||||
|
||||
// When
|
||||
let wf = Workflow::build(&mut conf, &mods);
|
||||
let mut wf = Workflow::build(&mut conf, &mods);
|
||||
wf.run(&mut record);
|
||||
|
||||
// Then
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
use crate::domain::action::Noop;
|
||||
use crate::domain::AvailableAction;
|
||||
|
||||
const ACTION_NOOP: &str = "action_noop";
|
||||
|
||||
inventory::submit! {
|
||||
AvailableAction::new(ACTION_NOOP.to_string(), move |a| Box::new(Noop::from_args(a)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::domain::{ModuleArgs, ModulesPort};
|
||||
use crate::infra::module::InventoryModulesAdapter;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
fn action_noop_is_available() {
|
||||
// Given
|
||||
let args: ModuleArgs = HashMap::new();
|
||||
|
||||
// When
|
||||
let aa = (InventoryModulesAdapter {}).available_actions();
|
||||
|
||||
// Then
|
||||
assert!(aa.contains_key(&super::ACTION_NOOP.to_string()));
|
||||
let _can_instantiate = (aa[&super::ACTION_NOOP.to_string()].cons)(args);
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ use std::env;
|
|||
use std::ffi::OsString;
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
const ENV_VARIABLE: &'static str = "PYRUSE_CONF";
|
||||
const ETC_PATH: &'static str = "/etc/pyruse";
|
||||
|
@ -48,7 +48,16 @@ fn find_candidates() -> Vec<ConfFile> {
|
|||
}
|
||||
}
|
||||
None => {
|
||||
let cwd = env::current_dir().expect("Error accessing the current working directory");
|
||||
let add_file: fn(&PathBuf, &str) -> OsString = |c, f| {
|
||||
let mut c2 = c.clone();
|
||||
c2.push(f); // not a fluent API…
|
||||
c2.into_os_string()
|
||||
};
|
||||
vec![
|
||||
ConfFile::Json(add_file(&cwd, "pyruse.yml")),
|
||||
ConfFile::Yaml(add_file(&cwd, "pyruse.yaml")),
|
||||
ConfFile::Yaml(add_file(&cwd, "pyruse.yml")),
|
||||
ConfFile::Json(OsString::from(format!("{}/{}", ETC_PATH, "pyruse.json"))),
|
||||
ConfFile::Yaml(OsString::from(format!("{}/{}", ETC_PATH, "pyruse.yaml"))),
|
||||
ConfFile::Yaml(OsString::from(format!("{}/{}", ETC_PATH, "pyruse.yml"))),
|
||||
|
|
|
@ -9,8 +9,9 @@ use std::fmt;
|
|||
use std::io::Read;
|
||||
|
||||
mod file;
|
||||
pub use self::file::*;
|
||||
|
||||
struct SerdeConfigAdapter {
|
||||
pub struct SerdeConfigAdapter {
|
||||
config: Config,
|
||||
}
|
||||
|
||||
|
@ -20,7 +21,7 @@ impl SerdeConfigAdapter {
|
|||
serde_json::from_reader(data).expect("Failed to parse configuration");
|
||||
SerdeConfigAdapter::from_serde(serde_config)
|
||||
}
|
||||
fn from_yaml(data: impl Read) -> SerdeConfigAdapter {
|
||||
pub fn from_yaml(data: impl Read) -> SerdeConfigAdapter {
|
||||
let serde_config: SerdeConfig =
|
||||
serde_yaml::from_reader(data).expect("Failed to parse configuration");
|
||||
SerdeConfigAdapter::from_serde(serde_config)
|
||||
|
@ -57,13 +58,13 @@ impl SerdeConfigAdapter {
|
|||
}
|
||||
|
||||
impl ConfigPort for SerdeConfigAdapter {
|
||||
fn get(&self) -> &Config {
|
||||
&self.config
|
||||
fn get(&mut self) -> &mut Config {
|
||||
&mut self.config
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct SerdeConfig {
|
||||
#[derive(Deserialize)]
|
||||
struct SerdeConfig {
|
||||
actions: IndexMap<String, SerdeChain>,
|
||||
|
||||
#[serde(flatten)]
|
||||
|
@ -72,16 +73,16 @@ pub struct SerdeConfig {
|
|||
|
||||
type SerdeChain = Vec<SerdeStep>;
|
||||
|
||||
#[derive(Debug, Deserialize, Eq, PartialEq)]
|
||||
pub enum StepType {
|
||||
#[derive(Deserialize, Eq, PartialEq)]
|
||||
enum StepType {
|
||||
#[serde(rename(deserialize = "action"))]
|
||||
Action(String),
|
||||
#[serde(rename(deserialize = "filter"))]
|
||||
Filter(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct SerdeStep {
|
||||
#[derive(Deserialize)]
|
||||
struct SerdeStep {
|
||||
#[serde(flatten)]
|
||||
module: StepType,
|
||||
args: ModuleArgs,
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
use crate::domain::filter::Equals;
|
||||
use crate::domain::AvailableFilter;
|
||||
|
||||
const FILTER_EQUALS: &str = "filter_equals";
|
||||
|
||||
inventory::submit! {
|
||||
AvailableFilter::new(FILTER_EQUALS.to_string(), move |a| Box::new(Equals::from_args(a)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::domain::{ModuleArgs, ModulesPort, Value};
|
||||
use crate::infra::module::InventoryModulesAdapter;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
fn filter_equals_is_available() {
|
||||
// Given
|
||||
let mut args: ModuleArgs = HashMap::new();
|
||||
args.insert("field".to_string(), Value::Str("a_field".to_string()));
|
||||
args.insert("value".to_string(), Value::Int(1));
|
||||
|
||||
// When
|
||||
let af = (InventoryModulesAdapter {}).available_filters();
|
||||
|
||||
// Then
|
||||
assert!(af.contains_key(&super::FILTER_EQUALS.to_string()));
|
||||
let _can_instantiate = (af[&super::FILTER_EQUALS.to_string()].cons)(args);
|
||||
}
|
||||
}
|
|
@ -23,8 +23,8 @@ pub struct SystemdLogAdapter {
|
|||
mappers: HashMap<String, JournalFieldMapper>,
|
||||
}
|
||||
|
||||
impl LogPort for SystemdLogAdapter {
|
||||
fn open() -> Result<Self, ()> {
|
||||
impl SystemdLogAdapter {
|
||||
pub fn open() -> Result<Self, ()> {
|
||||
let mappers = create_mappers();
|
||||
if let Ok(mut journal) = OpenOptions::default()
|
||||
.system(true)
|
||||
|
@ -38,7 +38,9 @@ impl LogPort for SystemdLogAdapter {
|
|||
}
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
impl LogPort for SystemdLogAdapter {
|
||||
fn read_next(&mut self) -> Result<Record, ()> {
|
||||
loop {
|
||||
match self.journal.await_next_entry(None) {
|
||||
|
|
|
@ -1,5 +1,2 @@
|
|||
pub mod action;
|
||||
pub mod config;
|
||||
pub mod filter;
|
||||
pub mod log;
|
||||
pub mod module;
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
use crate::domain::{AvailableAction, AvailableFilter, ModulesPort};
|
||||
use std::collections::HashMap;
|
||||
|
||||
inventory::collect!(AvailableAction);
|
||||
inventory::collect!(AvailableFilter);
|
||||
|
||||
pub struct InventoryModulesAdapter {}
|
||||
|
||||
impl ModulesPort for InventoryModulesAdapter {
|
||||
fn available_actions(&self) -> HashMap<&String, &AvailableAction> {
|
||||
let mut h: HashMap<&String, &AvailableAction> = HashMap::new();
|
||||
for action in inventory::iter::<AvailableAction> {
|
||||
h.insert(&action.name, &action);
|
||||
}
|
||||
h
|
||||
}
|
||||
|
||||
fn available_filters(&self) -> HashMap<&String, &AvailableFilter> {
|
||||
let mut h: HashMap<&String, &AvailableFilter> = HashMap::new();
|
||||
for filter in inventory::iter::<AvailableFilter> {
|
||||
h.insert(&filter.name, &filter);
|
||||
}
|
||||
h
|
||||
}
|
||||
}
|
29
src/main.rs
29
src/main.rs
|
@ -1,7 +1,34 @@
|
|||
mod domain;
|
||||
mod service;
|
||||
mod infra;
|
||||
mod service;
|
||||
|
||||
use crate::domain::action::Log;
|
||||
use crate::domain::action::Noop;
|
||||
use crate::domain::filter::Equals;
|
||||
use crate::domain::{ConfigPort, Modules, Workflow};
|
||||
use crate::infra::config::ConfFile;
|
||||
use crate::infra::log::SystemdLogAdapter;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
fn main() {
|
||||
let mut conf = ConfFile::from_filesystem().to_config();
|
||||
let log = Rc::new(RefCell::new(
|
||||
SystemdLogAdapter::open().expect("Error initializing systemd"),
|
||||
));
|
||||
let mut modules = Modules::new();
|
||||
modules.register_action(
|
||||
"action_noop".to_string(),
|
||||
Box::new(move |a| Box::new(Noop::from_args(a))),
|
||||
);
|
||||
modules.register_action(
|
||||
"action_log".to_string(),
|
||||
Box::new(move |a| Box::new(Log::from_args(a, log.clone()))),
|
||||
);
|
||||
modules.register_filter(
|
||||
"filter_equals".to_string(),
|
||||
Box::new(move |a| Box::new(Equals::from_args(a))),
|
||||
);
|
||||
let _workflow = Workflow::build(conf.get(), &modules);
|
||||
println!("Hello, world!");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue