use crate::{FieldRef, ParserLimits, RqsError, RqsResult, RqsValue, value::parse_value};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum FilterOp {
Eq,
Ne,
Gt,
Gte,
Lt,
Lte,
Exists,
NotExists,
In,
NotIn,
Regex,
}
impl FilterOp {
#[must_use]
pub fn token(self) -> &'static str {
match self {
Self::Eq => "=",
Self::Ne => "!=",
Self::Gt => ">",
Self::Gte => ">=",
Self::Lt => "<",
Self::Lte => "<=",
Self::Exists => "exists",
Self::NotExists => "not_exists",
Self::In => "in",
Self::NotIn => "not_in",
Self::Regex => "regex",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RegexLiteral {
pattern: String,
flags: String,
}
impl RegexLiteral {
#[must_use]
pub fn pattern(&self) -> &str {
&self.pattern
}
#[must_use]
pub fn flags(&self) -> &str {
&self.flags
}
#[cfg(test)]
pub(crate) fn new_for_test(pattern: &str, flags: &str) -> Self {
Self {
pattern: pattern.to_owned(),
flags: flags.to_owned(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Filter {
field: FieldRef,
op: FilterOp,
value: Option<RqsValue>,
regex: Option<RegexLiteral>,
}
impl Filter {
#[must_use]
pub(crate) fn new(field: FieldRef, op: FilterOp, value: Option<RqsValue>) -> Self {
Self {
field,
op,
value,
regex: None,
}
}
#[must_use]
pub(crate) fn regex(field: FieldRef, regex: RegexLiteral) -> Self {
Self {
field,
op: FilterOp::Regex,
value: None,
regex: Some(regex),
}
}
#[must_use]
pub fn field(&self) -> &FieldRef {
&self.field
}
#[must_use]
pub fn op(&self) -> FilterOp {
self.op
}
#[must_use]
pub fn value(&self) -> Option<&RqsValue> {
self.value.as_ref()
}
#[must_use]
pub fn regex_literal(&self) -> Option<&RegexLiteral> {
self.regex.as_ref()
}
}
pub(crate) fn build_value_filter(
field: FieldRef,
op: FilterOp,
raw_value: &str,
limits: ParserLimits,
) -> RqsResult<Filter> {
if raw_value.is_empty() {
return Err(RqsError::MissingValue {
field: field.public_name().to_owned(),
});
}
if let Some(regex) = parse_regex_literal(raw_value) {
if !field.regex_allowed() {
return Err(RqsError::RegexDisabled {
field: field.public_name().to_owned(),
});
}
return Ok(Filter::regex(field, regex));
}
let value = parse_value(field.public_name(), raw_value, field.value_kind(), limits)?;
let op = list_operator(op, &value);
Ok(Filter::new(field, op, Some(value)))
}
fn list_operator(op: FilterOp, value: &RqsValue) -> FilterOp {
match (op, value) {
(FilterOp::Eq, RqsValue::List(_)) => FilterOp::In,
(FilterOp::Ne, RqsValue::List(_)) => FilterOp::NotIn,
_ => op,
}
}
fn parse_regex_literal(raw: &str) -> Option<RegexLiteral> {
if !raw.starts_with('/') {
return None;
}
let offset = raw[1..].rfind('/')?;
let end = offset + 1;
let pattern = raw[1..end].to_owned();
let flags = raw[end + 1..]
.chars()
.filter(|flag| matches!(flag, 'i' | 'm' | 's' | 'x'))
.collect();
Some(RegexLiteral { pattern, flags })
}