#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EqFilter {
pub attribute: String,
pub value: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FilterError(pub String);
pub fn parse_eq(filter: &str) -> Result<EqFilter, FilterError> {
let trimmed = filter.trim();
let lowered = trimmed.to_ascii_lowercase();
for connective in [" and ", " or ", " not "] {
if lowered.contains(connective) {
return Err(FilterError(format!(
"unsupported filter: only a single 'attribute eq \"value\"' comparison is \
supported, and this one uses '{}'",
connective.trim()
)));
}
}
let (attribute, rest) = trimmed.split_once(char::is_whitespace).ok_or_else(|| {
FilterError("unsupported filter: expected 'attribute eq \"value\"'".to_string())
})?;
let rest = rest.trim_start();
let (op, value) = rest.split_once(char::is_whitespace).ok_or_else(|| {
FilterError("unsupported filter: expected 'attribute eq \"value\"'".to_string())
})?;
if !op.eq_ignore_ascii_case("eq") {
return Err(FilterError(format!(
"unsupported filter operator '{op}': only 'eq' is supported"
)));
}
let value = value.trim();
let unquoted = value.strip_prefix('"').and_then(|v| v.strip_suffix('"')).ok_or_else(|| {
FilterError("unsupported filter: value must be a quoted string".to_string())
})?;
Ok(EqFilter {
attribute: attribute.trim().to_ascii_lowercase(),
value: unescape(unquoted),
})
}
fn unescape(raw: &str) -> String {
let mut out = String::with_capacity(raw.len());
let mut chars = raw.chars();
while let Some(c) = chars.next() {
if c == '\\' {
match chars.next() {
Some('"') => out.push('"'),
Some(other) => {
out.push('\\');
if other != '\\' {
out.push(other);
}
},
None => out.push('\\'),
}
} else {
out.push(c);
}
}
out
}
pub fn expect_attribute(filter: &EqFilter, supported: &str) -> Result<String, FilterError> {
if filter.attribute == supported.to_ascii_lowercase() {
return Ok(filter.value.clone());
}
Err(FilterError(format!(
"unsupported filter attribute '{}': only '{supported}' is filterable here",
filter.attribute
)))
}