add regex support to text search
This commit is contained in:
parent
2446aefba1
commit
59fffc1bf9
@ -81,6 +81,12 @@ fn filter_value(op: &Operator, field_value: &Value, query_value: &Value) -> bool
|
|||||||
// greater/less than aren’t supported for string fields.
|
// greater/less than aren’t supported for string fields.
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
|
(Value::String(field), Value::Regex(query)) => match op {
|
||||||
|
Operator::Equal => query.is_match(field),
|
||||||
|
Operator::NotEqual => !query.is_match(field),
|
||||||
|
// greater/less than aren’t supported for string fields.
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
// Currently only for sets the card was released in.
|
// Currently only for sets the card was released in.
|
||||||
(Value::Multiple(field), query @ Value::String(_)) => match op {
|
(Value::Multiple(field), query @ Value::String(_)) => match op {
|
||||||
Operator::Equal => field.iter().any(|f| f == query),
|
Operator::Equal => field.iter().any(|f| f == query),
|
||||||
@ -151,4 +157,13 @@ mod tests {
|
|||||||
let astral_pack_4_filter = parse_filters("set:ap04").unwrap().1;
|
let astral_pack_4_filter = parse_filters("set:ap04").unwrap().1;
|
||||||
assert!(!astral_pack_4_filter[0](&lacooda));
|
assert!(!astral_pack_4_filter[0](&lacooda));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn regex_filter_test() {
|
||||||
|
let lacooda = SearchCard::from(&serde_json::from_str::<Card>(RAW_MONSTER).unwrap());
|
||||||
|
let bls = SearchCard::from(&serde_json::from_str::<Card>(RAW_LINK_MONSTER).unwrap());
|
||||||
|
let draw_filter = parse_filters("o:/draw \\d cards?/").unwrap().1;
|
||||||
|
assert!(draw_filter[0](&lacooda));
|
||||||
|
assert!(!draw_filter[0](&bls));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#![feature(lazy_cell)]
|
#![feature(lazy_cell, try_blocks)]
|
||||||
use actix_web::{get, http::header, web, App, Either, HttpResponse, HttpServer};
|
use actix_web::{get, http::header, web, App, Either, HttpResponse, HttpServer};
|
||||||
use data::{Card, CardInfo, Set};
|
use data::{Card, CardInfo, Set};
|
||||||
use filter::SearchCard;
|
use filter::SearchCard;
|
||||||
|
@ -14,6 +14,7 @@ use nom::{
|
|||||||
sequence::{delimited, preceded, tuple},
|
sequence::{delimited, preceded, tuple},
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
pub fn parse_filters(input: &str) -> Result<(Vec<RawCardFilter>, Vec<CardFilter>), String> {
|
pub fn parse_filters(input: &str) -> Result<(Vec<RawCardFilter>, Vec<CardFilter>), String> {
|
||||||
parse_raw_filters(input).map_err(|e| format!("Error while parsing filters “{input}”: {e:?}")).and_then(|(rest, mut v)| {
|
parse_raw_filters(input).map_err(|e| format!("Error while parsing filters “{input}”: {e:?}")).and_then(|(rest, mut v)| {
|
||||||
@ -47,20 +48,19 @@ fn parse_raw_filters(input: &str) -> IResult<&str, Vec<RawCardFilter>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn word_non_empty(input: &str) -> IResult<&str, &str> {
|
fn word_non_empty(input: &str) -> IResult<&str, &str> {
|
||||||
verify(alt((take_until1(" "), rest)), |s: &str| s.len() >= 2)(input)
|
verify(alt((take_until1(" "), rest)), |s: &str| !s.is_empty())(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sanitize(query: &str) -> Result<String, String> {
|
fn sanitize(query: &str) -> Result<String, String> {
|
||||||
if query.contains(OPERATOR_CHARS) || query.is_empty() {
|
if query.is_empty() {
|
||||||
Err(format!("Invalid query: {query}"))
|
Err("Query must not be empty".to_owned())
|
||||||
} else {
|
} else {
|
||||||
Ok(query.to_lowercase())
|
Ok(query.to_lowercase())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fallback_filter(query: &str) -> Result<RawCardFilter, String> {
|
fn fallback_filter(query: &str) -> Result<RawCardFilter, String> {
|
||||||
let q = sanitize(query)?;
|
Ok(RawCardFilter(Field::Name, Operator::Equal, Value::String(sanitize(query)?)))
|
||||||
Ok(RawCardFilter(Field::Name, Operator::Equal, Value::String(q)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_raw_filter(input: &str) -> IResult<&str, RawCardFilter> {
|
fn parse_raw_filter(input: &str) -> IResult<&str, RawCardFilter> {
|
||||||
@ -87,6 +87,7 @@ fn values(input: &str) -> IResult<&str, Value> {
|
|||||||
map_res(
|
map_res(
|
||||||
alt((
|
alt((
|
||||||
delimited(char('"'), take_until1("\""), char('"')),
|
delimited(char('"'), take_until1("\""), char('"')),
|
||||||
|
recognize(delimited(char('/'), take_until1("/"), char('/'))),
|
||||||
recognize(separated_list1(char('|'), take_until1(" |"))),
|
recognize(separated_list1(char('|'), take_until1(" |"))),
|
||||||
take_until1(" "),
|
take_until1(" "),
|
||||||
rest,
|
rest,
|
||||||
@ -106,7 +107,12 @@ fn parse_values(input: &str) -> Result<Value, String> {
|
|||||||
fn parse_single_value(input: &str) -> Result<Value, String> {
|
fn parse_single_value(input: &str) -> Result<Value, String> {
|
||||||
Ok(match input.parse() {
|
Ok(match input.parse() {
|
||||||
Ok(n) => Value::Numerical(n),
|
Ok(n) => Value::Numerical(n),
|
||||||
Err(_) => Value::String(sanitize(input)?),
|
Err(_) => {
|
||||||
|
match try { Value::Regex(Regex::new(&input.strip_prefix('/')?.strip_suffix('/')?.to_lowercase()).ok()?) } {
|
||||||
|
Some(regex) => regex,
|
||||||
|
None => Value::String(sanitize(input)?),
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,15 +238,32 @@ impl Display for RawCardFilter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
String(String),
|
String(String),
|
||||||
|
Regex(Regex),
|
||||||
Numerical(i32),
|
Numerical(i32),
|
||||||
Multiple(Vec<Value>),
|
Multiple(Vec<Value>),
|
||||||
#[default]
|
#[default]
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Manually implementing this because `Regex` isn’t `PartialEq`
|
||||||
|
impl PartialEq for Value {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
match (self, other) {
|
||||||
|
(Value::String(s1), Value::String(s2)) => s1 == s2,
|
||||||
|
(Value::Numerical(a), Value::Numerical(b)) => a == b,
|
||||||
|
(Value::Multiple(v1), Value::Multiple(v2)) => v1 == v2,
|
||||||
|
(Value::Regex(r1), Value::Regex(r2)) => r1.as_str() == r2.as_str(),
|
||||||
|
(Value::None, Value::None) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Value {}
|
||||||
|
|
||||||
impl Display for Value {
|
impl Display for Value {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match &self {
|
match &self {
|
||||||
@ -251,10 +274,11 @@ impl Display for Value {
|
|||||||
f.write_str(s)
|
f.write_str(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Self::Regex(r) => write!(f, "Regex \"{}\"", r.as_str()),
|
||||||
Self::Numerical(n) => write!(f, "{n}"),
|
Self::Numerical(n) => write!(f, "{n}"),
|
||||||
Self::Multiple(m) => {
|
Self::Multiple(m) => {
|
||||||
write!(f, "one of [{}]", m.iter().map(Value::to_string).join(", "))
|
write!(f, "one of [{}]", m.iter().map(Value::to_string).join(", "))
|
||||||
},
|
}
|
||||||
Self::None => f.write_str("none"),
|
Self::None => f.write_str("none"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -277,19 +301,6 @@ mod tests {
|
|||||||
parse_raw_filter(input)
|
parse_raw_filter(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test_case("atk<=>1")]
|
|
||||||
#[test_case("atk=50|")]
|
|
||||||
#[test_case("def=|")]
|
|
||||||
#[test_case("l===10")]
|
|
||||||
#[test_case("t=")]
|
|
||||||
#[test_case("=100")]
|
|
||||||
#[test_case("a")]
|
|
||||||
fn unsuccessful_parsing_test(input: &str) {
|
|
||||||
if let Ok((filters, _)) = parse_filters(input) {
|
|
||||||
assert!(false, "Should have failed, but parsed as {filters:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sequential_parsing_test() {
|
fn sequential_parsing_test() {
|
||||||
let (rest, filter) = parse_raw_filter("atk>=100 l:4").unwrap();
|
let (rest, filter) = parse_raw_filter("atk>=100 l:4").unwrap();
|
||||||
|
Loading…
Reference in New Issue
Block a user