use crate::event::Event;
use crate::pipeline::EventParser;
use anyhow::Result;
use rhai::Dynamic;
pub const FORMAT_FIELD: &str = "_format";
pub struct CascadingParser {
parsers: Vec<(String, Box<dyn EventParser>)>,
}
impl CascadingParser {
pub fn new(parsers: Vec<(String, Box<dyn EventParser>)>) -> Self {
Self { parsers }
}
#[allow(dead_code)] pub fn format_names(&self) -> Vec<&str> {
self.parsers.iter().map(|(n, _)| n.as_str()).collect()
}
}
impl EventParser for CascadingParser {
fn parse(&self, line: &str) -> Result<Event> {
let mut last_err: Option<anyhow::Error> = None;
for (name, parser) in &self.parsers {
match parser.parse(line) {
Ok(mut event) => {
event.set_field(FORMAT_FIELD.to_string(), Dynamic::from(name.clone()));
crate::stats::stats_add_cascade_format_hit(name);
return Ok(event);
}
Err(e) => {
last_err = Some(e);
}
}
}
Err(last_err
.unwrap_or_else(|| anyhow::anyhow!("cascade parser has no inner parsers configured")))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parsers::{JsonlParser, LineParser};
#[test]
fn cascade_prefers_first_success() {
let cascade = CascadingParser::new(vec![
("json".to_string(), Box::new(JsonlParser::new())),
("line".to_string(), Box::new(LineParser::new())),
]);
let ev = cascade.parse(r#"{"msg":"hi"}"#).unwrap();
assert_eq!(
ev.fields
.get(FORMAT_FIELD)
.unwrap()
.clone()
.into_string()
.unwrap(),
"json"
);
assert!(ev.fields.contains_key("msg"));
}
#[test]
fn cascade_falls_through_to_line() {
let cascade = CascadingParser::new(vec![
("json".to_string(), Box::new(JsonlParser::new())),
("line".to_string(), Box::new(LineParser::new())),
]);
let ev = cascade.parse("not json at all").unwrap();
assert_eq!(
ev.fields
.get(FORMAT_FIELD)
.unwrap()
.clone()
.into_string()
.unwrap(),
"line"
);
assert!(ev.fields.contains_key("line"));
}
}