use pest::iterators::Pair;
use pest::Parser;
use pest_derive::Parser;
use crate::filter::{ComparisonOp, FieldPath, FilterExpr, FilterValue, Template, TemplateItem};
#[derive(Parser)]
#[grammar = "pest/parsm.pest"]
pub struct DSLParser;
#[derive(Debug)]
pub struct ParsedDSL {
pub filter: Option<FilterExpr>,
pub template: Option<Template>,
pub field_selector: Option<FieldPath>,
}
impl ParsedDSL {
pub fn new() -> Self {
Self {
filter: None,
template: None,
field_selector: None,
}
}
}
impl Default for ParsedDSL {
fn default() -> Self {
Self::new()
}
}
impl DSLParser {
pub fn parse_dsl(input: &str) -> Result<ParsedDSL, Box<pest::error::Error<Rule>>> {
let mut pairs = Self::parse(Rule::program, input)?;
let program = pairs.next().unwrap();
let mut result = ParsedDSL::new();
for pair in program.into_inner() {
match pair.as_rule() {
Rule::expression => {
Self::parse_expression(pair, &mut result)?;
}
Rule::EOI => break,
_ => {}
}
}
Ok(result)
}
pub fn parse_separate(
filter_input: Option<&str>,
template_input: Option<&str>,
) -> Result<ParsedDSL, Box<pest::error::Error<Rule>>> {
let mut result = ParsedDSL::new();
if let Some(filter_str) = filter_input {
if let Ok(filter_result) = Self::parse_dsl(filter_str) {
if filter_result.filter.is_some() {
result.filter = filter_result.filter;
} else if filter_result.field_selector.is_some() {
result.field_selector = filter_result.field_selector;
} else {
return Err(Box::new(pest::error::Error::new_from_pos(
pest::error::ErrorVariant::CustomError {
message: format!(
"Could not parse '{filter_str}' as filter expression or field selector",
),
},
pest::Position::new(filter_str, 0).unwrap(),
)));
}
} else {
return Err(Box::new(pest::error::Error::new_from_pos(
pest::error::ErrorVariant::CustomError {
message: format!(
"Could not parse '{filter_str}' as filter expression or field selector",
),
},
pest::Position::new(filter_str, 0).unwrap(),
)));
}
}
if let Some(template_str) = template_input {
if let Ok(template_result) = Self::parse_dsl(template_str) {
result.template = template_result.template;
} else {
return Err(Box::new(pest::error::Error::new_from_pos(
pest::error::ErrorVariant::CustomError {
message: format!("Could not parse '{template_str}' as template"),
},
pest::Position::new(template_str, 0).unwrap(),
)));
}
}
Ok(result)
}
pub fn parse_filter_only(input: &str) -> Result<FilterExpr, Box<pest::error::Error<Rule>>> {
match Self::parse_dsl(input) {
Ok(result) => {
if let Some(filter) = result.filter {
Ok(filter)
} else {
Err(Box::new(pest::error::Error::new_from_pos(
pest::error::ErrorVariant::CustomError {
message: "Input is not a filter expression".to_string(),
},
pest::Position::new(input, 0).unwrap(),
)))
}
}
Err(e) => Err(e),
}
}
pub fn parse_template_only(input: &str) -> Result<Template, Box<pest::error::Error<Rule>>> {
match Self::parse_dsl(input) {
Ok(result) => {
if let Some(template) = result.template {
Ok(template)
} else {
Err(Box::new(pest::error::Error::new_from_pos(
pest::error::ErrorVariant::CustomError {
message: "Input is not a template expression".to_string(),
},
pest::Position::new(input, 0).unwrap(),
)))
}
}
Err(e) => Err(e),
}
}
pub fn parse_field_selector_only(
input: &str,
) -> Result<FieldPath, Box<pest::error::Error<Rule>>> {
match Self::parse_dsl(input) {
Ok(result) => {
if let Some(field_selector) = result.field_selector {
Ok(field_selector)
} else {
Err(Box::new(pest::error::Error::new_from_pos(
pest::error::ErrorVariant::CustomError {
message: "Input is not a field selector".to_string(),
},
pest::Position::new(input, 0).unwrap(),
)))
}
}
Err(e) => Err(e),
}
}
fn parse_expression(
pair: Pair<Rule>,
result: &mut ParsedDSL,
) -> Result<(), Box<pest::error::Error<Rule>>> {
let inner = pair.into_inner().next().unwrap();
match inner.as_rule() {
Rule::combined_expr => {
let mut inner_pairs = inner.into_inner();
let filter_pair = inner_pairs.next().unwrap();
let template_pair = inner_pairs.next().unwrap();
result.filter = Some(Self::parse_filter_expr(filter_pair)?);
result.template = Some(Self::parse_template_expr(template_pair)?);
}
Rule::template_expr => {
result.template = Some(Self::parse_template_expr(inner)?);
}
Rule::filter_expr => {
result.filter = Some(Self::parse_filter_expr(inner)?);
}
Rule::field_selector => {
result.field_selector = Some(Self::parse_field_selector(inner));
}
_ => {}
}
Ok(())
}
fn parse_template_expr(pair: Pair<Rule>) -> Result<Template, Box<pest::error::Error<Rule>>> {
let inner = pair.into_inner().next().unwrap();
match inner.as_rule() {
Rule::braced_template => Self::parse_braced_template(inner),
Rule::simple_variable => {
let field_path = Self::parse_field_path_from_simple_var(inner);
Ok(Template {
items: vec![TemplateItem::Field(field_path)],
})
}
Rule::interpolated_text => Self::parse_interpolated_text(inner),
_ => unreachable!("Unexpected template expression type"),
}
}
fn parse_braced_template(pair: Pair<Rule>) -> Result<Template, Box<pest::error::Error<Rule>>> {
let template_content = pair.into_inner().next().unwrap();
match template_content.as_rule() {
Rule::template_content_atomic => {
Self::parse_template_content_manually(template_content.as_str())
}
_ => {
Self::parse_template_content_manually(template_content.as_str())
}
}
}
pub fn parse_template_content_manually(
content: &str,
) -> Result<Template, Box<pest::error::Error<Rule>>> {
let mut items = Vec::new();
let mut chars = content.chars().peekable();
let mut current_text = String::new();
while let Some(ch) = chars.next() {
if ch == '$' {
if chars.peek() == Some(&'{') {
chars.next();
if !current_text.is_empty() {
items.push(TemplateItem::Literal(current_text.clone()));
current_text.clear();
}
let mut var_name = String::new();
let mut brace_depth = 1;
while chars.peek().is_some() {
let ch = chars.next().unwrap();
if ch == '{' {
brace_depth += 1;
var_name.push(ch);
} else if ch == '}' {
brace_depth -= 1;
if brace_depth == 0 {
break;
} else {
var_name.push(ch);
}
} else {
var_name.push(ch);
}
}
if !var_name.is_empty() {
let field_path = Self::parse_field_name(&var_name);
items.push(TemplateItem::Field(field_path));
}
} else {
if !current_text.is_empty() {
items.push(TemplateItem::Literal(current_text.clone()));
current_text.clear();
}
let mut var_name = String::new();
if let Some(&first_ch) = chars.peek() {
if first_ch.is_alphabetic() || first_ch == '_' {
var_name.push(chars.next().unwrap());
while let Some(&next_ch) = chars.peek() {
if next_ch.is_alphanumeric() || next_ch == '_' || next_ch == '.' {
var_name.push(chars.next().unwrap());
} else {
break;
}
}
}
}
if !var_name.is_empty() {
let field_path = Self::parse_field_name(&var_name);
items.push(TemplateItem::Field(field_path));
} else {
current_text.push(ch);
}
}
} else {
current_text.push(ch);
}
}
if !current_text.is_empty() {
items.push(TemplateItem::Literal(current_text));
}
if items.is_empty() {
}
Ok(Template { items })
}
fn parse_interpolated_text(
pair: Pair<Rule>,
) -> Result<Template, Box<pest::error::Error<Rule>>> {
let mut items = Vec::new();
for part in pair.into_inner() {
match part.as_rule() {
Rule::template_variable => {
let field_path = Self::parse_template_variable(part);
items.push(TemplateItem::Field(field_path));
}
Rule::literal_text => {
let text = part.as_str().to_string();
if !text.is_empty() {
items.push(TemplateItem::Literal(text));
}
}
_ => {}
}
}
Ok(Template { items })
}
fn parse_template_variable(pair: Pair<Rule>) -> FieldPath {
let inner = pair.into_inner().next().unwrap();
match inner.as_rule() {
Rule::var_content => {
let content = inner.as_str();
Self::parse_field_name(content)
}
Rule::simple_var_name => {
let content = inner.as_str();
Self::parse_field_name(content)
}
_ => unreachable!("Unexpected template variable type"),
}
}
fn parse_field_path_from_simple_var(pair: Pair<Rule>) -> FieldPath {
let var_name_pair = pair.into_inner().next().unwrap();
let var_name = var_name_pair.as_str();
let parts: Vec<String> = var_name.split('.').map(|s| s.to_string()).collect();
FieldPath::new(parts)
}
fn parse_field_path(pair: Pair<Rule>) -> FieldPath {
let parts: Vec<String> = pair
.into_inner()
.map(|component| component.as_str().to_string())
.collect();
FieldPath::new(parts)
}
fn parse_field_name(field_name: &str) -> FieldPath {
if field_name == "0" {
return FieldPath::new(vec!["$0".to_string()]);
}
if let Ok(index) = field_name.parse::<usize>() {
if index > 0 {
return FieldPath::new(vec![format!("field_{}", index - 1)]);
}
}
let parts: Vec<String> = field_name
.split('.')
.map(|s| s.trim().to_string())
.collect();
FieldPath::new(parts)
}
fn parse_field_selector(pair: Pair<Rule>) -> FieldPath {
let inner = pair.into_inner().next().unwrap();
match inner.as_rule() {
Rule::quoted_field => {
let string_literal = inner.into_inner().next().unwrap();
let content = Self::parse_string_literal(string_literal);
let parts: Vec<String> = content.split('.').map(|s| s.to_string()).collect();
FieldPath::new(parts)
}
Rule::field_access => Self::parse_field_path(inner),
_ => unreachable!("Unexpected field selector type"),
}
}
fn parse_string_literal(pair: Pair<Rule>) -> String {
let string_content = pair.into_inner().next().unwrap();
string_content.as_str().to_string()
}
fn parse_filter_expr(pair: Pair<Rule>) -> Result<FilterExpr, Box<pest::error::Error<Rule>>> {
let inner = pair.into_inner().next().unwrap();
Self::parse_condition(inner)
}
fn parse_condition(pair: Pair<Rule>) -> Result<FilterExpr, Box<pest::error::Error<Rule>>> {
let inner = pair.into_inner().next().unwrap();
Self::parse_or_expr(inner)
}
fn parse_or_expr(pair: Pair<Rule>) -> Result<FilterExpr, Box<pest::error::Error<Rule>>> {
let span = pair.as_span();
let mut inner = pair.into_inner();
let mut left = Self::parse_and_expr(inner.next().ok_or_else(|| {
pest::error::Error::new_from_pos(
pest::error::ErrorVariant::CustomError {
message: "Expected first expression in OR".to_string(),
},
span.start_pos(),
)
})?)?;
while let Some(op_pair) = inner.next() {
if matches!(op_pair.as_rule(), Rule::or_op) {
let right = Self::parse_and_expr(inner.next().ok_or_else(|| {
pest::error::Error::new_from_pos(
pest::error::ErrorVariant::CustomError {
message: "Expected second expression after OR".to_string(),
},
span.start_pos(),
)
})?)?;
left = FilterExpr::Or(Box::new(left), Box::new(right));
}
}
Ok(left)
}
fn parse_and_expr(pair: Pair<Rule>) -> Result<FilterExpr, Box<pest::error::Error<Rule>>> {
let mut inner = pair.into_inner();
let mut left = Self::parse_comparison(inner.next().unwrap())?;
while let Some(op_pair) = inner.next() {
if matches!(op_pair.as_rule(), Rule::and_op) {
let right = Self::parse_comparison(inner.next().unwrap())?;
left = FilterExpr::And(Box::new(left), Box::new(right));
}
}
Ok(left)
}
fn parse_comparison(pair: Pair<Rule>) -> Result<FilterExpr, Box<pest::error::Error<Rule>>> {
let mut inner = pair.into_inner();
let first = inner.next().unwrap();
match first.as_rule() {
Rule::not_op => {
let next = inner.next().unwrap();
let comparison = Self::parse_comparison(next)?;
Ok(FilterExpr::Not(Box::new(comparison)))
}
Rule::field_access => {
let field = Self::parse_field_path(first);
if let Some(op_pair) = inner.next() {
let op = Self::parse_comparison_op(op_pair);
let value_pair = inner.next().unwrap();
let value = Self::parse_value(value_pair);
Ok(FilterExpr::Comparison { field, op, value })
} else {
Ok(FilterExpr::FieldTruthy(field))
}
}
_ => Self::parse_condition(first),
}
}
fn parse_comparison_op(pair: Pair<Rule>) -> ComparisonOp {
crate::operators::parse_comparison_op(pair.as_str())
}
fn parse_value(pair: Pair<Rule>) -> FilterValue {
let inner = pair.into_inner().next().unwrap();
match inner.as_rule() {
Rule::string_literal => {
let content = Self::parse_string_literal(inner);
FilterValue::String(content)
}
Rule::number => {
let number_str = inner.as_str();
let num: f64 = number_str.parse().unwrap_or(0.0);
FilterValue::Number(num)
}
Rule::boolean => {
let bool_val = inner.as_str() == "true";
FilterValue::Boolean(bool_val)
}
Rule::null => FilterValue::Null,
Rule::unquoted_value => FilterValue::String(inner.as_str().to_string()),
_ => FilterValue::String(inner.as_str().to_string()),
}
}
}
pub fn parse_command(input: &str) -> Result<ParsedDSL, Box<dyn std::error::Error>> {
let trimmed = input.trim();
match DSLParser::parse_dsl(trimmed) {
Ok(result) => Ok(result),
Err(_parse_error) => {
if let Ok(result) = try_boolean_with_truthy_fields(trimmed) {
return Ok(result);
}
if let Ok(result) = try_simple_template_patterns(trimmed) {
return Ok(result);
}
if let Ok(result) = try_as_field_selector(trimmed) {
return Ok(result);
}
if let Ok(result) = try_manual_parsing(trimmed) {
return Ok(result);
}
Err(format!(
"Could not parse '{trimmed}'. Try:\n - Templates: {{name}}, $name, or Hello $name\n - Filters: name == \"value\" or age > 25\n - Field selectors: name or \"field name\""
).into())
}
}
}
fn try_simple_template_patterns(input: &str) -> Result<ParsedDSL, Box<dyn std::error::Error>> {
let mut result = ParsedDSL::new();
if input.starts_with('{') && input.ends_with('}') {
let content = &input[1..input.len() - 1];
if !content.trim().is_empty() {
if content.contains('$') {
if let Ok(template) = DSLParser::parse_template_content_manually(content) {
result.template = Some(template);
return Ok(result);
}
}
result.template = Some(Template {
items: vec![TemplateItem::Literal(content.to_string())],
});
return Ok(result);
}
}
if input.starts_with('$') && !input.contains(' ') && !input.starts_with("${") {
let field_name = &input[1..];
if !field_name.is_empty() && !field_name.chars().all(|c| c.is_ascii_digit()) {
let field_path = parse_field_name_simple(field_name);
result.template = Some(Template {
items: vec![TemplateItem::Field(field_path)],
});
return Ok(result);
}
}
if input.contains('$') && !input.starts_with('{') {
if let Some(template) = parse_interpolated_template_simple(input) {
result.template = Some(template);
return Ok(result);
}
}
Err("Not a simple template pattern".into())
}
fn try_as_field_selector(input: &str) -> Result<ParsedDSL, Box<dyn std::error::Error>> {
let mut result = ParsedDSL::new();
if (input.starts_with('"') && input.ends_with('"'))
|| (input.starts_with('\'') && input.ends_with('\''))
{
let content = &input[1..input.len() - 1];
let parts: Vec<String> = content.split('.').map(|s| s.to_string()).collect();
result.field_selector = Some(FieldPath::new(parts));
return Ok(result);
}
if input
.chars()
.all(|c| c.is_alphanumeric() || c == '_' || c == '.')
&& !input.is_empty()
&& !input.contains(' ')
&& !input.contains('$')
&& !input.contains('{')
&& !input.contains('}')
&& !input.contains('!')
&& !input.contains('=')
&& !input.contains('>')
&& !input.contains('<')
&& !input.contains('&')
&& !input.contains('|')
{
let parts: Vec<String> = input.split('.').map(|s| s.to_string()).collect();
result.field_selector = Some(FieldPath::new(parts));
return Ok(result);
}
Err("Not a field selector".into())
}
fn try_manual_parsing(input: &str) -> Result<ParsedDSL, Box<dyn std::error::Error>> {
if let Some((filter_part, template_part)) = split_filter_template_manually(input) {
let mut result = ParsedDSL::new();
if let Ok(boolean_result) = try_boolean_with_truthy_fields(filter_part) {
result.filter = boolean_result.filter;
} else if let Ok(filter) = parse_simple_filter(filter_part) {
result.filter = Some(filter);
}
if let Ok(template_result) = try_simple_template_patterns(template_part) {
result.template = template_result.template;
}
if result.filter.is_some() || result.template.is_some() {
return Ok(result);
}
}
if let Ok(filter) = parse_simple_filter(input) {
let mut result = ParsedDSL::new();
result.filter = Some(filter);
return Ok(result);
}
Err("Could not manually parse".into())
}
fn parse_field_name_simple(field_name: &str) -> FieldPath {
if field_name == "0" {
return FieldPath::new(vec!["$0".to_string()]);
}
if let Ok(index) = field_name.parse::<usize>() {
if index > 0 {
return FieldPath::new(vec![format!("field_{}", index - 1)]);
}
}
let parts: Vec<String> = field_name
.split('.')
.map(|s| s.trim().to_string())
.collect();
FieldPath::new(parts)
}
fn parse_interpolated_template_simple(input: &str) -> Option<Template> {
let mut items = Vec::new();
let mut current_text = String::new();
let mut chars = input.chars().peekable();
while let Some(ch) = chars.next() {
if ch == '$' {
if let Some(&next_ch) = chars.peek() {
if next_ch.is_alphabetic() || next_ch == '_' {
if !current_text.is_empty() {
items.push(TemplateItem::Literal(current_text.clone()));
current_text.clear();
}
let mut var_name = String::new();
while let Some(&var_ch) = chars.peek() {
if var_ch.is_alphanumeric() || var_ch == '_' || var_ch == '.' {
var_name.push(chars.next().unwrap());
} else {
break;
}
}
let field_path = parse_field_name_simple(&var_name);
items.push(TemplateItem::Field(field_path));
} else {
current_text.push('$');
}
} else {
current_text.push('$');
}
} else {
current_text.push(ch);
}
}
if !current_text.is_empty() {
items.push(TemplateItem::Literal(current_text));
}
if !items.is_empty() {
Some(Template { items })
} else {
None
}
}
fn split_filter_template_manually(input: &str) -> Option<(&str, &str)> {
if let Some(brace_pos) = input.find('{') {
let filter_part = input[..brace_pos].trim();
let template_part = input[brace_pos..].trim();
if !filter_part.is_empty() && !template_part.is_empty() {
return Some((filter_part, template_part));
}
}
None
}
fn parse_simple_filter(input: &str) -> Result<FilterExpr, Box<dyn std::error::Error>> {
let parts: Vec<&str> = input.split_whitespace().collect();
if parts.len() >= 3 {
let field_name = parts[0];
let operator = parts[1];
let value_str = parts[2..].join(" ");
let field_parts: Vec<String> = field_name.split('.').map(|s| s.to_string()).collect();
let field = FieldPath::new(field_parts);
let op = match operator {
"==" => ComparisonOp::Equal,
"!=" => ComparisonOp::NotEqual,
">" => ComparisonOp::GreaterThan,
"<" => ComparisonOp::LessThan,
">=" => ComparisonOp::GreaterThanOrEqual,
"<=" => ComparisonOp::LessThanOrEqual,
_ => return Err("Unknown operator".into()),
};
let value = if value_str.starts_with('"') && value_str.ends_with('"') {
FilterValue::String(value_str[1..value_str.len() - 1].to_string())
} else if value_str == "true" {
FilterValue::Boolean(true)
} else if value_str == "false" {
FilterValue::Boolean(false)
} else if value_str == "null" {
FilterValue::Null
} else if let Ok(num) = value_str.parse::<f64>() {
FilterValue::Number(num)
} else {
FilterValue::String(value_str)
};
return Ok(FilterExpr::Comparison { field, op, value });
}
Err("Could not parse as simple filter".into())
}
fn try_boolean_with_truthy_fields(input: &str) -> Result<ParsedDSL, Box<dyn std::error::Error>> {
if input.contains("&&") {
return try_parse_and_expression(input);
}
if input.contains("||") {
return try_parse_or_expression(input);
}
Err("Not a boolean expression with explicit operators".into())
}
fn try_parse_and_expression(input: &str) -> Result<ParsedDSL, Box<dyn std::error::Error>> {
let parts: Vec<&str> = input.split("&&").map(|s| s.trim()).collect();
if parts.len() != 2 {
return Err("Complex AND expressions not supported".into());
}
let left = try_parse_single_boolean_term(parts[0])?;
let right = try_parse_single_boolean_term(parts[1])?;
let mut result = ParsedDSL::new();
result.filter = Some(FilterExpr::And(Box::new(left), Box::new(right)));
Ok(result)
}
fn try_parse_or_expression(input: &str) -> Result<ParsedDSL, Box<dyn std::error::Error>> {
let parts: Vec<&str> = input.split("||").map(|s| s.trim()).collect();
if parts.len() != 2 {
return Err("Complex OR expressions not supported".into());
}
let left = try_parse_single_boolean_term(parts[0])?;
let right = try_parse_single_boolean_term(parts[1])?;
let mut result = ParsedDSL::new();
result.filter = Some(FilterExpr::Or(Box::new(left), Box::new(right)));
Ok(result)
}
fn try_parse_single_boolean_term(term: &str) -> Result<FilterExpr, Box<dyn std::error::Error>> {
let trimmed = term.trim();
if let Some(stripped) = trimmed.strip_prefix('!') {
let field_part = stripped.trim();
if is_known_boolean_field(field_part) {
let field_path = parse_field_name_for_truthy(field_part);
return Ok(FilterExpr::Not(Box::new(FilterExpr::FieldTruthy(
field_path,
))));
} else {
return Err(format!(
"Cannot parse '!{field_part}' as boolean term - field not recognized for truthy evaluation"
)
.into());
}
}
if crate::operators::contains_filter_operators(trimmed) {
if let Ok(result) = DSLParser::parse_dsl(trimmed) {
if let Some(filter) = result.filter {
return Ok(filter);
}
}
}
if is_known_boolean_field(trimmed) {
let field_path = parse_field_name_for_truthy(trimmed);
Ok(FilterExpr::FieldTruthy(field_path))
} else {
Err(format!("Cannot parse '{trimmed}' as boolean term - use explicit comparison").into())
}
}
fn is_known_boolean_field(field_name: &str) -> bool {
matches!(
field_name,
"field_2" | "field_3" | "verified" | "premium" | "active"
)
}
fn parse_field_name_for_truthy(name: &str) -> FieldPath {
let parts: Vec<String> = name.split('.').map(|s| s.to_string()).collect();
FieldPath::new(parts)
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_new_template_syntax() {
let result = parse_command("{${state}}").unwrap();
assert!(result.template.is_some());
assert!(result.filter.is_none());
assert!(result.field_selector.is_none());
let template = result.template.unwrap();
assert_eq!(template.items.len(), 1);
match &template.items[0] {
TemplateItem::Field(field) => assert_eq!(field.parts, vec!["state"]),
_ => panic!("Expected field"),
}
let result = parse_command("$state").unwrap();
assert!(result.template.is_some());
assert!(result.filter.is_none());
assert!(result.field_selector.is_none());
let template = result.template.unwrap();
assert_eq!(template.items.len(), 1);
match &template.items[0] {
TemplateItem::Field(field) => assert_eq!(field.parts, vec!["state"]),
_ => panic!("Expected field"),
} let result = parse_command("{state}").unwrap();
assert!(result.template.is_some());
assert!(result.filter.is_none());
assert!(result.field_selector.is_none());
let template = result.template.unwrap();
assert_eq!(template.items.len(), 1);
match &template.items[0] {
TemplateItem::Literal(text) => assert_eq!(text, "state"),
_ => panic!("Expected literal (not field)"),
}
let result = parse_command("${state}").unwrap();
assert!(result.template.is_some());
assert!(result.filter.is_none());
assert!(result.field_selector.is_none());
let template = result.template.unwrap();
assert_eq!(template.items.len(), 1);
match &template.items[0] {
TemplateItem::Literal(text) => assert_eq!(text, "${state}"),
_ => panic!("Expected literal"),
}
}
#[test]
fn test_mixed_template_syntax() {
let result = parse_command("{State of ${state}}").unwrap();
assert!(result.template.is_some());
assert!(result.filter.is_none());
assert!(result.field_selector.is_none());
let template = result.template.unwrap();
assert_eq!(template.items.len(), 2);
match &template.items[0] {
TemplateItem::Literal(text) => assert_eq!(text, "State of "),
_ => panic!("Expected literal"),
}
match &template.items[1] {
TemplateItem::Field(field) => assert_eq!(field.parts, vec!["state"]),
_ => panic!("Expected field"),
}
let result = parse_command("{${name} is ${age} years old}").unwrap();
assert!(result.template.is_some());
let template = result.template.unwrap();
assert_eq!(template.items.len(), 4);
match &template.items[0] {
TemplateItem::Field(field) => assert_eq!(field.parts, vec!["name"]),
_ => panic!("Expected field"),
}
match &template.items[1] {
TemplateItem::Literal(text) => assert_eq!(text, " is "),
_ => panic!("Expected literal"),
}
match &template.items[2] {
TemplateItem::Field(field) => assert_eq!(field.parts, vec!["age"]),
_ => panic!("Expected field"),
}
match &template.items[3] {
TemplateItem::Literal(text) => assert_eq!(text, " years old"),
_ => panic!("Expected literal"),
}
}
#[test]
fn test_field_selector_disambiguation() {
let result = parse_command("name").unwrap();
println!(
"Debug: name parsed as - filter: {:?}, template: {:?}, field_selector: {:?}",
result.filter.is_some(),
result.template.is_some(),
result.field_selector.is_some()
);
assert!(result.field_selector.is_some());
assert!(result.filter.is_none());
assert!(result.template.is_none());
let result = parse_command("user.name").unwrap();
assert!(result.field_selector.is_some());
assert!(result.filter.is_none());
assert!(result.template.is_none());
let result = parse_command("\"name\"").unwrap();
assert!(result.field_selector.is_some());
assert!(result.filter.is_none());
assert!(result.template.is_none());
let result = parse_command("'user.name'").unwrap();
assert!(result.field_selector.is_some());
assert!(result.filter.is_none());
assert!(result.template.is_none());
}
#[test]
fn test_filter_expressions() {
let result = parse_command(r#"name == "Alice""#).unwrap();
assert!(result.filter.is_some());
assert!(result.template.is_none());
assert!(result.field_selector.is_none());
if let Some(FilterExpr::Comparison { field, op, value }) = result.filter {
assert_eq!(field.parts, vec!["name"]);
assert_eq!(op, ComparisonOp::Equal);
assert_eq!(value, FilterValue::String("Alice".to_string()));
} else {
panic!("Expected simple comparison");
}
let result = parse_command("age > 25").unwrap();
assert!(result.filter.is_some());
assert!(result.template.is_none());
assert!(result.field_selector.is_none());
}
#[test]
fn test_combined_expressions() {
let result = parse_command("age > 25 {${name}}").unwrap();
assert!(result.filter.is_some());
assert!(result.template.is_some());
assert!(result.field_selector.is_none());
let result = parse_command("name == \"Alice\" {Name: ${name}, Age: ${age}}").unwrap();
assert!(result.filter.is_some());
assert!(result.template.is_some());
assert!(result.field_selector.is_none());
let template = result.template.unwrap();
assert_eq!(template.items.len(), 4);
match &template.items[0] {
TemplateItem::Literal(text) => assert_eq!(text, "Name: "),
_ => panic!("Expected literal"),
}
match &template.items[1] {
TemplateItem::Field(field) => assert_eq!(field.parts, vec!["name"]),
_ => panic!("Expected field"),
}
match &template.items[2] {
TemplateItem::Literal(text) => assert_eq!(text, ", Age: "),
_ => panic!("Expected literal"),
}
match &template.items[3] {
TemplateItem::Field(field) => assert_eq!(field.parts, vec!["age"]),
_ => panic!("Expected field"),
}
}
#[test]
fn test_combined_expression_simple() {
let simple_pattern = r#"field_1 > "25" {name: ${field_0}}"#;
println!("Testing simple pattern: {simple_pattern}");
match parse_command(simple_pattern) {
Ok(result) => {
println!("✓ Simple pattern parsed:");
println!(" Filter: {:?}", result.filter.is_some());
println!(" Template: {:?}", result.template.is_some());
println!(" Field selector: {:?}", result.field_selector.is_some());
}
Err(e) => {
println!("✗ Simple pattern failed: {e}");
}
}
let complex_pattern = r#"field_1 > "25" {{"name": "${field_0}", "age": "${field_1}", "role": "${field_2}", "senior": true}}"#;
println!("\nTesting complex pattern: {complex_pattern}");
match parse_command(complex_pattern) {
Ok(result) => {
println!("✓ Complex pattern parsed:");
println!(" Filter: {:?}", result.filter.is_some());
println!(" Template: {:?}", result.template.is_some());
println!(" Field selector: {:?}", result.field_selector.is_some());
}
Err(e) => {
println!("✗ Complex pattern failed: {e}");
}
}
}
#[test]
fn test_complex_filters() {
let result = parse_command("name == \"Alice\" && age > 25").unwrap();
assert!(result.filter.is_some());
match result.filter {
Some(FilterExpr::And(left, right)) => {
if let FilterExpr::Comparison { field, op, value } = left.as_ref() {
assert_eq!(field.parts, vec!["name"]);
assert_eq!(*op, ComparisonOp::Equal);
assert_eq!(*value, FilterValue::String("Alice".to_string()));
} else {
panic!("Expected comparison on left");
}
if let FilterExpr::Comparison { field, op, value } = right.as_ref() {
assert_eq!(field.parts, vec!["age"]);
assert_eq!(*op, ComparisonOp::GreaterThan);
assert_eq!(*value, FilterValue::Number(25.0));
} else {
panic!("Expected comparison on right");
}
}
Some(FilterExpr::Comparison { field, op, value }) => {
assert_eq!(field.parts, vec!["name"]);
assert_eq!(op, ComparisonOp::Equal);
assert_eq!(value, FilterValue::String("Alice".to_string()));
eprintln!("Warning: Complex filter was simplified to single comparison");
}
_ => panic!("Expected some form of filter expression"),
}
}
#[test]
fn test_nested_field_access() {
let result = parse_command("user.email == \"alice@example.com\"").unwrap();
if let Some(FilterExpr::Comparison { field, .. }) = result.filter {
assert_eq!(field.parts, vec!["user", "email"]);
} else {
panic!("Expected comparison with nested field");
}
let result = parse_command("{${user.name}}").unwrap();
let template = result.template.unwrap();
match &template.items[0] {
TemplateItem::Field(field) => assert_eq!(field.parts, vec!["user", "name"]),
_ => panic!("Expected nested field"),
}
let result = parse_command("user.profile.name").unwrap();
let field = result.field_selector.unwrap();
assert_eq!(field.parts, vec!["user", "profile", "name"]);
}
#[test]
fn test_special_field_references() {
let result = parse_command("{${0}}").unwrap();
let template = result.template.unwrap();
match &template.items[0] {
TemplateItem::Field(field) => assert_eq!(field.parts, vec!["$0"]),
_ => panic!("Expected $0 field"),
}
let result = parse_command("{${1}, ${2}, ${3}}").unwrap();
let template = result.template.unwrap();
assert_eq!(template.items.len(), 5);
match &template.items[0] {
TemplateItem::Field(field) => assert_eq!(field.parts, vec!["field_0"]),
_ => panic!("Expected field_0"),
}
match &template.items[2] {
TemplateItem::Field(field) => assert_eq!(field.parts, vec!["field_1"]),
_ => panic!("Expected field_1"),
}
match &template.items[4] {
TemplateItem::Field(field) => assert_eq!(field.parts, vec!["field_2"]),
_ => panic!("Expected field_2"),
}
}
#[test]
fn test_edge_cases() {
let result = parse_command("{}");
assert!(result.is_ok());
let _result = parse_command("{incomplete");
let result = parse_command("{$$100}").unwrap();
let template = result.template.unwrap();
assert!(!template.items.is_empty());
}
#[test]
fn test_comprehensive_disambiguation() {
let test_cases = vec![
("{${name}}", "template"),
("{State of ${name}}", "template"),
("$name", "template"),
("{name}", "literal_template"),
("name", "field_selector"),
("user.email", "field_selector"),
("\"field name\"", "field_selector"),
("name == \"Alice\"", "filter"),
("age > 25", "filter"),
("user.active == true", "filter"),
("age > 25 {${name}}", "combined"),
("${name}", "literal_or_error"),
];
for (input, expected_type) in test_cases {
let result = parse_command(input);
match expected_type {
"template" => {
assert!(result.is_ok(), "Template '{input}' should parse");
let parsed = result.unwrap();
assert!(
parsed.template.is_some(),
"Input '{input}' should be template"
);
assert!(
parsed.filter.is_none(),
"Input '{input}' should not be filter"
);
assert!(
parsed.field_selector.is_none(),
"Input '{input}' should not be field selector"
);
}
"field_selector" => {
assert!(result.is_ok(), "Field selector '{input}' should parse");
let parsed = result.unwrap();
assert!(
parsed.field_selector.is_some(),
"Input '{input}' should be field selector"
);
assert!(
parsed.filter.is_none(),
"Input '{input}' should not be filter"
);
assert!(
parsed.template.is_none(),
"Input '{input}' should not be template"
);
}
"filter" => {
assert!(result.is_ok(), "Filter '{input}' should parse");
let parsed = result.unwrap();
assert!(parsed.filter.is_some(), "Input '{input}' should be filter");
assert!(
parsed.template.is_none(),
"Input '{input}' should not be template"
);
assert!(
parsed.field_selector.is_none(),
"Input '{input}' should not be field selector"
);
}
"combined" => {
assert!(result.is_ok(), "Combined '{input}' should parse");
let parsed = result.unwrap();
assert!(
parsed.filter.is_some(),
"Input '{input}' should have filter"
);
assert!(
parsed.template.is_some(),
"Input '{input}' should have template"
);
assert!(
parsed.field_selector.is_none(),
"Input '{input}' should not be field selector"
);
}
"literal_template" => {
assert!(result.is_ok(), "Literal template '{input}' should parse");
let parsed = result.unwrap();
assert!(
parsed.template.is_some(),
"Input '{input}' should be template"
);
assert!(
parsed.filter.is_none(),
"Input '{input}' should not be filter"
);
assert!(
parsed.field_selector.is_none(),
"Input '{input}' should not be field selector"
);
let template = parsed.template.unwrap();
if template.items.len() == 1 {
if let TemplateItem::Literal(_) = &template.items[0] {
} else {
panic!(
"Input '{input}' should be literal template, not field template"
);
}
}
}
"literal_or_error" => {
if let Ok(parsed) = result {
if let Some(template) = parsed.template {
if template.items.len() == 1 {
if let TemplateItem::Literal(_) = &template.items[0] {
} else {
panic!("Input '{input}' should be literal template, not field template");
}
}
}
}
}
_ => panic!("Unknown expected type: {expected_type}"),
}
}
}
#[test]
fn test_debug_boolean_logic() {
println!("\n=== Testing Boolean Logic ===");
let test_cases = vec![
"field_2 && field_3",
"!(field_2 && field_3)",
"field_2 == \"true\"",
"field_2 == \"true\" && field_3 == \"false\"",
"!field_3",
"(field_2 && field_3)",
];
for test in test_cases {
println!("\nTesting: {test}");
match parse_command(test) {
Ok(result) => {
println!(" Filter: {:?}", result.filter.is_some());
println!(" Template: {:?}", result.template.is_some());
println!(" Field selector: {:?}", result.field_selector.is_some());
if let Some(filter) = result.filter {
println!(" Filter type: {filter:?}");
}
}
Err(e) => {
println!(" Error: {e}");
}
}
}
}
#[test]
fn debug_boolean_expressions() {
println!("Testing boolean expressions:");
let test_cases = vec![
"field_2 && field_3",
"!(field_2 && field_3)",
"field_2 == \"true\"",
"field_2 == \"true\" && field_3 == \"false\"",
"!field_3",
"(field_2 && field_3)",
];
for test in test_cases {
println!("\nTesting: {test}");
match parse_command(test) {
Ok(result) => {
println!(" Filter: {:?}", result.filter.is_some());
println!(" Template: {:?}", result.template.is_some());
println!(" Field selector: {:?}", result.field_selector.is_some());
if let Some(filter) = result.filter {
println!(" Filter type: {filter:?}");
}
}
Err(e) => {
println!(" Error: {e}");
}
}
}
}
#[test]
fn test_conservative_boolean_parsing() {
match parse_command("field_2 && field_3") {
Ok(result) => {
assert!(result.filter.is_some(), "Should parse as filter");
println!("✓ field_2 && field_3 parsed successfully");
}
Err(e) => panic!("field_2 && field_3 should work: {e}"),
}
match parse_command("undefined_field && field_2") {
Ok(result) => {
if result.filter.is_some() {
panic!("undefined_field && field_2 should not parse as filter");
}
}
Err(_) => {
println!("✓ undefined_field && field_2 correctly rejected");
}
}
match parse_command("name") {
Ok(result) => {
assert!(result.field_selector.is_some(), "Should be field selector");
assert!(result.filter.is_none(), "Should not be filter");
println!("✓ bare 'name' correctly parsed as field selector");
}
Err(e) => panic!("bare 'name' should work: {e}"),
}
match parse_command("random_field") {
Ok(result) => {
assert!(result.field_selector.is_some(), "Should be field selector");
assert!(result.filter.is_none(), "Should not be filter");
println!("✓ 'random_field' correctly parsed as field selector");
}
Err(e) => panic!("'random_field' should work as field selector: {e}"),
}
}
#[test]
fn test_debug_csv_pattern() {
let pattern = r#"field_1 > "25" {{"name": "${field_0}", "age": "${field_1}", "role": "${field_2}", "senior": true}}"#;
println!("Testing pattern: {pattern}");
match parse_command(pattern) {
Ok(result) => {
println!("✓ Parsed successfully:");
println!(" Filter: {:?}", result.filter.is_some());
println!(" Template: {:?}", result.template.is_some());
println!(" Field selector: {:?}", result.field_selector.is_some());
if let Some(filter) = &result.filter {
println!(" Filter details: {filter:?}");
}
if let Some(template) = &result.template {
println!(" Template items: {}", template.items.len());
}
}
Err(e) => {
println!("✗ Failed to parse: {e}");
}
}
}
#[test]
fn test_manual_split_logic() {
let complex_pattern = r#"field_1 > "25" {{"name": "${field_0}", "age": "${field_1}", "role": "${field_2}", "senior": true}}"#;
if let Some((filter_part, template_part)) = split_filter_template_manually(complex_pattern)
{
println!("✓ Manual split successful:");
println!(" Filter part: '{filter_part}'");
println!(" Template part: '{template_part}'");
println!("\nTesting filter part...");
match parse_command(filter_part) {
Ok(result) => {
println!(
" ✓ Filter part parsed: filter={:?}",
result.filter.is_some()
);
}
Err(e) => {
println!(" ✗ Filter part failed: {e}");
}
}
println!("\nTesting template part...");
match parse_command(template_part) {
Ok(result) => {
println!(
" ✓ Template part parsed: template={:?}",
result.template.is_some()
);
}
Err(e) => {
println!(" ✗ Template part failed: {e}");
}
}
} else {
println!("✗ Manual split failed");
}
}
#[test]
fn test_array_element_selection() {
use crate::filter::FieldPath;
use serde_json::json;
let data = json!({
"users": [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25}
],
"items": ["apple", "banana", "cherry"]
});
let field_path = FieldPath::new(vec![
"users".to_string(),
"0".to_string(),
"name".to_string(),
]);
let result = field_path.get_value(&data);
assert_eq!(result, Some(&json!("Alice")));
let field_path = FieldPath::new(vec![
"users".to_string(),
"1".to_string(),
"name".to_string(),
]);
let result = field_path.get_value(&data);
assert_eq!(result, Some(&json!("Bob")));
let field_path = FieldPath::new(vec!["items".to_string(), "0".to_string()]);
let result = field_path.get_value(&data);
assert_eq!(result, Some(&json!("apple")));
let field_path = FieldPath::new(vec!["items".to_string(), "2".to_string()]);
let result = field_path.get_value(&data);
assert_eq!(result, Some(&json!("cherry")));
let field_path = FieldPath::new(vec![
"users".to_string(),
"5".to_string(),
"name".to_string(),
]);
let result = field_path.get_value(&data);
assert_eq!(result, None);
let simple_data = json!({"name": "Alice"});
let field_path = FieldPath::new(vec!["name".to_string(), "0".to_string()]);
let result = field_path.get_value(&simple_data);
assert_eq!(result, None);
let result = parse_command("users.0.name").unwrap();
assert!(result.field_selector.is_some());
assert!(result.filter.is_none());
assert!(result.template.is_none());
if let Some(field_selector) = result.field_selector {
assert_eq!(field_selector.parts, vec!["users", "0", "name"]);
}
}
}