128 lines
3.6 KiB
Rust
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,
|
|
}
|