Browse Source

big cleanup + simplified dependency inversion + action_log

rust-rewrite
Yves G 1 month ago
parent
commit
14b62814dc
19 changed files with 377 additions and 250 deletions
  1. +1
    -1
      Cargo.toml
  2. +195
    -0
      src/domain/action/log.rs
  3. +2
    -0
      src/domain/action/mod.rs
  4. +2
    -3
      src/domain/action/noop.rs
  5. +1
    -1
      src/domain/config.rs
  6. +33
    -34
      src/domain/filter/equals.rs
  7. +1
    -3
      src/domain/log.rs
  8. +7
    -7
      src/domain/mod.rs
  9. +32
    -42
      src/domain/module.rs
  10. +32
    -25
      src/domain/test_util.rs
  11. +18
    -34
      src/domain/workflow.rs
  12. +0
    -28
      src/infra/action.rs
  13. +10
    -1
      src/infra/config/file.rs
  14. +11
    -10
      src/infra/config/mod.rs
  15. +0
    -30
      src/infra/filter.rs
  16. +4
    -2
      src/infra/log.rs
  17. +0
    -3
      src/infra/mod.rs
  18. +0
    -25
      src/infra/module.rs
  19. +28
    -1
      src/main.rs

+ 1
- 1
Cargo.toml 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
- 0
src/domain/action/log.rs 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.");
}
};
}
}

+ 2
- 0
src/domain/action/mod.rs View File

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

+ 2
- 3
src/domain/action/noop.rs 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());


+ 1
- 1
src/domain/config.rs 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 {


+ 33
- 34
src/domain/filter/equals.rs 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,35 +35,12 @@ mod tests {
use chrono::Utc;
use std::collections::HashMap;
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()));
args.insert(String::from("value"), value.clone());
let mut record = HashMap::with_capacity(1);
record.insert(name, value);
(args, record)
}
fn generate_args_record_custom(
ref_name: String,
ref_value: Value,
test_name: String,
test_value: Value,
) -> (ModuleArgs, Record) {
let mut args = HashMap::with_capacity(2);
args.insert(String::from("field"), Value::Str(ref_name));
args.insert(String::from("value"), ref_value);
let mut record = HashMap::with_capacity(1);
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);
let mut filter = Equals::from_args(args);
// Then
assert!(filter.filter(&mut record));
@ -75,7 +51,7 @@ mod tests {
// Given
let (args, mut record) =
generate_args_record_equal(String::from("a_string"), Value::Str(String::from("Hello!")));
let filter = Equals::from_args(args);
let mut filter = Equals::from_args(args);
// Then
assert!(filter.filter(&mut record));
@ -85,7 +61,7 @@ mod tests {
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);
let mut filter = Equals::from_args(args);
// Then
assert!(filter.filter(&mut record));
@ -96,7 +72,7 @@ mod tests {
// Given
let (args, mut record) =
generate_args_record_equal(String::from("a_date"), Value::Date(Utc::now()));
let filter = Equals::from_args(args);
let mut filter = Equals::from_args(args);
// Then
assert!(filter.filter(&mut record));
@ -111,7 +87,7 @@ mod tests {
String::from("a_boolean"),
Value::Bool(false),
);
let filter = Equals::from_args(args);
let mut filter = Equals::from_args(args);
// Then
assert!(!filter.filter(&mut record));
@ -126,7 +102,7 @@ mod tests {
String::from("a_string"),
Value::Str(String::from("World!")),
);
let filter = Equals::from_args(args);
let mut filter = Equals::from_args(args);
// Then
assert!(!filter.filter(&mut record));
@ -141,7 +117,7 @@ mod tests {
String::from("an_integer"),
Value::Int(3),
);
let filter = Equals::from_args(args);
let mut filter = Equals::from_args(args);
// Then
assert!(!filter.filter(&mut record));
@ -156,7 +132,7 @@ mod tests {
String::from("a_date"),
Value::Date(Utc::now()),
);
let filter = Equals::from_args(args);
let mut filter = Equals::from_args(args);
// Then
assert!(!filter.filter(&mut record));
@ -171,9 +147,32 @@ mod tests {
String::from("second_one"),
Value::Int(1),
);
let filter = Equals::from_args(args);
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()));
args.insert(String::from("value"), value.clone());
let mut record = HashMap::with_capacity(1);
record.insert(name, value);
(args, record)
}
fn generate_args_record_custom(
ref_name: String,
ref_value: Value,
test_name: String,
test_value: Value,
) -> (ModuleArgs, Record) {
let mut args = HashMap::with_capacity(2);
args.insert(String::from("field"), Value::Str(ref_name));
args.insert(String::from("value"), ref_value);
let mut record = HashMap::with_capacity(1);
record.insert(test_name, test_value);
(args, record)
}
}

+ 1
- 3
src/domain/log.rs 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<(), ()>;
}

+ 7
- 7
src/domain/mod.rs 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;

+ 32
- 42
src/domain/module.rs 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>>;
impl AvailableAction {
pub fn new(name: String, cons: fn(ModuleArgs) -> Box<dyn Action>) -> Self {
AvailableAction { name, cons }
}
pub struct Modules {
available_actions: HashMap<String, ActionConstructor>,
available_filters: HashMap<String, FilterConstructor>,
}
pub struct AvailableFilter {
pub name: String,
pub cons: fn(ModuleArgs) -> Box<dyn Filter>,
}
impl Modules {
pub fn new() -> Modules {
Modules {
available_actions: HashMap::new(),
available_filters: HashMap::new(),
}
}
impl AvailableFilter {
pub fn new(name: String, cons: fn(ModuleArgs) -> Box<dyn Filter>) -> Self {
AvailableFilter { name, cons }
pub fn register_action(&mut self, name: String, cons: ActionConstructor) {
self.available_actions.insert(name, cons);
}
}
pub trait ModulesPort {
fn available_actions(&self) -> HashMap<&String, &AvailableAction>;
fn available_filters(&self) -> HashMap<&String, &AvailableFilter>;
pub fn register_filter(&mut self, name: String, cons: FilterConstructor) {
self.available_filters.insert(name, cons);
}
}
#[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));


+ 32
- 25
src/domain/test_util.rs 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(())
}
}

+ 18
- 34
src/domain/workflow.rs 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


+ 0
- 28
src/infra/action.rs 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);
}
}

+ 10
- 1
src/infra/config/file.rs 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"))),


+ 11
- 10
src/infra/config/mod.rs 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,


+ 0
- 30
src/infra/filter.rs 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);
}
}

+ 4
- 2
src/infra/log.rs 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) {


+ 0
- 3
src/infra/mod.rs View File

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

+ 0
- 25
src/infra/module.rs 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
}
}

+ 28
- 1
src/main.rs 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!");
}

Loading…
Cancel
Save