use std::cmp;
use std::string;
use crate::attributes;
use crate::filter;
use crate::sink;
pub struct MessageConfig<'s, const A: usize, const B: usize> {
pub has: [&'s str; A],
pub has_not: [&'s str; B],
pub match_all: bool,
}
pub struct Message {
name: string::String,
has: Vec<String>,
has_not: Vec<String>,
match_all: bool,
}
impl Message {
pub fn new<const A: usize, const B: usize>(conf: MessageConfig<A, B>) -> Self {
Self {
name: format!(
"message matcher ({how} has:{has}, has_not:{has_not}",
how = if conf.match_all { "all" } else { "any" },
has = conf.has.len(),
has_not = conf.has_not.len()
),
match_all: conf.match_all,
has: conf.has.iter().map(|x: &&str| x.to_string()).collect(),
has_not: conf.has_not.iter().map(|x: &&str| x.to_string()).collect(),
}
}
}
impl filter::Filter for Message {
fn name(&self) -> &str {
self.name.as_str()
}
fn pass(&mut self, update: &sink::LogUpdate, _: &attributes::Map) -> bool {
match self.match_all {
true => {
if !self.has.iter().all(|x| update.msg.contains((*x).as_str())) {
return false;
}
if !self.has_not.iter().all(|x| !update.msg.contains((*x).as_str())) {
return false;
}
}
false => {
if !self.has.is_empty() && !self.has.iter().any(|x| update.msg.contains((*x).as_str())) {
return false;
}
if !self.has_not.is_empty() && !self.has_not.iter().any(|x| !update.msg.contains((*x).as_str())) {
return false;
}
}
}
true
}
}
pub struct AttributeKeyConfig<'s, const A: usize, const B: usize> {
pub has: [&'s str; A],
pub has_not: [&'s str; B],
pub match_all: bool,
}
pub struct AttributeKey {
name: string::String,
has: Vec<String>,
has_not: Vec<String>,
match_all: bool,
}
impl AttributeKey {
pub fn new<const A: usize, const B: usize>(conf: AttributeKeyConfig<A, B>) -> Self {
Self {
name: format!(
"attribute key matcher ({how} has:{has}, has_not:{has_not}",
how = if conf.match_all { "all" } else { "any" },
has = conf.has.len(),
has_not = conf.has_not.len()
),
match_all: conf.match_all,
has: conf.has.iter().map(|x: &&str| x.to_string()).collect(),
has_not: conf.has_not.iter().map(|x: &&str| x.to_string()).collect(),
}
}
}
impl filter::Filter for AttributeKey {
fn name(&self) -> &str {
self.name.as_str()
}
fn pass(&mut self, _: &sink::LogUpdate, attrs: &attributes::Map) -> bool {
let mut has_matches: usize = 0;
let mut has_not_matches: usize = 0;
for key in attrs.key_iter() {
has_matches += self.has.iter().filter(|x| key == *x).count();
has_not_matches += self.has_not.iter().filter(|x| key == *x).count();
}
has_not_matches = self.has_not.len() - has_not_matches;
match self.match_all {
true => has_matches == self.has.len() && has_not_matches == self.has_not.len(),
false => {
let mut res = true;
if !self.has.is_empty() {
res &= has_matches > 0;
}
if !self.has_not.is_empty() {
res &= has_not_matches > 0;
}
res
}
}
}
}
pub struct AttributeValueConfig<'s, const A: usize, const B: usize> {
pub key: &'s str,
pub has: [&'s str; A],
pub has_not: [&'s str; B],
pub match_all: bool,
}
pub struct AttributeValue {
name: string::String,
key: String,
has: Vec<String>,
has_not: Vec<String>,
match_all: bool,
str_cache: String,
}
impl AttributeValue {
pub fn new<const A: usize, const B: usize>(conf: AttributeValueConfig<A, B>) -> Self {
Self {
name: format!(
"attribute value matcher (on \"{key}\" {how} has:{has}, has_not:{has_not}",
key = conf.key,
how = if conf.match_all { "all" } else { "any" },
has = conf.has.len(),
has_not = conf.has_not.len()
),
key: conf.key.into(),
match_all: conf.match_all,
has: conf.has.iter().map(|x: &&str| x.to_string()).collect(),
has_not: conf.has_not.iter().map(|x: &&str| x.to_string()).collect(),
str_cache: String::new(),
}
}
}
impl filter::Filter for AttributeValue {
fn name(&self) -> &str {
self.name.as_str()
}
fn pass(&mut self, _: &sink::LogUpdate, attrs: &attributes::Map) -> bool {
let Some(val) = attrs.get(self.key.as_str()) else {
return false;
};
if self.has.is_empty() && self.has_not.is_empty() {
return true;
}
let mut has_matches: usize = 0;
let mut has_not_matches: usize = 0;
for i in 0..cmp::max(self.has.len(), self.has_not.len()) {
let mut found_has: bool = false;
let mut found_has_not: bool = false;
let ss = match val {
attributes::Value::Scalar(ref s) => &[s.clone()],
attributes::Value::List(ss) => ss,
attributes::Value::Map(_, ss) => ss,
};
for s in ss {
s.into_string(&mut self.str_cache, attrs);
if !found_has && i < self.has.len() {
found_has = self.str_cache.contains(self.has[i].as_str())
}
if !found_has_not && i < self.has_not.len() {
found_has_not = self.str_cache.contains(self.has_not[i].as_str());
}
}
if found_has {
has_matches += 1;
}
if !found_has_not {
has_not_matches += 1;
}
}
match self.match_all {
true => (has_matches == self.has.len()) && (has_not_matches == self.has_not.len()),
false => (!self.has.is_empty() && has_matches > 0) || (!self.has_not.is_empty() && has_not_matches > 0),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use ntime::Timestamp;
use crate::attributes::Value;
use crate::filter::Filter;
use crate::level::Level;
#[test]
fn message() {
fn run(mut filter: Message, want: bool) {
let args = attributes::Map::new();
let update = sink::LogUpdate::new(Timestamp::now(), Level::Info, "this is a test log".into());
assert_eq!(filter.pass(&update, &args), want);
}
run(
Message::new(MessageConfig {
has: [],
has_not: [],
match_all: false,
}),
true,
);
run(
Message::new(MessageConfig {
has: ["this is a"],
has_not: [],
match_all: false,
}),
true,
);
run(
Message::new(MessageConfig {
has: ["thIS IS a"],
has_not: [],
match_all: false,
}),
false,
);
run(
Message::new(MessageConfig {
has: ["test", "log"],
has_not: [],
match_all: false,
}),
true,
);
run(
Message::new(MessageConfig {
has: ["test", "log"],
has_not: [],
match_all: true,
}),
true,
);
run(
Message::new(MessageConfig {
has: ["tEsT", "log"],
has_not: [],
match_all: true,
}),
false,
);
run(
Message::new(MessageConfig {
has: ["tEsT", "log"],
has_not: [],
match_all: false,
}),
true,
);
run(
Message::new(MessageConfig {
has: ["tEsT", "lXg"],
has_not: [],
match_all: true,
}),
false,
);
run(
Message::new(MessageConfig {
has: ["tEsT", "lXg"],
has_not: [],
match_all: false,
}),
false,
);
run(
Message::new(MessageConfig {
has: [],
has_not: ["this is a"],
match_all: false,
}),
false,
);
run(
Message::new(MessageConfig {
has: [],
has_not: ["thIS IS a"],
match_all: false,
}),
true,
);
run(
Message::new(MessageConfig {
has: [],
has_not: ["test", "log"],
match_all: false,
}),
false,
);
run(
Message::new(MessageConfig {
has: [],
has_not: ["test", "log"],
match_all: true,
}),
false,
);
run(
Message::new(MessageConfig {
has: [],
has_not: ["tEsT", "log"],
match_all: true,
}),
false,
);
run(
Message::new(MessageConfig {
has: [],
has_not: ["tEsT", "log"],
match_all: false,
}),
true,
);
run(
Message::new(MessageConfig {
has: [],
has_not: ["tEsT", "lXg"],
match_all: true,
}),
true,
);
run(
Message::new(MessageConfig {
has: [],
has_not: ["tEsT", "lXg"],
match_all: false,
}),
true,
);
run(
Message::new(MessageConfig {
has: ["this", "is"],
has_not: ["test", "log"],
match_all: false,
}),
false,
);
run(
Message::new(MessageConfig {
has: ["this", "is"],
has_not: ["test", "log"],
match_all: true,
}),
false,
);
run(
Message::new(MessageConfig {
has: ["this", "is"],
has_not: ["tEsT", "lXg"],
match_all: false,
}),
true,
);
run(
Message::new(MessageConfig {
has: ["this", "is"],
has_not: ["tEsT", "lXg"],
match_all: true,
}),
true,
);
run(
Message::new(MessageConfig {
has: ["tHiS", "is"],
has_not: ["tEsT", "log"],
match_all: false,
}),
true,
);
run(
Message::new(MessageConfig {
has: ["tHiS", "is"],
has_not: ["tEsT", "log"],
match_all: true,
}),
false,
);
}
#[test]
fn attribute_keys_single() {
fn run(mut filter: AttributeKey, want: bool) {
let mut args = attributes::Map::new();
args.insert("a_string", Value::from("hello there!"));
args.insert("an_int", Value::from(12345));
args.insert("a_float", Value::from(6789.0123 as f32));
let update = sink::LogUpdate::new(Timestamp::now(), Level::Info, "unused update :(".into());
assert_eq!(filter.pass(&update, &args), want);
}
run(
AttributeKey::new(AttributeKeyConfig {
has: [],
has_not: [],
match_all: false,
}),
true,
);
run(
AttributeKey::new(AttributeKeyConfig {
has: ["a_bool"],
has_not: [],
match_all: false,
}),
false,
);
run(
AttributeKey::new(AttributeKeyConfig {
has: ["a_string", "a_bool"],
has_not: [],
match_all: false,
}),
true,
);
run(
AttributeKey::new(AttributeKeyConfig {
has: ["a_string", "a_bool"],
has_not: [],
match_all: true,
}),
false,
);
run(
AttributeKey::new(AttributeKeyConfig {
has: [],
has_not: ["a_float"],
match_all: false,
}),
false,
);
run(
AttributeKey::new(AttributeKeyConfig {
has: [],
has_not: ["a_string", "a_bool"],
match_all: false,
}),
true,
);
run(
AttributeKey::new(AttributeKeyConfig {
has: [],
has_not: ["a_string", "a_bool"],
match_all: true,
}),
false,
);
run(
AttributeKey::new(AttributeKeyConfig {
has: ["an_int", "a_float"],
has_not: ["a_bool", "an_usize"],
match_all: false,
}),
true,
);
run(
AttributeKey::new(AttributeKeyConfig {
has: ["an_int", "a_float"],
has_not: ["a_bool", "an_usize"],
match_all: true,
}),
true,
);
run(
AttributeKey::new(AttributeKeyConfig {
has: ["an_int", "a_bool"],
has_not: ["a_float", "an_usize"],
match_all: false,
}),
true,
);
run(
AttributeKey::new(AttributeKeyConfig {
has: ["an_int", "a_bool"],
has_not: ["a_float", "an_usize"],
match_all: true,
}),
false,
);
}
#[test]
fn attribute_keys_multi() {
}
#[test]
fn attribute_values() {
fn run(mut filter: AttributeValue, want: bool) {
let mut args = attributes::Map::new();
args.insert("a_string", Value::from("hello there!"));
args.insert("an_int", Value::from(12345));
args.insert("a_float", Value::from(6789.0123 as f32));
let update = sink::LogUpdate::new(Timestamp::now(), Level::Info, "unused update :(".into());
assert_eq!(filter.pass(&update, &args), want);
}
run(
AttributeValue::new(AttributeValueConfig {
key: "",
has: [],
has_not: [],
match_all: false,
}),
false,
);
run(
AttributeValue::new(AttributeValueConfig {
key: "a_string",
has: [],
has_not: [],
match_all: false,
}),
true,
);
run(
AttributeValue::new(AttributeValueConfig {
key: "wrong",
has: [],
has_not: [],
match_all: false,
}),
false,
);
run(
AttributeValue::new(AttributeValueConfig {
key: "an_int",
has: ["1234", "wrong"],
has_not: [],
match_all: false,
}),
true,
);
run(
AttributeValue::new(AttributeValueConfig {
key: "an_int",
has: ["1234", "wrong"],
has_not: [],
match_all: true,
}),
false,
);
run(
AttributeValue::new(AttributeValueConfig {
key: "a_string",
has: [],
has_not: ["hello", "tHeRe"],
match_all: false,
}),
true,
);
run(
AttributeValue::new(AttributeValueConfig {
key: "a_string",
has: [],
has_not: ["hello", "tHeRe"],
match_all: true,
}),
false,
);
run(
AttributeValue::new(AttributeValueConfig {
key: "a_string",
has: ["hello", "tHeRe"],
has_not: ["there!", "123456"],
match_all: false,
}),
true,
);
run(
AttributeValue::new(AttributeValueConfig {
key: "a_string",
has: ["hello", "tHeRe"],
has_not: ["there!", "123456"],
match_all: true,
}),
false,
);
}
}