pub(super) struct Tail {
pub(super) limit: usize,
pub(super) cursor_raw: Option<Vec<u8>>,
pub(super) fields: Vec<Vec<u8>>,
pub(super) highlight: Option<Vec<Vec<u8>>>,
pub(super) typo: u32,
pub(super) offset: usize,
pub(super) scope: Vec<Vec<u8>>,
pub(super) filters: Vec<FilterClause>,
pub(super) sort: Option<(Vec<u8>, bool)>,
pub(super) distinct: Option<Vec<u8>>,
pub(super) facets: Vec<Vec<u8>>,
}
pub(super) struct FilterClause {
pub(super) field: Vec<u8>,
pub(super) shape: FilterShape,
}
pub(super) enum FilterShape {
Range(Vec<u8>, Vec<u8>),
Eq(Vec<u8>),
}
impl FilterClause {
pub(super) fn as_value_filter(&self) -> crate::ValueFilter<'_> {
match &self.shape {
FilterShape::Range(min, max) => {
crate::ValueFilter::Range { field: &self.field, min, max }
}
FilterShape::Eq(v) => crate::ValueFilter::Eq { field: &self.field, value: v },
}
}
}
fn is_tail_keyword(a: &[u8]) -> bool {
a.eq_ignore_ascii_case(b"LIMIT")
|| a.eq_ignore_ascii_case(b"CURSOR")
|| a.eq_ignore_ascii_case(b"FIELDS")
|| a.eq_ignore_ascii_case(b"HIGHLIGHT")
|| a.eq_ignore_ascii_case(b"TYPO")
|| a.eq_ignore_ascii_case(b"OFFSET")
|| a.eq_ignore_ascii_case(b"IN")
|| a.eq_ignore_ascii_case(b"FILTER")
|| a.eq_ignore_ascii_case(b"SORT")
|| a.eq_ignore_ascii_case(b"DISTINCT")
|| a.eq_ignore_ascii_case(b"FACET")
}
fn apply_match_clause(argv: &[Vec<u8>], i: usize, t: &mut Tail) -> Option<usize> {
let a = &argv[i];
if a.eq_ignore_ascii_case(b"HIGHLIGHT") {
let (hs, next) = collect_until_keyword(argv, i + 1);
t.highlight = Some(hs);
Some(next)
} else if a.eq_ignore_ascii_case(b"TYPO") {
t.typo = match argv.get(i + 1)?.as_slice() {
b"0" => 0,
b"1" => 1,
b"2" => 2,
_ => return None,
};
Some(i + 2)
} else if a.eq_ignore_ascii_case(b"IN") {
let (fs, next) = collect_until_keyword(argv, i + 1);
if fs.is_empty() {
return None;
}
t.scope = fs;
Some(next)
} else {
None
}
}
fn apply_sort(argv: &[Vec<u8>], i: usize, t: &mut Tail) -> Option<usize> {
let field = argv.get(i + 1)?.clone();
let dir = argv.get(i + 2)?;
let desc = if dir.eq_ignore_ascii_case(b"DESC") {
true
} else if dir.eq_ignore_ascii_case(b"ASC") {
false
} else {
return None;
};
t.sort = Some((field, desc));
Some(i + 3)
}
fn apply_facet(argv: &[Vec<u8>], i: usize, t: &mut Tail) -> Option<usize> {
let (fs, next) = collect_until_keyword(argv, i + 1);
if fs.is_empty() {
return None;
}
t.facets = fs;
Some(next)
}
fn apply_distinct(argv: &[Vec<u8>], i: usize, t: &mut Tail) -> Option<usize> {
t.distinct = Some(argv.get(i + 1)?.clone());
Some(i + 2)
}
fn apply_filter(argv: &[Vec<u8>], i: usize, t: &mut Tail) -> Option<usize> {
let field = argv.get(i + 1)?.clone();
let mode = argv.get(i + 2)?;
if mode.eq_ignore_ascii_case(b"RANGE") {
let shape = FilterShape::Range(argv.get(i + 3)?.clone(), argv.get(i + 4)?.clone());
t.filters.push(FilterClause { field, shape });
Some(i + 5)
} else if mode.eq_ignore_ascii_case(b"EQ") {
t.filters.push(FilterClause { field, shape: FilterShape::Eq(argv.get(i + 3)?.clone()) });
Some(i + 4)
} else {
None
}
}
fn collect_until_keyword(argv: &[Vec<u8>], start: usize) -> (Vec<Vec<u8>>, usize) {
let mut i = start;
let mut out = Vec::new();
while i < argv.len() && !is_tail_keyword(&argv[i]) {
out.push(argv[i].clone());
i += 1;
}
(out, i)
}
#[derive(Clone, Copy, PartialEq)]
pub(super) enum TailMode {
Scalar,
Match,
}
pub(super) fn parse_tail(
argv: &[Vec<u8>],
mut i: usize,
default_limit: usize,
cap: usize,
mode: TailMode,
) -> Option<Tail> {
let mut t = Tail {
limit: default_limit,
cursor_raw: None,
fields: Vec::new(),
highlight: None,
typo: 0,
offset: 0,
scope: Vec::new(),
filters: Vec::new(),
sort: None,
distinct: None,
facets: Vec::new(),
};
while i < argv.len() {
i = apply_tail_clause(argv, i, &mut t, mode)?;
}
t.limit = t.limit.clamp(1, cap);
t.offset = t.offset.min(10_000);
Some(t)
}
fn apply_tail_clause(
argv: &[Vec<u8>],
i: usize,
t: &mut Tail,
mode: TailMode,
) -> Option<usize> {
let a = &argv[i];
if a.eq_ignore_ascii_case(b"LIMIT") {
t.limit = std::str::from_utf8(argv.get(i + 1)?).ok()?.parse().ok()?;
Some(i + 2)
} else if a.eq_ignore_ascii_case(b"CURSOR") {
t.cursor_raw = Some(argv.get(i + 1)?.clone());
Some(i + 2)
} else if a.eq_ignore_ascii_case(b"FIELDS") {
let (fs, next) = if mode == TailMode::Scalar {
(argv[i + 1..].to_vec(), argv.len())
} else {
collect_until_keyword(argv, i + 1)
};
if fs.is_empty() {
return None;
}
t.fields = fs;
Some(next)
} else if a.eq_ignore_ascii_case(b"OFFSET") {
t.offset = std::str::from_utf8(argv.get(i + 1)?).ok()?.parse().ok()?;
Some(i + 2)
} else if a.eq_ignore_ascii_case(b"FILTER") {
apply_filter(argv, i, t)
} else if a.eq_ignore_ascii_case(b"SORT") {
apply_sort(argv, i, t)
} else if a.eq_ignore_ascii_case(b"DISTINCT") {
apply_distinct(argv, i, t)
} else if a.eq_ignore_ascii_case(b"FACET") {
apply_facet(argv, i, t)
} else if mode == TailMode::Match {
apply_match_clause(argv, i, t)
} else {
None
}
}