logviewer/src/tui/filter.rs
2026-04-03 16:47:32 +02:00

128 lines
3.6 KiB
Rust

use regex::bytes::Regex;
use serde::{Deserialize, Serialize};
use crate::tui::{
log_viewer::{
LogViewer,
input::{FieldMatcher, InputTarget},
},
model::{FieldsName, LogEntry},
};
mod serialize_regex {
use regex::bytes::Regex;
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error};
pub fn serialize<S: Serializer>(r: &Regex, serializer: S) -> Result<S::Ok, S::Error> {
r.as_str().serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Regex, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Regex::new(&s).map_err(|e| D::Error::custom(e.to_string()))
}
}
#[derive(Serialize, Deserialize, Debug)]
pub enum MatcherValue {
Exact(String),
Regex(#[serde(with = "serialize_regex")] Regex),
Prefix(String),
Contains(String),
}
impl MatcherValue {
pub fn from_field_matcher(fm: FieldMatcher, selected: Option<String>) -> Option<Self> {
match fm {
FieldMatcher::EqualTo => Some(Self::Exact(selected?)),
FieldMatcher::Prefix(p) => Some(Self::Prefix(p)),
FieldMatcher::Regex(r) => Some(Self::Regex(Regex::new(&r).ok()?)),
FieldMatcher::Contains(c) => Some(Self::Contains(c)),
}
}
pub fn matches(&self, v: &str) -> bool {
match self {
MatcherValue::Exact(e) => e == v,
MatcherValue::Regex(regex) => regex.is_match(v.as_bytes()),
MatcherValue::Prefix(p) => v.starts_with(p),
MatcherValue::Contains(c) => v.contains(c),
}
}
}
#[derive(Serialize, Deserialize, Debug)]
pub enum Matcher {
Field {
span: FieldsName,
name: String,
value: MatcherValue,
},
Message {
value: MatcherValue,
},
Specific {
hash: u64,
},
}
impl Matcher {
pub fn matches(&self, entry: &LogEntry) -> bool {
match self {
Matcher::Specific { hash } => entry.hash() == *hash,
Matcher::Field { span, name, value } => entry
.spans()
.find(span, name)
.is_some_and(|v| value.matches(v)),
Matcher::Message { value } => entry.message_or_name().is_some_and(|v| value.matches(v)),
}
}
pub fn from_input(target: InputTarget, lv: &LogViewer) -> Option<Self> {
match target {
InputTarget::Fields(fm) => {
let (span, name, value) = lv.get_selected_field()?;
Some(Self::Field {
span,
name,
value: MatcherValue::from_field_matcher(fm?, Some(value))?,
})
}
InputTarget::Text(fm) => Some(Self::Message {
value: MatcherValue::from_field_matcher(
fm,
lv.selected()
.as_ref()
.and_then(|(i, _)| i.message_or_name())
.map(|i| i.to_string()),
)?,
}),
InputTarget::This => {
if lv
.selected()
.is_none_or(|(i, _)| !i.can_be_inlined(&lv.filters))
{
return None;
}
lv.selected()
.map(|(i, _)| Self::Specific { hash: i.hash() })
}
InputTarget::Surround => todo!(),
}
}
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub enum FilterKind {
Inline,
Remove,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Filter {
pub matcher: Matcher,
pub kind: FilterKind,
}