use pest::Parser; use pest_derive::Parser; use std::collections::HashMap; #[derive(Parser)] #[grammar = "filter.pest"] pub struct FilterParser; #[derive(Debug)] pub struct Filter { pub name: String, pub options: HashMap, } pub fn parse_filter_string(input: &str) -> Result, Box> { let mut filters = Vec::new(); let pairs = FilterParser::parse(Rule::filters, input)?; for pair in pairs { if pair.as_rule() == Rule::filter { let mut name = String::new(); let mut options = HashMap::new(); for inner_pair in pair.into_inner() { match inner_pair.as_rule() { Rule::filter_name => { name = inner_pair.as_str().to_string(); } Rule::options => { for option_pair in inner_pair.into_inner() { if option_pair.as_rule() == Rule::option { let mut option_name = None; let mut option_value = None; for option_inner in option_pair.into_inner() { match option_inner.as_rule() { Rule::option_name => { option_name = Some(option_inner.as_str().to_string()); } Rule::option_value => { option_value = Some(parse_option_value(option_inner.as_str())?); } _ => {} } } if let Some(value) = option_value { // If no name is provided, use the filter name as the key let key = option_name.unwrap_or_else(|| name.clone()); options.insert(key, value); } } } } _ => {} } } filters.push(Filter { name, options }); } } Ok(filters) } fn parse_option_value(input: &str) -> Result> { // Try to parse as number if let Ok(num) = input.parse::() { return Ok(serde_json::Value::Number(num.into())); } if let Ok(num) = input.parse::() { if let Some(number) = serde_json::Number::from_f64(num) { return Ok(serde_json::Value::Number(number)); } } // Try to parse as boolean if let Ok(boolean) = input.parse::() { return Ok(serde_json::Value::Bool(boolean)); } // Treat as string (remove quotes if present) let value = if input.starts_with('"') && input.ends_with('"') { input[1..input.len()-1].to_string() } else if input.starts_with('\'') && input.ends_with('\'') { input[1..input.len()-1].to_string() } else { input.to_string() }; Ok(serde_json::Value::String(value)) } #[cfg(test)] mod tests { use super::*; #[test] fn test_parse_simple_filter() { let result = parse_filter_string("grep").unwrap(); assert_eq!(result.len(), 1); assert_eq!(result[0].name, "grep"); assert!(result[0].options.is_empty()); } #[test] fn test_parse_filter_with_options() { let result = parse_filter_string("head_lines(10)").unwrap(); assert_eq!(result.len(), 1); assert_eq!(result[0].name, "head_lines"); assert_eq!(result[0].options["head_lines"], 10); } #[test] fn test_parse_filter_with_named_options() { let result = parse_filter_string("grep(pattern=\"error\")").unwrap(); assert_eq!(result.len(), 1); assert_eq!(result[0].name, "grep"); assert_eq!(result[0].options["pattern"], "error"); } #[test] fn test_parse_multiple_filters() { let result = parse_filter_string("head_lines(10), grep(pattern=\"error\")").unwrap(); assert_eq!(result.len(), 2); assert_eq!(result[0].name, "head_lines"); assert_eq!(result[0].options["head_lines"], 10); assert_eq!(result[1].name, "grep"); assert_eq!(result[1].options["pattern"], "error"); } }