Skip to content
Snippets Groups Projects
Commit 85ef562c authored by Éloïs's avatar Éloïs
Browse files

[feat] add crate rules-engine

parent 3f5e883b
No related branches found
No related tags found
1 merge request!131[feat] add crate rules-engine
......@@ -18,6 +18,7 @@ members = [
"lib/tools/documents",
"lib/tools/json-pest-parser",
"lib/tools/network-documents",
"lib/tools/rules-engine",
"lib/tests-tools/crypto-tests-tools",
"lib/tests-tools/documents-tests-tools",
"lib/tests-tools/rust-tests-tools",
......
[package]
name = "rules-engine"
version = "0.1.0"
authors = ["librelois <elois@ifee.fr>"]
description = "Rules engine"
license = "AGPL-3.0"
edition = "2018"
[lib]
path = "src/lib.rs"
[dependencies]
failure = "0.1.5"
rayon = "1.0.3"
[dev-dependencies]
maplit = "1.0.1"
// Copyright (C) 2019 Éloïs SANCHEZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Rules engine
#![deny(
missing_copy_implementations,
trivial_casts,
trivial_numeric_casts,
unsafe_code,
unstable_features,
unused_import_braces
)]
pub mod rule;
use failure::Fail;
use rayon::prelude::*;
use rule::{Rule, RuleError, RuleNumber};
use std::collections::BTreeMap;
use std::fmt::Debug;
#[derive(Copy, Clone, Debug, Ord, PartialEq, PartialOrd, Eq, Hash)]
pub struct ProtocolVersion(pub usize);
impl std::fmt::Display for ProtocolVersion {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ProtocolRules(pub Vec<RulesGroup>);
impl From<Vec<usize>> for ProtocolRules {
fn from(rules_numbers: Vec<usize>) -> Self {
ProtocolRules(vec![RulesGroup::Ser(
rules_numbers.into_iter().map(RuleNumber).collect(),
)])
}
}
impl From<Vec<RulesGroup>> for ProtocolRules {
fn from(rules_groups: Vec<RulesGroup>) -> Self {
ProtocolRules(rules_groups)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
/// Protocol
pub struct Protocol(BTreeMap<ProtocolVersion, ProtocolRules>);
impl Protocol {
/// Create new protocol
/// protocol_versions: Dictionary of rules to be applied for each version of the protocol (rules will be applied in the order provided)
pub fn new(protocol_versions: BTreeMap<ProtocolVersion, ProtocolRules>) -> Self {
Protocol(protocol_versions)
}
/// Get specific protocol version
pub fn get(&self, protocol_version: ProtocolVersion) -> Option<&ProtocolRules> {
self.0.get(&protocol_version)
}
}
/// Rules groups
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum RulesGroup {
/// In serial
Ser(Vec<RuleNumber>),
/// In parallel
Par(Vec<RulesGroup>),
}
impl RulesGroup {
#[inline]
/// Create singleton rules group
pub fn s1(rule_number: usize) -> Self {
RulesGroup::Ser(vec![RuleNumber(rule_number)])
}
#[inline]
/// Create parallel set of rules
pub fn pr(rules_numbers: Vec<usize>) -> Self {
RulesGroup::Par(rules_numbers.into_iter().map(RulesGroup::s1).collect())
}
}
/// Rules engine
pub struct RulesEngine<D: Debug + Sync, E: Eq + Fail + PartialEq> {
/// All rules
all_rules: BTreeMap<RuleNumber, Rule<D, E>>,
}
impl<D: Debug + Sync, E: Eq + Fail + PartialEq> RulesEngine<D, E> {
/// Create new rules engine
pub fn new(all_rules: BTreeMap<RuleNumber, Rule<D, E>>) -> Self {
RulesEngine { all_rules }
}
fn apply_rules_group_ref(
&self,
protocol_version: ProtocolVersion,
rules_group: RulesGroup,
rule_datas: &D,
) -> Result<(), EngineError<E>> {
match rules_group {
RulesGroup::Ser(rules_numbers) => rules_numbers
.into_iter()
.map(|rule_number| self.apply_rule_ref(protocol_version, rule_number, rule_datas))
.collect(),
RulesGroup::Par(rules_group) => rules_group
.into_par_iter()
.map(|rg| self.apply_rules_group_ref(protocol_version, rg, rule_datas))
.collect(),
}
}
fn apply_rule_ref(
&self,
protocol_version: ProtocolVersion,
rule_number: RuleNumber,
rule_datas: &D,
) -> Result<(), EngineError<E>> {
if let Some(rule) = self.all_rules.get(&rule_number) {
rule.execute(protocol_version, rule_number, rule_datas)
} else {
Err(EngineError::RuleNotExist {
rule_number,
protocol_version,
})
}
}
fn apply_rule_mut(
&self,
protocol_version: ProtocolVersion,
rule_number: RuleNumber,
rule_datas: &mut D,
) -> Result<(), EngineError<E>> {
if let Some(rule) = self.all_rules.get(&rule_number) {
rule.execute_mut(protocol_version, rule_number, rule_datas)
} else {
Err(EngineError::RuleNotExist {
rule_number,
protocol_version,
})
}
}
/// Apply a specific version of the protocol
pub fn apply_protocol(
&self,
protocol: Protocol,
protocol_version: ProtocolVersion,
rule_datas: &mut D,
) -> Result<(), EngineError<E>> {
if let Some(protocol_rules) = protocol.get(protocol_version) {
for rules_group in &protocol_rules.0 {
let result: Result<(), EngineError<E>> = match rules_group {
RulesGroup::Ser(rules_numbers) => rules_numbers
.iter()
.map(|rule_number| {
self.apply_rule_mut(protocol_version, *rule_number, rule_datas)
})
.collect(),
RulesGroup::Par(rules_group) => rules_group
.par_iter()
.map(|rg| {
self.apply_rules_group_ref(protocol_version, rg.clone(), rule_datas)
})
.collect(),
};
if let Err(err) = result {
return Err(err);
}
}
Ok(())
} else {
Err(EngineError::ProtocolVersionNotExist { protocol_version })
}
}
}
/// Protocol error
#[derive(Debug, Eq, Fail, PartialEq)]
pub enum EngineError<E: Eq + Fail + PartialEq> {
#[fail(display = "{}", _0)]
/// Rule Error
RuleError(RuleError<E>),
#[fail(display = "protocol V{} not exist", protocol_version)]
/// The protocol version does not exist
ProtocolVersionNotExist {
/// Protocole version
protocol_version: ProtocolVersion,
},
#[fail(
display = "Rule n°{} not exist (require by protocol V{})",
rule_number, protocol_version
)]
/// A rule required by the protocol version does not exist
RuleNotExist {
/// Rule number
rule_number: RuleNumber,
/// Protocole version
protocol_version: ProtocolVersion,
},
#[fail(
display = "Rule n°{} is mutable and called in parallel in the V{} protocol, this is prohibited.
A rule can be mutable or called in parallel but not both at the same time.",
rule_number, protocol_version
)]
/// Calling a mutable rule in a part executed in parallel
MutRuleInPar {
/// Rule number
rule_number: RuleNumber,
/// Protocole version
protocol_version: ProtocolVersion,
},
#[fail(
display = "Rule n°{} does not exist in a version less than or equal to the protocol version (V{})",
rule_number, protocol_version
)]
/// Calling a rule too recent
RuleTooRecent {
/// Rule number
rule_number: RuleNumber,
/// Protocole version
protocol_version: ProtocolVersion,
},
}
#[cfg(test)]
mod tests {
use super::rule::*;
use super::*;
use maplit::btreemap;
#[derive(Debug)]
struct Datas {
i: usize,
}
#[derive(Debug, Eq, Fail, PartialEq)]
#[fail(display = "")]
struct Error {}
fn r2_v1(datas: &mut Datas) -> Result<(), Error> {
if datas.i == 0 {
datas.i += 1;
Ok(())
} else {
Err(Error {})
}
}
fn r3_v2(datas: &Datas) -> Result<(), Error> {
if datas.i == 1 {
Ok(())
} else {
Err(Error {})
}
}
fn get_test_engine() -> RulesEngine<Datas, Error> {
let all_rules: BTreeMap<RuleNumber, Rule<Datas, Error>> = btreemap![
RuleNumber(2) => Rule::new(RuleNumber(2), btreemap![
ProtocolVersion(1) => RuleFn::RefMut(r2_v1),
]).expect("Fail to create rule n°2"),
RuleNumber(3) => Rule::new(RuleNumber(3), btreemap![
ProtocolVersion(2) => RuleFn::Ref(r3_v2),
]).expect("Fail to create rule n°2"),
];
RulesEngine::new(all_rules)
}
#[test]
fn rule_without_impl() {
if let Err(err) = Rule::<Datas, Error>::new(RuleNumber(1), btreemap![]) {
assert_eq!(
RuleWithoutImpl {
rule_number: RuleNumber(1),
},
err,
)
} else {
panic!("Rule creation must be fail")
}
println!("{}", ProtocolVersion(1));
println!("{}", RuleNumber(1));
}
#[test]
fn protocol_empty() -> Result<(), EngineError<Error>> {
let engine = get_test_engine();
let mut datas = Datas { i: 0 };
let protocol_empty: Protocol = Protocol::new(btreemap![
ProtocolVersion(1) => Vec::<usize>::with_capacity(0).into()
]);
engine.apply_protocol(protocol_empty, ProtocolVersion(1), &mut datas)
}
#[test]
fn protocol_version_not_exist() {
let engine = get_test_engine();
let mut datas = Datas { i: 0 };
let protocol_empty: Protocol = Protocol::new(btreemap![
ProtocolVersion(1) => Vec::<usize>::with_capacity(0).into()
]);
assert_eq!(
Err(EngineError::ProtocolVersionNotExist {
protocol_version: ProtocolVersion(2),
}),
engine.apply_protocol(protocol_empty, ProtocolVersion(2), &mut datas)
)
}
#[test]
fn rule_not_exist() {
let engine = get_test_engine();
let mut datas = Datas { i: 0 };
let protocol: Protocol = Protocol::new(btreemap![
ProtocolVersion(1) => vec![1usize].into()
]);
assert_eq!(
Err(EngineError::RuleNotExist {
rule_number: RuleNumber(1),
protocol_version: ProtocolVersion(1)
}),
engine.apply_protocol(protocol, ProtocolVersion(1), &mut datas)
);
let mut datas = Datas { i: 0 };
let protocol_par: Protocol = Protocol::new(btreemap![
ProtocolVersion(1) => vec![RulesGroup::pr(vec![1usize])].into()
]);
assert_eq!(
Err(EngineError::RuleNotExist {
rule_number: RuleNumber(1),
protocol_version: ProtocolVersion(1)
}),
engine.apply_protocol(protocol_par, ProtocolVersion(1), &mut datas)
);
}
#[test]
fn rule_fail() {
let engine = get_test_engine();
let mut datas = Datas { i: 1 };
let protocol: Protocol = Protocol::new(btreemap![
ProtocolVersion(1) => vec![2usize].into()
]);
assert_eq!(
Err(EngineError::RuleError(RuleError {
rule_number: RuleNumber(2),
cause: Error {},
})),
engine.apply_protocol(protocol, ProtocolVersion(1), &mut datas)
)
}
#[test]
fn par_rule_fail() {
let engine = get_test_engine();
let mut datas = Datas { i: 0 };
let protocol: Protocol = Protocol::new(btreemap![
ProtocolVersion(2) => vec![RulesGroup::pr(vec![3usize])].into()
]);
assert_eq!(
Err(EngineError::RuleError(RuleError {
rule_number: RuleNumber(3),
cause: Error {},
})),
engine.apply_protocol(protocol, ProtocolVersion(2), &mut datas)
)
}
#[test]
fn rule_too_recent() {
let engine = get_test_engine();
let mut datas = Datas { i: 0 };
let protocol: Protocol = Protocol::new(btreemap![
ProtocolVersion(1) => vec![2usize, 3].into()
]);
assert_eq!(
Err(EngineError::RuleTooRecent {
protocol_version: ProtocolVersion(1),
rule_number: RuleNumber(3),
}),
engine.apply_protocol(protocol, ProtocolVersion(1), &mut datas)
)
}
#[test]
fn par_rule_too_recent() {
let engine = get_test_engine();
let mut datas = Datas { i: 0 };
let protocol: Protocol = Protocol::new(btreemap![
ProtocolVersion(1) => vec![RulesGroup::pr(vec![3])].into()
]);
assert_eq!(
Err(EngineError::RuleTooRecent {
protocol_version: ProtocolVersion(1),
rule_number: RuleNumber(3),
}),
engine.apply_protocol(protocol, ProtocolVersion(1), &mut datas)
)
}
#[test]
fn mut_rule_in_par_protocol() {
let engine = get_test_engine();
let mut datas = Datas { i: 1 };
let protocol: Protocol = Protocol::new(btreemap![
ProtocolVersion(2) => vec![RulesGroup::pr(vec![2usize, 3])].into()
]);
assert_eq!(
Err(EngineError::MutRuleInPar {
protocol_version: ProtocolVersion(2),
rule_number: RuleNumber(2),
}),
engine.apply_protocol(protocol, ProtocolVersion(2), &mut datas)
)
}
#[test]
fn protocol_success() -> Result<(), EngineError<Error>> {
let engine = get_test_engine();
let mut datas = Datas { i: 0 };
let protocol: Protocol = Protocol::new(btreemap![
ProtocolVersion(2) => vec![2usize, 3].into()
]);
engine.apply_protocol(protocol, ProtocolVersion(2), &mut datas)
}
}
// Copyright (C) 2019 Éloïs SANCHEZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Rules engine : rules
use crate::{EngineError, ProtocolVersion};
use failure::Fail;
use std::collections::BTreeMap;
use std::fmt::Debug;
#[derive(Copy, Clone, Debug, Ord, PartialEq, PartialOrd, Eq, Hash)]
/// Rule number
pub struct RuleNumber(pub usize);
impl std::fmt::Display for RuleNumber {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
/// Rule error
#[derive(Debug, Eq, Fail, PartialEq)]
#[fail(display = "An error occurred with rule n°{} : {}", rule_number, cause)]
pub struct RuleError<E: Eq + Fail + PartialEq> {
/// Rule number
pub rule_number: RuleNumber,
/// Cause of the error
pub cause: E,
}
/// Rule immutable execution function
pub type RuleFnRef<D, E> = fn(&D) -> Result<(), E>;
/// Rule mutable execution function
pub type RuleFnRefMut<D, E> = fn(&mut D) -> Result<(), E>;
/// Rule execution function
pub enum RuleFn<D, E> {
Ref(RuleFnRef<D, E>),
RefMut(RuleFnRefMut<D, E>),
}
#[derive(Debug, Copy, Clone, Eq, Fail, PartialEq)]
#[fail(
display = "Fatal error: rules-engine: try to create rule n°{} without implementation !",
rule_number
)]
pub struct RuleWithoutImpl {
pub rule_number: RuleNumber,
}
/// Rule
pub struct Rule<D: Debug, E: Eq + Fail + PartialEq> {
/// Dictionary of the different versions of the rule execution function
rule_versions: BTreeMap<ProtocolVersion, RuleFn<D, E>>,
}
impl<D: Debug, E: Eq + Fail + PartialEq> Rule<D, E> {
/// Create new rule
pub fn new(
rule_number: RuleNumber,
rule_versions: BTreeMap<ProtocolVersion, RuleFn<D, E>>,
) -> Result<Self, RuleWithoutImpl> {
if rule_versions.is_empty() {
Err(RuleWithoutImpl { rule_number })
} else {
Ok(Rule { rule_versions })
}
}
/// Executes the correct version of the rule
pub fn execute(
&self,
protocol_version: ProtocolVersion,
rule_number: RuleNumber,
rule_datas: &D,
) -> Result<(), EngineError<E>> {
let rule_opt: Option<(&ProtocolVersion, &RuleFn<D, E>)> =
self.rule_versions.range(..=protocol_version).last();
if let Some((_, rule_fn)) = rule_opt {
match rule_fn {
RuleFn::Ref(rule_fn_ref) => rule_fn_ref(rule_datas).map_err(|err| {
EngineError::RuleError(RuleError {
rule_number,
cause: err,
})
}),
RuleFn::RefMut(_) => Err(EngineError::MutRuleInPar {
rule_number,
protocol_version,
}),
}
} else {
Err(EngineError::RuleTooRecent {
rule_number,
protocol_version,
})
}
}
/// Executes the correct version of the rule
pub fn execute_mut(
&self,
protocol_version: ProtocolVersion,
rule_number: RuleNumber,
rule_datas: &mut D,
) -> Result<(), EngineError<E>> {
let rule_opt: Option<(&ProtocolVersion, &RuleFn<D, E>)> =
self.rule_versions.range(..=protocol_version).last();
if let Some((_, rule_fn)) = rule_opt {
match rule_fn {
RuleFn::Ref(rule_fn_ref) => rule_fn_ref(rule_datas),
RuleFn::RefMut(rule_fn_ref_mut) => rule_fn_ref_mut(rule_datas),
}
.map_err(|err| {
EngineError::RuleError(RuleError {
rule_number,
cause: err,
})
})
} else {
Err(EngineError::RuleTooRecent {
rule_number,
protocol_version,
})
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment