use std::{fmt, str::FromStr};
use regex::Regex;
use serde::{Deserialize, Serialize};
use crate::{
error::MatcherError,
event::{AttributeValue, Event},
};
mod regex_serde {
use std::str::FromStr;
use regex::Regex;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S>(regex: &Regex, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
regex.as_str().serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Regex, D::Error>
where
D: Deserializer<'de>,
{
let pattern = String::deserialize(deserializer)?;
Regex::from_str(&pattern).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ElementaryTest {
Equals {
attribute: String,
value: AttributeValue,
},
Contains {
attribute: String,
substring: String,
},
StartsWith {
attribute: String,
prefix: String,
},
EndsWith {
attribute: String,
suffix: String,
},
Regex {
attribute: String,
#[serde(with = "regex_serde")]
pattern: Regex,
},
GreaterThan {
attribute: String,
value: f64,
},
LessThan {
attribute: String,
value: f64,
},
In {
attribute: String,
values: Vec<AttributeValue>,
},
Exists {
attribute: String,
},
}
impl PartialEq for ElementaryTest {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(
ElementaryTest::Equals {
attribute: a1,
value: v1,
},
ElementaryTest::Equals {
attribute: a2,
value: v2,
},
) => a1 == a2 && v1 == v2,
(
ElementaryTest::Contains {
attribute: a1,
substring: s1,
},
ElementaryTest::Contains {
attribute: a2,
substring: s2,
},
) => a1 == a2 && s1 == s2,
(
ElementaryTest::StartsWith {
attribute: a1,
prefix: p1,
},
ElementaryTest::StartsWith {
attribute: a2,
prefix: p2,
},
) => a1 == a2 && p1 == p2,
(
ElementaryTest::EndsWith {
attribute: a1,
suffix: s1,
},
ElementaryTest::EndsWith {
attribute: a2,
suffix: s2,
},
) => a1 == a2 && s1 == s2,
(
ElementaryTest::Regex {
attribute: a1,
pattern: p1,
},
ElementaryTest::Regex {
attribute: a2,
pattern: p2,
},
) => a1 == a2 && p1.as_str() == p2.as_str(),
(
ElementaryTest::GreaterThan {
attribute: a1,
value: v1,
},
ElementaryTest::GreaterThan {
attribute: a2,
value: v2,
},
) => a1 == a2 && v1 == v2,
(
ElementaryTest::LessThan {
attribute: a1,
value: v1,
},
ElementaryTest::LessThan {
attribute: a2,
value: v2,
},
) => a1 == a2 && v1 == v2,
(
ElementaryTest::In {
attribute: a1,
values: v1,
},
ElementaryTest::In {
attribute: a2,
values: v2,
},
) => a1 == a2 && v1 == v2,
(
ElementaryTest::Exists { attribute: a1 },
ElementaryTest::Exists { attribute: a2 },
) => a1 == a2,
_ => false,
}
}
}
impl ElementaryTest {
pub fn regex(attribute: String, pattern: &str) -> Result<Self, MatcherError> {
match Regex::from_str(pattern) {
Ok(regex) => Ok(ElementaryTest::Regex {
attribute,
pattern: regex,
}),
Err(err) => Err(MatcherError::InvalidPredicate(format!(
"Invalid regex pattern: {err}"
))),
}
}
pub fn evaluate(&self, event: &impl Event) -> Result<bool, MatcherError> {
match self {
ElementaryTest::Equals { attribute, value } => match event.get_attribute(attribute) {
Some(attr_value) => Ok(attr_value == value),
None => Ok(false),
},
ElementaryTest::Contains {
attribute,
substring,
} => match event.get_attribute(attribute) {
Some(AttributeValue::String(s)) => Ok(s.contains(substring)),
Some(_) => Err(MatcherError::IncompatibleAttributeType(attribute.clone())),
None => Ok(false),
},
ElementaryTest::StartsWith { attribute, prefix } => {
match event.get_attribute(attribute) {
Some(AttributeValue::String(s)) => Ok(s.starts_with(prefix)),
Some(_) => Err(MatcherError::IncompatibleAttributeType(attribute.clone())),
None => Ok(false),
}
}
ElementaryTest::EndsWith { attribute, suffix } => {
match event.get_attribute(attribute) {
Some(AttributeValue::String(s)) => Ok(s.ends_with(suffix)),
Some(_) => Err(MatcherError::IncompatibleAttributeType(attribute.clone())),
None => Ok(false),
}
}
ElementaryTest::Regex { attribute, pattern } => match event.get_attribute(attribute) {
Some(AttributeValue::String(s)) => Ok(pattern.is_match(s)),
Some(_) => Err(MatcherError::IncompatibleAttributeType(attribute.clone())),
None => Ok(false),
},
ElementaryTest::GreaterThan { attribute, value } => {
match event.get_attribute(attribute) {
Some(AttributeValue::Integer(i)) => Ok(*i as f64 > *value),
Some(AttributeValue::Float(f)) => Ok(*f > *value),
Some(_) => Err(MatcherError::IncompatibleAttributeType(attribute.clone())),
None => Ok(false),
}
}
ElementaryTest::LessThan { attribute, value } => match event.get_attribute(attribute) {
Some(AttributeValue::Integer(i)) => Ok((*i as f64) < *value),
Some(AttributeValue::Float(f)) => Ok(*f < *value),
Some(_) => Err(MatcherError::IncompatibleAttributeType(attribute.clone())),
None => Ok(false),
},
ElementaryTest::In { attribute, values } => match event.get_attribute(attribute) {
Some(attr_value) => Ok(values.contains(attr_value)),
None => Ok(false),
},
ElementaryTest::Exists { attribute } => Ok(event.get_attribute(attribute).is_some()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ElementaryPredicate {
pub test: ElementaryTest,
pub negated: bool,
}
impl ElementaryPredicate {
pub fn new(test: ElementaryTest, negated: bool) -> Self {
Self { test, negated }
}
pub fn evaluate(&self, event: &impl Event) -> Result<bool, MatcherError> {
let result = self.test.evaluate(event)?;
Ok(if self.negated { !result } else { result })
}
}
impl PartialEq for ElementaryPredicate {
fn eq(&self, other: &Self) -> bool {
self.test == other.test && self.negated == other.negated
}
}
impl fmt::Display for ElementaryPredicate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let negation = if self.negated { "NOT " } else { "" };
match &self.test {
ElementaryTest::Equals { attribute, value } => {
write!(f, "{negation}({attribute} = {value:?})")
}
ElementaryTest::Contains {
attribute,
substring,
} => {
write!(f, "{negation}({attribute} CONTAINS \"{substring}\")")
}
ElementaryTest::StartsWith { attribute, prefix } => {
write!(f, "{negation}({attribute} STARTS WITH \"{prefix}\")")
}
ElementaryTest::EndsWith { attribute, suffix } => {
write!(f, "{negation}({attribute} ENDS WITH \"{suffix}\")")
}
ElementaryTest::Regex { attribute, pattern } => {
write!(
f,
"{}({} MATCHES \"{}\")",
negation,
attribute,
pattern.as_str()
)
}
ElementaryTest::GreaterThan { attribute, value } => {
write!(f, "{negation}({attribute} > {value})")
}
ElementaryTest::LessThan { attribute, value } => {
write!(f, "{negation}({attribute} < {value})")
}
ElementaryTest::In { attribute, values } => {
write!(f, "{negation}({attribute} IN {values:?})")
}
ElementaryTest::Exists { attribute } => {
write!(f, "{negation}({attribute} EXISTS)")
}
}
}
}