use crate::hyperql::{
CompilerError, CompilerResult, FilterCondition, FilterOperator, FilterValue, LiteralValue,
};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
pub struct FilterCompiler;
impl FilterCompiler {
pub fn new() -> Self {
Self
}
pub fn compile(&self, filters: &[FilterCondition]) -> CompilerResult<TokenStream> {
if filters.is_empty() {
return Ok(quote! { true });
}
let conditions: Result<Vec<_>, _> =
filters.iter().map(|f| self.compile_condition(f)).collect();
let conditions = conditions?;
if conditions.len() == 1 {
Ok(conditions[0].clone())
} else {
Ok(quote! {
#(#conditions)&&*
})
}
}
pub fn compile_condition(&self, condition: &FilterCondition) -> CompilerResult<TokenStream> {
let field_ident = format_ident!("{}", condition.field);
let value_tokens = self.compile_value(&condition.value)?;
let comparison = match condition.operator {
FilterOperator::Equal => quote! { entity.#field_ident == #value_tokens },
FilterOperator::NotEqual => quote! { entity.#field_ident != #value_tokens },
FilterOperator::LessThan => quote! { entity.#field_ident < #value_tokens },
FilterOperator::LessThanOrEqual => quote! { entity.#field_ident <= #value_tokens },
FilterOperator::GreaterThan => quote! { entity.#field_ident > #value_tokens },
FilterOperator::GreaterThanOrEqual => quote! { entity.#field_ident >= #value_tokens },
FilterOperator::Like => {
quote! { entity.#field_ident.contains(#value_tokens) }
}
FilterOperator::In => {
return Err(CompilerError::UnsupportedPattern(
"IN operator not yet supported in filters".to_string(),
));
}
};
Ok(comparison)
}
fn compile_value(&self, value: &FilterValue) -> CompilerResult<TokenStream> {
match value {
FilterValue::Parameter(param_name) => {
let param_ident = format_ident!("{}", param_name);
Ok(quote! { #param_ident })
}
FilterValue::Literal(lit) => {
let lit_tokens = self.compile_literal(lit);
Ok(lit_tokens)
}
}
}
fn compile_literal(&self, literal: &LiteralValue) -> TokenStream {
match literal {
LiteralValue::Null => quote! { None },
LiteralValue::Boolean(b) => quote! { #b },
LiteralValue::Integer(i) => quote! { #i },
LiteralValue::Float(f) => quote! { #f },
LiteralValue::String(s) => quote! { #s },
}
}
pub fn compile_logical_combination(
&self,
conditions: &[FilterCondition],
) -> CompilerResult<TokenStream> {
self.compile(conditions)
}
}
impl Default for FilterCompiler {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_filter_compiler_creation() {
let compiler = FilterCompiler::new();
assert!(true); }
#[test]
fn test_compile_single_equal_filter() {
let compiler = FilterCompiler::new();
let filter = FilterCondition {
field: "age".to_string(),
operator: FilterOperator::Equal,
value: FilterValue::Parameter("min_age".to_string()),
};
let result = compiler.compile_condition(&filter);
assert!(
result.is_ok(),
"Failed to compile equal filter: {:?}",
result
);
let code = result.unwrap();
let code_str = code.to_string();
assert!(code_str.contains("entity"));
assert!(code_str.contains("age"));
assert!(code_str.contains("=="));
assert!(code_str.contains("min_age"));
}
#[test]
fn test_compile_greater_than_filter() {
let compiler = FilterCompiler::new();
let filter = FilterCondition {
field: "age".to_string(),
operator: FilterOperator::GreaterThan,
value: FilterValue::Literal(LiteralValue::Integer(25)),
};
let result = compiler.compile_condition(&filter);
assert!(result.is_ok());
let code = result.unwrap();
let code_str = code.to_string();
assert!(code_str.contains("entity"));
assert!(code_str.contains("age"));
assert!(code_str.contains(">"));
assert!(code_str.contains("25"));
}
#[test]
fn test_compile_multiple_filters() {
let compiler = FilterCompiler::new();
let filters = vec![
FilterCondition {
field: "age".to_string(),
operator: FilterOperator::GreaterThan,
value: FilterValue::Parameter("min_age".to_string()),
},
FilterCondition {
field: "active".to_string(),
operator: FilterOperator::Equal,
value: FilterValue::Literal(LiteralValue::Boolean(true)),
},
];
let result = compiler.compile(&filters);
assert!(
result.is_ok(),
"Failed to compile multiple filters: {:?}",
result
);
let code = result.unwrap();
let code_str = code.to_string();
assert!(code_str.contains("entity . age"));
assert!(code_str.contains("entity . active"));
assert!(code_str.contains("&&"));
}
#[test]
fn test_compile_all_comparison_operators() {
let compiler = FilterCompiler::new();
let operators = vec![
(FilterOperator::Equal, "=="),
(FilterOperator::NotEqual, "!="),
(FilterOperator::LessThan, "<"),
(FilterOperator::LessThanOrEqual, "<="),
(FilterOperator::GreaterThan, ">"),
(FilterOperator::GreaterThanOrEqual, ">="),
];
for (op, expected_token) in operators {
let filter = FilterCondition {
field: "value".to_string(),
operator: op,
value: FilterValue::Parameter("param".to_string()),
};
let result = compiler.compile_condition(&filter);
assert!(result.is_ok(), "Failed to compile operator {:?}", op);
let code = result.unwrap();
let code_str = code.to_string();
assert!(
code_str.contains(expected_token),
"Expected '{}' in generated code for {:?}",
expected_token,
op
);
}
}
#[test]
fn test_compile_literal_values() {
let compiler = FilterCompiler::new();
let literals = vec![
(LiteralValue::Boolean(true), "true"),
(LiteralValue::Integer(42), "42"),
(LiteralValue::Float(3.14), "3.14"),
(LiteralValue::String("test".to_string()), "\"test\""),
];
for (lit, expected) in literals {
let filter = FilterCondition {
field: "field".to_string(),
operator: FilterOperator::Equal,
value: FilterValue::Literal(lit),
};
let result = compiler.compile_condition(&filter);
assert!(result.is_ok());
let code = result.unwrap();
let code_str = code.to_string();
assert!(
code_str.contains(expected),
"Expected '{}' in generated code",
expected
);
}
}
#[test]
fn test_compile_empty_filters() {
let compiler = FilterCompiler::new();
let filters = vec![];
let result = compiler.compile(&filters);
assert!(result.is_ok());
let code = result.unwrap();
let code_str = code.to_string();
assert_eq!(code_str, "true");
}
#[test]
fn test_compile_like_operator() {
let compiler = FilterCompiler::new();
let filter = FilterCondition {
field: "name".to_string(),
operator: FilterOperator::Like,
value: FilterValue::Literal(LiteralValue::String("John".to_string())),
};
let result = compiler.compile_condition(&filter);
assert!(result.is_ok());
let code = result.unwrap();
let code_str = code.to_string();
assert!(code_str.contains("entity . name"));
assert!(code_str.contains("contains"));
assert!(code_str.contains("\"John\""));
}
#[test]
fn test_compile_in_operator_unsupported() {
let compiler = FilterCompiler::new();
let filter = FilterCondition {
field: "id".to_string(),
operator: FilterOperator::In,
value: FilterValue::Parameter("ids".to_string()),
};
let result = compiler.compile_condition(&filter);
assert!(result.is_err());
if let Err(CompilerError::UnsupportedPattern(msg)) = result {
assert!(msg.contains("IN operator"));
} else {
panic!("Expected UnsupportedPattern error for IN operator");
}
}
}