big cleanup + simplified dependency inversion + action_log

rust-rewrite
Yves G 2021-02-28 22:18:51 +01:00
parent 5e120a05bb
commit 14b62814dc
19 changed files with 466 additions and 339 deletions

View File

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

195
src/domain/action/log.rs Normal file
View File

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

View File

@ -1,2 +1,4 @@
mod log;
pub use self::log::*;
mod noop;
pub use self::noop::*;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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) {

View File

@ -1,5 +1,2 @@
pub mod action;
pub mod config;
pub mod filter;
pub mod log;
pub mod module;

View File

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

View File

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