use regex::Regex;
use std::{any::Any, collections::HashMap, fmt::Display, sync::OnceLock};
use crate::{
collection::Document,
errors::{ErrorKind, NitriteError, NitriteResult},
index::{
text::{Tokenizer, TokenizerProvider},
IndexMap,
},
DefaultFilter, StringTokenizer, Value,
};
use super::{is_element_match_filter, is_text_filter, Filter, FilterProvider};
pub(crate) struct RegexFilter {
field_name: OnceLock<String>,
field_value: OnceLock<String>,
pattern: OnceLock<Regex>,
collection_name: OnceLock<String>,
}
impl RegexFilter {
#[inline]
pub(crate) fn new(field_name: String, field_value: String) -> Self {
let name = OnceLock::new();
let _ = name.set(field_name);
let value = OnceLock::new();
let _ = value.set(field_value.clone());
let pattern = OnceLock::new();
match Regex::new(&field_value) {
Ok(regex) => {
let _ = pattern.set(regex);
}
Err(e) => {
log::error!("Invalid regex pattern '{}': {}", field_value, e);
}
}
RegexFilter {
field_name: name,
field_value: value,
pattern,
collection_name: OnceLock::new(),
}
}
}
impl Display for RegexFilter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match (self.field_name.get(), self.field_value.get()) {
(Some(name), Some(value)) => write!(f, "({} =~ {})", name, value),
(Some(name), None) => write!(f, "({} =~ unknown)", name),
(None, Some(value)) => write!(f, "(unknown =~ {})", value),
(None, None) => write!(f, "(unknown =~ unknown)"),
}
}
}
impl FilterProvider for RegexFilter {
#[inline]
fn apply(&self, entry: &Document) -> NitriteResult<bool> {
let field_name = self.field_name.get()
.ok_or_else(|| NitriteError::new("Field name not initialized", ErrorKind::InvalidFieldName))?;
let value = entry.get(field_name)?;
let value = value.as_string();
if value.is_none() {
return Ok(false);
}
match self.pattern.get() {
Some(p) => {
let val = value.as_ref().ok_or_else(|| NitriteError::new(
"Field value is null or not a string",
ErrorKind::InvalidOperation,
))?;
Ok(p.is_match(val))
}
None => {
log::error!("Invalid regex pattern for filter {}", self);
Err(NitriteError::new(
"Invalid regex pattern",
ErrorKind::InvalidOperation,
))
},
}
}
fn get_collection_name(&self) -> NitriteResult<String> {
self.collection_name.get()
.cloned()
.ok_or_else(|| {
log::error!("Collection name is not set for filter");
NitriteError::new(
"Collection name is not set",
ErrorKind::InvalidOperation,
)
})
}
fn set_collection_name(&self, collection_name: String) -> NitriteResult<()> {
self.collection_name.get_or_init(|| collection_name);
Ok(())
}
fn has_field(&self) -> bool {
true
}
fn get_field_name(&self) -> NitriteResult<String> {
self.field_name.get()
.cloned()
.ok_or_else(|| NitriteError::new("Field name not initialized", ErrorKind::InvalidFieldName))
}
fn set_field_name(&self, field_name: String) -> NitriteResult<()> {
self.field_name.get_or_init(|| field_name);
Ok(())
}
fn get_field_value(&self) -> NitriteResult<Option<Value>> {
Ok(self.field_value.get()
.map(|v| Value::String(v.clone())))
}
fn set_field_value(&self, field_value: Value) -> NitriteResult<()> {
if let Value::String(string_value) = field_value {
self.field_value.get_or_init(|| string_value.clone());
match Regex::new(&string_value) {
Ok(regex) => {
self.pattern.get_or_init(|| regex);
}
Err(e) => {
log::error!("Invalid regex pattern '{}': {}", string_value, e);
return Err(NitriteError::new(
&format!("Invalid regex pattern: {}", e),
ErrorKind::InvalidOperation,
));
}
}
Ok(())
} else {
log::error!("Field value is not a string for filter {}", self);
Err(NitriteError::new(
"Field value is not a string",
ErrorKind::InvalidOperation,
))
}
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[derive(Clone)]
pub(crate) struct TextFilter {
field_name: OnceLock<String>,
field_value: OnceLock<String>,
collection_name: OnceLock<String>,
case_sensitive: OnceLock<bool>,
tokenizer: OnceLock<Tokenizer>,
}
impl TextFilter {
#[inline]
pub(crate) fn new(field_name: String, field_value: String, case_sensitive: bool) -> Self {
let name = OnceLock::new();
let _ = name.set(field_name);
let value = OnceLock::new();
let _ = value.set(field_value.clone());
let case = OnceLock::new();
let _ = case.set(case_sensitive);
TextFilter {
field_name: name,
field_value: value,
collection_name: OnceLock::new(),
case_sensitive: case,
tokenizer: OnceLock::new(),
}
}
pub(crate) fn set_tokenizer(&self, tokenizer: Tokenizer) {
self.tokenizer.get_or_init(|| tokenizer);
}
fn search_exact_by_index(
&self,
index_map: &IndexMap,
search_string: String,
) -> NitriteResult<Vec<Value>> {
let mut score_map = HashMap::new();
let tokenizer = self.tokenizer.get()
.ok_or_else(|| {
log::error!("Tokenizer not initialized for text filter");
NitriteError::new("Tokenizer not initialized", ErrorKind::InvalidOperation)
})?;
let words = tokenizer.tokenize(&search_string);
for word in words {
let value = index_map.get(&Value::String(word.clone()))?;
if let Some(val) = value {
if let Some(nitrite_ids) = val.as_array() {
for nitrite_id in nitrite_ids {
let count = score_map.entry(nitrite_id.clone()).or_insert(0);
*count += 1;
}
}
}
let case_sensitive = self.case_sensitive.get()
.ok_or_else(|| {
log::error!("Case sensitive flag not initialized for text filter");
NitriteError::new("Case sensitive flag not initialized", ErrorKind::InvalidOperation)
})?;
if !*case_sensitive {
let search_string = format!("i_{}", word.to_lowercase());
let value = index_map.get(&Value::String(search_string))?;
if let Some(val) = value {
if let Some(nitrite_ids) = val.as_array() {
for nitrite_id in nitrite_ids {
let count = score_map.entry(nitrite_id.clone()).or_insert(0);
*count += 1;
}
}
}
}
}
self.sorted_ids_by_score(score_map)
}
fn sorted_ids_by_score(&self, score_map: HashMap<Value, i32>) -> NitriteResult<Vec<Value>> {
let mut sorted_map: Vec<_> = score_map.into_iter().collect();
sorted_map.sort_by_key(|entry| std::cmp::Reverse(entry.1));
let mut sorted_ids = Vec::new();
for (key, _) in sorted_map {
sorted_ids.push(key);
}
Ok(sorted_ids)
}
fn search_by_wildcard(
&self,
index_map: &IndexMap,
search_string: String,
) -> NitriteResult<Vec<Value>> {
if search_string.eq("*") {
let field_name = self.field_name.get().map(|s| s.as_str()).unwrap_or("unknown");
log::error!("'*' alone is not a valid search string for wildcard filter on field '{}'", field_name);
return Err(NitriteError::new(
&format!("Invalid wildcard search pattern '*' on field '{}'. Use '*text' (ends with), 'text*' (starts with), or '*text*' (contains)", field_name),
ErrorKind::FilterError,
));
}
let string_tokenizer = StringTokenizer::new(DefaultFilter, &search_string);
let tokens = string_tokenizer.collect::<Vec<_>>();
if tokens.len() > 1 {
let field_name = self.field_name.get().map(|s| s.as_str()).unwrap_or("unknown");
log::error!("Wildcard search with multiple words '{}' cannot be applied on field '{}' - use phrase search instead", search_string, field_name);
return Err(NitriteError::new(
&format!("Wildcard search on field '{}' failed: '{}' contains multiple words. Use phrase search or split into multiple filters", field_name, search_string),
ErrorKind::FilterError,
));
}
if search_string.starts_with("*") && !search_string.ends_with("*") {
self.search_end_with(index_map, search_string)
} else if !search_string.starts_with("*") && search_string.ends_with("*") {
self.search_start_with(index_map, search_string)
} else if search_string.starts_with("*") && search_string.ends_with("*") {
self.search_contains(index_map, search_string)
} else {
self.search_exact_by_index(index_map, search_string)
}
}
fn search_start_with(
&self,
index_map: &IndexMap,
search_string: String,
) -> NitriteResult<Vec<Value>> {
let mut nitrite_ids = Vec::new();
let search_term = search_string.trim_end_matches('*');
let entries = index_map.entries()?;
for result in entries {
let (key, value) = result?;
if let Value::String(key_str) = &key {
if key_str.starts_with(search_term) {
if let Value::Array(array) = value {
nitrite_ids.extend(array);
}
}
}
}
Ok(nitrite_ids)
}
fn search_end_with(
&self,
index_map: &IndexMap,
search_string: String,
) -> NitriteResult<Vec<Value>> {
let mut nitrite_ids = Vec::new();
let search_term = search_string.trim_start_matches('*');
let entries = index_map.entries()?;
for result in entries {
let (key, value) = result?;
if let Value::String(key_str) = &key {
if key_str.ends_with(search_term) {
if let Value::Array(array) = value {
nitrite_ids.extend(array);
}
}
}
}
Ok(nitrite_ids)
}
fn search_contains(
&self,
index_map: &IndexMap,
search_string: String,
) -> NitriteResult<Vec<Value>> {
let mut nitrite_ids = Vec::new();
let search_term = search_string.trim_start_matches('*').trim_end_matches('*');
let entries = index_map.entries()?;
for result in entries {
let (key, value) = result?;
if let Value::String(key_str) = &key {
if key_str.contains(search_term) {
if let Value::Array(array) = value {
nitrite_ids.extend(array);
}
}
}
}
Ok(nitrite_ids)
}
}
impl Display for TextFilter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let case_sensitive = self.case_sensitive.get().unwrap_or(&false);
let field_name = self.field_name.get().map(|s| s.as_str()).unwrap_or("unknown");
let field_value = self.field_value.get().map(|s| s.as_str()).unwrap_or("unknown");
if *case_sensitive {
write!(
f,
"({} text {} case_sensitive)",
field_name, field_value
)
} else {
write!(
f,
"({} text_case_insensitive {})",
field_name, field_value
)
}
}
}
impl FilterProvider for TextFilter {
#[inline]
fn apply(&self, entry: &Document) -> NitriteResult<bool> {
let field_name = self.field_name.get()
.ok_or_else(|| NitriteError::new("Field name not initialized", ErrorKind::InvalidFieldName))?;
let value = entry.get(field_name)?;
let value = value.as_string();
if value.is_none() {
return Ok(false);
}
let field_value = self.field_value.get()
.ok_or_else(|| NitriteError::new("Field value not initialized", ErrorKind::InvalidOperation))?;
let case_sensitive = *self.case_sensitive.get()
.ok_or_else(|| NitriteError::new("Case sensitive flag not initialized", ErrorKind::InvalidOperation))?;
if case_sensitive {
Ok(value.as_ref().ok_or_else(|| NitriteError::new(
"Field value is null or not a string",
ErrorKind::InvalidOperation,
))?.contains(field_value))
} else {
Ok(value.as_ref().ok_or_else(|| NitriteError::new(
"Field value is null or not a string",
ErrorKind::InvalidOperation,
))?.to_lowercase().contains(&field_value.to_lowercase()))
}
}
fn apply_on_index(&self, index_map: &IndexMap) -> NitriteResult<Vec<Value>> {
let search_string = self.field_value.get()
.ok_or_else(|| NitriteError::new("Field value not initialized", ErrorKind::InvalidOperation))?
.clone();
self.search_by_wildcard(index_map, search_string)
}
fn get_collection_name(&self) -> NitriteResult<String> {
self.collection_name.get()
.cloned()
.ok_or_else(|| {
log::error!("Collection name is not set for filter");
NitriteError::new(
"Collection name is not set",
ErrorKind::InvalidOperation,
)
})
}
fn set_collection_name(&self, collection_name: String) -> NitriteResult<()> {
self.collection_name.get_or_init(|| collection_name);
Ok(())
}
fn has_field(&self) -> bool {
true
}
fn get_field_name(&self) -> NitriteResult<String> {
self.field_name.get()
.cloned()
.ok_or_else(|| NitriteError::new("Field name not initialized", ErrorKind::InvalidFieldName))
}
fn set_field_name(&self, field_name: String) -> NitriteResult<()> {
self.field_name.get_or_init(|| field_name);
Ok(())
}
fn get_field_value(&self) -> NitriteResult<Option<Value>> {
Ok(self.field_value.get()
.map(|v| Value::String(v.clone())))
}
fn set_field_value(&self, field_value: Value) -> NitriteResult<()> {
if let Value::String(string_value) = field_value {
self.field_value.get_or_init(|| string_value);
Ok(())
} else {
log::error!("Field value is not a string for filter {}", self);
Err(NitriteError::new(
"Field value is not a string",
ErrorKind::InvalidOperation,
))
}
}
fn as_any(&self) -> &dyn Any {
self
}
}
pub(crate) struct ElementMatchFilter {
field_name: OnceLock<String>,
filter: Filter,
collection_name: OnceLock<String>,
}
impl ElementMatchFilter {
#[inline]
pub(crate) fn new(field_name: String, filter: Filter) -> Self {
let name = OnceLock::new();
let _ = name.set(field_name);
ElementMatchFilter {
field_name: name,
filter,
collection_name: OnceLock::new(),
}
}
fn has_element_match_filter(&self) -> bool {
if is_element_match_filter(&self.filter) {
return true;
}
false
}
fn has_text_filter(&self) -> bool {
if is_text_filter(&self.filter) {
return true;
}
false
}
fn matches(&self, value: Vec<Value>) -> NitriteResult<bool> {
for v in value {
if self.match_element(&v)? {
return Ok(true);
}
}
Ok(false)
}
fn match_element(&self, value: &Value) -> NitriteResult<bool> {
match value {
Value::Document(doc) => self.filter.apply(doc),
_ => {
let mut doc = Document::new();
doc.put("$", value.clone())?;
self.filter.apply(&doc)
}
}
}
}
impl Display for ElementMatchFilter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(elemMatch {})", self.filter)
}
}
impl FilterProvider for ElementMatchFilter {
#[inline]
fn apply(&self, entry: &Document) -> NitriteResult<bool> {
if self.has_element_match_filter() {
log::error!("ElementMatchFilter {} cannot have another ElementMatchFilter {}", self, self.filter);
return Err(NitriteError::new(
"ElementMatchFilter cannot have another ElementMatchFilter",
ErrorKind::FilterError,
));
}
if self.has_text_filter() {
log::error!("ElementMatchFilter {} cannot have TextFilter {}", self, self.filter);
return Err(NitriteError::new(
"ElementMatchFilter cannot have TextFilter",
ErrorKind::FilterError,
));
}
let field_name = self.field_name.get()
.ok_or_else(|| NitriteError::new("Field name not initialized", ErrorKind::InvalidFieldName))?;
let value = entry.get(field_name)?;
if value.is_null() {
return Ok(false);
}
if let Value::Array(array) = value {
return self.matches(array);
}
log::error!(
"ElementMatchFilter can only be applied on array field, found {}",
value
);
Err(NitriteError::new(
"ElementMatchFilter can only be applied on array field",
ErrorKind::FilterError,
))
}
fn get_collection_name(&self) -> NitriteResult<String> {
self.collection_name.get()
.cloned()
.ok_or_else(|| {
log::error!("Collection name is not set for filter {}", self);
NitriteError::new(
"Collection name is not set",
ErrorKind::InvalidOperation,
)
})
}
fn set_collection_name(&self, collection_name: String) -> NitriteResult<()> {
self.collection_name.get_or_init(|| collection_name);
Ok(())
}
fn has_field(&self) -> bool {
true
}
fn get_field_name(&self) -> NitriteResult<String> {
self.field_name.get()
.cloned()
.ok_or_else(|| NitriteError::new("Field name not initialized", ErrorKind::InvalidFieldName))
}
fn set_field_name(&self, field_name: String) -> NitriteResult<()> {
self.field_name.get_or_init(|| field_name);
Ok(())
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::collection::Document;
use crate::filter::basic_filters::EqualsFilter;
#[test]
fn test_regex_filter_apply() {
let filter = RegexFilter::new("field".to_string(), "test.*".to_string());
let mut doc = Document::new();
doc.put("field", Value::String("test123".to_string()))
.unwrap();
assert!(filter.apply(&doc).unwrap());
}
#[test]
fn test_regex_filter_apply_negative() {
let filter = RegexFilter::new("field".to_string(), "test.*".to_string());
let mut doc = Document::new();
doc.put("field", Value::String("no_match".to_string()))
.unwrap();
assert!(!filter.apply(&doc).unwrap());
}
#[test]
fn test_text_filter_apply() {
let filter = TextFilter::new("field".to_string(), "test".to_string(), false);
let mut doc = Document::new();
doc.put("field", Value::String("this is a test".to_string()))
.unwrap();
assert!(filter.apply(&doc).unwrap());
}
#[test]
fn test_text_filter_apply_negative() {
let filter = TextFilter::new("field".to_string(), "test".to_string(), false);
let mut doc = Document::new();
doc.put("field", Value::String("no match".to_string()))
.unwrap();
assert!(!filter.apply(&doc).unwrap());
}
#[test]
fn test_element_match_filter_apply() {
let inner_filter = EqualsFilter::new("inner_field".to_string(), Value::I32(42));
let filter =
ElementMatchFilter::new("field".to_string(), Filter::new(inner_filter));
let mut doc = Document::new();
let mut inner_doc = Document::new();
inner_doc.put("inner_field", Value::I32(42)).unwrap();
doc.put("field", Value::Array(vec![Value::Document(inner_doc)]))
.unwrap();
assert!(filter.apply(&doc).unwrap());
}
#[test]
fn test_element_match_filter_apply_negative() {
let inner_filter = EqualsFilter::new("inner_field".to_string(), Value::I32(42));
let filter =
ElementMatchFilter::new("field".to_string(), Filter::new(inner_filter));
let mut doc = Document::new();
let mut inner_doc = Document::new();
inner_doc.put("inner_field", Value::I32(43)).unwrap();
doc.put("field", Value::Array(vec![Value::Document(inner_doc)]))
.unwrap();
assert!(!filter.apply(&doc).unwrap());
}
#[test]
fn test_regex_filter_handles_invalid_pattern_in_constructor() {
let filter = RegexFilter::new("field".to_string(), "(?P<invalid>".to_string());
assert!(filter.field_name.get().is_some());
assert!(filter.field_value.get().is_some());
assert!(filter.pattern.get().is_none());
}
#[test]
fn test_regex_filter_valid_pattern_initializes() {
let filter = RegexFilter::new("field".to_string(), "test.*".to_string());
assert!(filter.pattern.get().is_some());
let mut doc = Document::new();
doc.put("field", Value::String("test123".to_string())).unwrap();
assert!(filter.apply(&doc).unwrap());
}
#[test]
fn test_regex_filter_set_invalid_field_value_returns_error() {
let filter = RegexFilter::new("field".to_string(), "valid.*".to_string());
let result = filter.set_field_value(Value::String("(?P<invalid>".to_string()));
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Invalid regex"));
}
#[test]
fn test_regex_filter_display_with_initialized_values() {
let filter = RegexFilter::new("email".to_string(), r"^[a-z]+@.*\.com$".to_string());
let display_str = format!("{}", filter);
assert!(display_str.contains("email"));
assert!(display_str.contains("=~"));
}
#[test]
fn test_regex_filter_apply_with_invalid_pattern_returns_error() {
let filter = RegexFilter::new("field".to_string(), "(?P<invalid>".to_string());
let mut doc = Document::new();
doc.put("field", Value::String("test".to_string())).unwrap();
let result = filter.apply(&doc);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Invalid regex"));
}
#[test]
fn test_regex_filter_get_field_name_error_handling() {
let filter = RegexFilter::new("test_field".to_string(), "test.*".to_string());
let field_name = filter.get_field_name().unwrap();
assert_eq!(field_name, "test_field");
}
#[test]
fn test_text_filter_basic_apply() {
let filter = TextFilter::new("content".to_string(), "hello".to_string(), false);
let mut doc = Document::new();
doc.put("content", Value::String("hello world example".to_string())).unwrap();
assert!(filter.apply(&doc).unwrap());
}
#[test]
fn test_text_filter_case_insensitive_apply() {
let filter = TextFilter::new("field".to_string(), "TEST".to_string(), false);
let mut doc = Document::new();
doc.put("field", Value::String("this is a test".to_string())).unwrap();
assert!(filter.apply(&doc).unwrap());
}
#[test]
fn test_text_filter_case_sensitive_apply() {
let filter = TextFilter::new("field".to_string(), "Test".to_string(), true);
let mut doc = Document::new();
doc.put("field", Value::String("this is a test".to_string())).unwrap();
assert!(!filter.apply(&doc).unwrap());
}
#[test]
fn test_text_filter_display_case_sensitive() {
let filter = TextFilter::new("body".to_string(), "search_term".to_string(), true);
let display_str = format!("{}", filter);
assert!(display_str.contains("case_sensitive"));
assert!(display_str.contains("body"));
}
#[test]
fn test_text_filter_display_case_insensitive() {
let filter = TextFilter::new("body".to_string(), "search_term".to_string(), false);
let display_str = format!("{}", filter);
assert!(display_str.contains("case_insensitive"));
assert!(display_str.contains("body"));
}
#[test]
fn test_text_filter_set_and_get_collection_name() {
let filter = TextFilter::new("field".to_string(), "value".to_string(), false);
filter.set_collection_name("my_collection".to_string()).unwrap();
let name = filter.get_collection_name().unwrap();
assert_eq!(name, "my_collection");
}
#[test]
fn test_text_filter_get_field_name() {
let filter = TextFilter::new("search_field".to_string(), "value".to_string(), false);
let field_name = filter.get_field_name().unwrap();
assert_eq!(field_name, "search_field");
}
#[test]
fn test_text_filter_set_invalid_field_value_type() {
let filter = TextFilter::new("field".to_string(), "value".to_string(), false);
let result = filter.set_field_value(Value::I32(42));
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not a string"));
}
#[test]
fn test_element_match_filter_safe_field_name_access() {
let inner_filter = EqualsFilter::new("inner".to_string(), Value::I32(1));
let filter = ElementMatchFilter::new("items".to_string(), Filter::new(inner_filter));
let result = filter.get_field_name();
assert!(result.is_ok());
let field_name = result.unwrap();
assert_eq!(field_name, "items");
}
#[test]
fn test_element_match_filter_safe_collection_name_access() {
let inner_filter = EqualsFilter::new("inner".to_string(), Value::I32(1));
let filter = ElementMatchFilter::new("field".to_string(), Filter::new(inner_filter));
filter.set_collection_name("test_collection".to_string()).unwrap();
let result = filter.get_collection_name();
assert!(result.is_ok());
let name = result.unwrap();
assert_eq!(name, "test_collection");
}
#[test]
fn test_element_match_filter_collection_name_not_set_error() {
let inner_filter = EqualsFilter::new("inner".to_string(), Value::I32(1));
let filter = ElementMatchFilter::new("field".to_string(), Filter::new(inner_filter));
let result = filter.get_collection_name();
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("Collection name is not set"));
}
#[test]
fn test_regex_filter_safe_string_extraction_in_apply() {
let filter = RegexFilter::new("email".to_string(), r"^[a-z]+@.*\.com$".to_string());
let mut doc = Document::new();
doc.put("email", Value::String("user@example.com".to_string())).unwrap();
let result = filter.apply(&doc);
assert!(result.is_ok());
assert!(result.unwrap());
}
#[test]
fn test_regex_filter_safe_handle_non_string_field() {
let filter = RegexFilter::new("count".to_string(), r"\d+".to_string());
let mut doc = Document::new();
doc.put("count", Value::I32(42)).unwrap();
let result = filter.apply(&doc);
assert!(result.is_ok());
assert!(!result.unwrap());
}
#[test]
fn test_text_filter_safe_string_unwrapping_case_sensitive() {
let filter = TextFilter::new("content".to_string(), "SEARCH".to_string(), true);
let mut doc = Document::new();
doc.put("content", Value::String("this is SEARCH term".to_string())).unwrap();
let result = filter.apply(&doc);
assert!(result.is_ok());
assert!(result.unwrap());
}
#[test]
fn test_text_filter_safe_string_unwrapping_case_insensitive() {
let filter = TextFilter::new("content".to_string(), "search".to_string(), false);
let mut doc = Document::new();
doc.put("content", Value::String("this is SEARCH term".to_string())).unwrap();
let result = filter.apply(&doc);
assert!(result.is_ok());
assert!(result.unwrap());
}
#[test]
fn test_element_match_filter_apply_with_empty_array() {
let inner_filter = EqualsFilter::new("value".to_string(), Value::I32(42));
let filter = ElementMatchFilter::new("items".to_string(), Filter::new(inner_filter));
let mut doc = Document::new();
doc.put("items", Value::Array(vec![])).unwrap();
let result = filter.apply(&doc);
assert!(result.is_ok());
assert!(!result.unwrap());
}
#[test]
fn test_regex_filter_complex_pattern_matches() {
let pattern = r"^[A-Z][a-z]*$";
let filter = RegexFilter::new("name".to_string(), pattern.to_string());
let mut doc = Document::new();
doc.put("name", Value::String("Alice".to_string())).unwrap();
let result = filter.apply(&doc);
assert!(result.is_ok());
assert!(result.unwrap());
}
#[test]
fn test_element_match_filter_with_multiple_elements() {
let inner_filter = EqualsFilter::new("status".to_string(), Value::String("active".to_string()));
let filter = ElementMatchFilter::new("users".to_string(), Filter::new(inner_filter));
let mut doc = Document::new();
let mut user1 = Document::new();
user1.put("status", Value::String("inactive".to_string())).unwrap();
let mut user2 = Document::new();
user2.put("status", Value::String("active".to_string())).unwrap();
doc.put("users", Value::Array(vec![
Value::Document(user1),
Value::Document(user2),
])).unwrap();
let result = filter.apply(&doc);
assert!(result.is_ok());
assert!(result.unwrap());
}
}