use crate::error::ParserError;
use crate::error::SelectionError::{
InvalidKeywordSelection, InvalidSelectionType, MixedKeywordAndFieldlist,
SelectionContainsNoFields,
};
use crate::event::Event;
use crate::field::Field;
use serde::Deserialize;
use serde_norway::Value;
use serde_norway::Value::{Mapping, Sequence};
#[derive(Debug)]
pub struct FieldGroup {
pub fields: Vec<Field>,
}
impl FieldGroup {
fn evaluate(&self, event: &Event) -> bool {
self.fields.iter().all(|field| field.evaluate(event))
}
}
impl TryFrom<serde_norway::Mapping> for FieldGroup {
type Error = ParserError;
fn try_from(mapping: serde_norway::Mapping) -> Result<Self, Self::Error> {
let mut fields = vec![];
for (name, values) in mapping.into_iter() {
match name {
Value::String(name) => fields.push(Field::from_yaml(name, values)?),
_ => return Err(Self::Error::InvalidFieldName(format!("{:?}", name))),
}
}
Ok(Self { fields })
}
}
#[derive(Deserialize)]
struct SelectionProxy {
#[serde(flatten)]
value: Value,
}
#[derive(Debug, Deserialize)]
#[serde(try_from = "SelectionProxy")]
pub enum Selection {
Keyword(Vec<String>),
Field(Vec<FieldGroup>),
}
impl TryFrom<SelectionProxy> for Selection {
type Error = ParserError;
fn try_from(other: SelectionProxy) -> Result<Self, Self::Error> {
Self::try_from(other.value)
}
}
impl TryFrom<Value> for Selection {
type Error = ParserError;
fn try_from(other: Value) -> Result<Self, Self::Error> {
match other {
Sequence(seq) => {
if seq.is_empty() {
return Err(Self::Error::SelectionParsingError(
String::new(),
SelectionContainsNoFields(),
));
}
let is_keyword_selection = !seq[0].is_mapping();
if is_keyword_selection {
let mut keywords = vec![];
for value in seq.iter() {
match value {
Value::String(s) => keywords.push(s.to_string()),
Value::Number(n) => keywords.push(n.to_string()),
Value::Bool(b) => keywords.push(b.to_string()),
_ => {
return Err(Self::Error::SelectionParsingError(
String::new(),
InvalidKeywordSelection(format!("{:?}", value)),
))
}
}
}
return Ok(Self::Keyword(keywords));
}
let mut field_groups = vec![];
for value in seq {
match value {
Mapping(map) => {
field_groups.push(FieldGroup::try_from(map)?);
}
_ => {
return Err(Self::Error::SelectionParsingError(
String::new(),
MixedKeywordAndFieldlist(),
))
}
}
}
Ok(Self::Field(field_groups))
}
Mapping(mapping) => {
let field_group = FieldGroup::try_from(mapping)?;
Ok(Self::Field(vec![field_group]))
}
_ => Err(Self::Error::SelectionParsingError(
String::new(),
InvalidSelectionType(),
)),
}
}
}
impl Selection {
pub(crate) fn evaluate(&self, event: &Event) -> bool {
match &self {
Self::Keyword(keywords) => event
.values()
.any(|v| keywords.iter().any(|kw| v.contains_keyword(kw))),
Self::Field(field_groups) => field_groups.iter().any(|g| g.evaluate(event)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::basevalue::BaseValue;
use crate::event::Event;
use crate::field::{FieldValue, MatchModifier};
use serde_norway::Value;
#[test]
fn test_keyword_selection() {
let selection = Selection::Keyword(vec![
"test".to_string(),
"l?nux".to_string(),
"arch *".to_string(),
]);
let event = Event::from([("key", "test")]);
assert!(selection.evaluate(&event));
let event = Event::from([("nomatch", "zsh".to_string())]);
assert!(!selection.evaluate(&event));
let event = Event::from([("some", "arch linux".to_string())]);
assert!(selection.evaluate(&event));
let event = Event::from([("some", "linux".to_string())]);
assert!(selection.evaluate(&event));
let event = Event::from([("some", "LINUX".to_string())]);
assert!(selection.evaluate(&event));
let event = Event::from([("some", "linus".to_string())]);
assert!(!selection.evaluate(&event));
}
#[test]
fn test_fields_selection() {
let selection = Selection::Field(vec![FieldGroup {
fields: vec![
Field::new(
"name1|contains",
vec![FieldValue::from("hello"), FieldValue::from("world")],
)
.unwrap(),
Field::new("name2|cidr", vec![FieldValue::from("10.0.0.0/16")]).unwrap(),
],
}]);
let event = Event::from([("name1", "the world is big"), ("name2", "10.0.43.44")]);
assert!(selection.evaluate(&event));
let event = Event::from([("nomatch", "the world is big"), ("name2", "10.42.43.44")]);
assert!(!selection.evaluate(&event));
}
#[test]
fn test_new_keyword_selection() {
let keywords = vec!["test".to_string(), "linux".to_string(), "arch".to_string()];
let value = Value::from(keywords.clone());
let selection = Selection::try_from(value).unwrap();
assert!(matches!(selection, Selection::Keyword(kw) if kw.len() == keywords.len()));
}
#[test]
fn test_mixed_keyword_selection() {
let yaml = r#"
- 0
- 6
- hello
"#;
let value: Value = serde_norway::from_str(yaml).unwrap();
let selection = Selection::try_from(value).unwrap();
assert!(
matches!(selection, Selection::Keyword(kw) if kw.len() == 3 && kw[0] == "0" && kw[1] == "6" && kw[2] == "hello")
);
}
#[test]
fn test_invalid_keyword_selection() {
let yaml = r#"
- 0
- 6
- hello: world
"#;
let value: Value = serde_norway::from_str(yaml).unwrap();
let err = Selection::try_from(value).unwrap_err();
assert!(matches!(
err,
ParserError::SelectionParsingError(_, InvalidKeywordSelection(_)),
));
}
#[test]
fn test_new_fields_selection() {
let yaml = r#"
selection:
EventID: 6416
Float: 42.21
ClassName: 'DiskDrive'
RandomID|contains:
- ab
- cd
- ed
"#;
let data: serde_norway::Mapping = serde_norway::from_str(yaml).unwrap();
assert_eq!(data.len(), 1);
let value = data.values().next().unwrap().clone();
let selection = Selection::try_from(value).unwrap();
match selection {
Selection::Field(field_group) => {
assert_eq!(field_group.len(), 1);
let fields = &field_group[0].fields;
assert_eq!(fields[0].name, "EventID");
assert_eq!(fields[0].values.len(), 1);
assert!(matches!(
fields[0].values[0],
FieldValue::Base(BaseValue::Int(6416))
));
assert_eq!(fields[1].name, "Float");
assert_eq!(fields[1].values.len(), 1);
assert!(matches!(
fields[1].values[0],
FieldValue::Base(BaseValue::Float(42.21))
));
assert_eq!(fields[2].name, "ClassName");
assert_eq!(fields[2].values.len(), 1);
assert!(matches!(
fields[2].values[0],
FieldValue::WildcardPattern(_)
));
assert_eq!(fields[3].name, "RandomID");
assert_eq!(fields[3].values.len(), 3);
assert!(matches!(
fields[3].values[0],
FieldValue::WildcardPattern(_)
));
assert!(matches!(
fields[3].modifier.match_modifier,
Some(MatchModifier::Contains)
));
}
Selection::Keyword(_) => {
panic!("wrong mode")
}
}
}
}