pub struct FilterBuilder {
filters: Vec<String>,
}
impl FilterBuilder {
pub fn new() -> Self {
Self {
filters: Vec::new(),
}
}
pub fn add_eq(mut self, field: &str, value: &str) -> Self {
let escaped = Self::escape_value(value);
self.filters.push(format!("{}=\"{}\"", field, escaped));
self
}
pub fn add_gte(mut self, field: &str, value: &str) -> Self {
let escaped = Self::escape_value(value);
self.filters.push(format!("{}>=\"{}\"", field, escaped));
self
}
pub fn add_lt(mut self, field: &str, value: &str) -> Self {
let escaped = Self::escape_value(value);
self.filters.push(format!("{}<\"{}\"", field, escaped));
self
}
pub fn finish(self) -> String {
match self.filters.len() {
0 => String::new(),
1 => self.filters.into_iter().next().unwrap(),
_ => format!("({})", self.filters.join(" AND ")),
}
}
fn escape_value(value: &str) -> String {
value.replace('\\', "\\\\").replace('"', "\\\"")
}
}
impl Default for FilterBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_filter_builder_single_eq() {
let filter = FilterBuilder::new().add_eq("status", "open").finish();
assert_eq!(filter, "status=\"open\"");
}
#[test]
fn test_filter_builder_multiple_filters() {
let filter = FilterBuilder::new()
.add_eq("status", "open")
.add_gte("created_on", "2024-01-01")
.finish();
assert_eq!(filter, "(status=\"open\" AND created_on>=\"2024-01-01\")");
}
#[test]
fn test_filter_builder_all_operators() {
let filter = FilterBuilder::new()
.add_eq("field1", "value1")
.add_gte("field4", "value4")
.add_lt("field5", "value5")
.finish();
assert!(filter.contains("field1=\"value1\""));
assert!(filter.contains("field4>=\"value4\""));
assert!(filter.contains("field5<\"value5\""));
assert!(filter.starts_with('('));
assert!(filter.ends_with(')'));
}
#[test]
fn test_filter_builder_empty() {
let filter = FilterBuilder::new().finish();
assert_eq!(filter, "");
}
#[test]
fn test_filter_builder_quote_escaping() {
let filter = FilterBuilder::new()
.add_eq("field", "value with \"quotes\"")
.finish();
assert!(filter.contains("\\\"quotes\\\""));
assert!(!filter.contains("\"quotes\""));
}
#[test]
fn test_filter_builder_backslash_escaping() {
let filter = FilterBuilder::new()
.add_eq("field", "path\\with\\backslashes")
.finish();
assert!(filter.contains("\\\\"));
}
#[test]
fn test_filter_injection_attempt() {
let malicious = "value\" OR admin=\"true";
let filter = FilterBuilder::new().add_eq("user", malicious).finish();
assert_eq!(filter, "user=\"value\\\" OR admin=\\\"true\"");
assert!(filter.contains("\\\""), "Quotes should be escaped");
assert!(filter.starts_with("user=\""));
assert!(filter.ends_with("\""));
let escaped_quotes = filter.matches("\\\"").count();
assert_eq!(
escaped_quotes, 2,
"Should have 2 escaped quotes from the malicious input"
);
}
#[test]
fn test_filter_builder_gte() {
let filter = FilterBuilder::new()
.add_gte("created_on", "2024-01-01T00:00:00")
.finish();
assert_eq!(filter, "created_on>=\"2024-01-01T00:00:00\"");
}
#[test]
fn test_filter_builder_lt() {
let filter = FilterBuilder::new()
.add_lt("updated_at", "2024-12-31T23:59:59")
.finish();
assert_eq!(filter, "updated_at<\"2024-12-31T23:59:59\"");
}
}