aro/src/filter.rs

110 lines
5.4 KiB
Rust
Raw Normal View History

2023-02-02 11:34:58 +01:00
use time::Date;
2023-01-26 23:07:16 +01:00
use crate::{
2023-02-03 16:19:26 +01:00
data::{BanlistStatus, Card},
2023-01-27 15:48:07 +01:00
parser::{Field, Operator, RawCardFilter, Value, OPERATOR_CHARS},
2023-02-02 11:34:58 +01:00
SETS_BY_NAME,
2023-01-26 23:07:16 +01:00
};
/// A struct derived from `Card` that has all fields lowercased for easier search
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct SearchCard {
2023-02-02 11:34:58 +01:00
pub id: usize,
card_type: String,
name: String,
text: String,
atk: Option<i32>,
def: Option<i32>,
attribute: Option<String>,
r#type: String,
2023-01-26 23:07:16 +01:00
// also includes rank
2023-02-02 11:34:58 +01:00
level: Option<i32>,
link_rating: Option<i32>,
link_arrows: Option<Vec<String>>,
sets: Vec<String>,
original_year: Option<i32>,
2023-02-03 16:19:26 +01:00
legal_copies: i32,
2023-01-26 23:07:16 +01:00
}
impl From<&Card> for SearchCard {
fn from(card: &Card) -> Self {
Self {
2023-02-02 11:34:58 +01:00
id: card.id,
card_type: card.card_type.to_lowercase(),
name: card.name.to_lowercase(),
text: card.text.to_lowercase(),
atk: card.atk,
def: card.def,
attribute: card.attribute.as_ref().map(|s| s.to_lowercase()),
r#type: card.r#type.to_lowercase(),
level: card.level,
link_rating: card.link_rating,
link_arrows: card.link_arrows.as_ref().map(|arrows| arrows.iter().map(|a| a.to_lowercase()).collect()),
sets: card.card_sets.iter().filter_map(|s| s.set_code.split('-').next().map(str::to_lowercase)).collect(),
original_year: card
.card_sets
.iter()
.filter_map(|s| SETS_BY_NAME.get(&s.set_name.to_lowercase()).and_then(|s| s.tcg_date))
2023-02-02 11:34:58 +01:00
.map(Date::year)
.min(),
2023-02-03 16:19:26 +01:00
legal_copies: card.banlist_info.map(|bi| bi.ban_tcg).unwrap_or(BanlistStatus::Unlimited) as i32,
2023-01-26 23:07:16 +01:00
}
}
}
pub type CardFilter = Box<dyn Fn(&SearchCard) -> bool>;
pub fn fallback_filter(query: &str) -> Result<RawCardFilter, String> {
2023-01-26 23:10:13 +01:00
if query.contains(OPERATOR_CHARS) {
2023-01-26 23:07:16 +01:00
return Err(format!("Invalid query: {query}"));
}
let q = query.to_lowercase();
2023-01-27 15:48:07 +01:00
Ok(RawCardFilter(Field::Name, Operator::Equal, Value::String(q)))
2023-01-26 23:07:16 +01:00
}
pub fn build_filter(query: RawCardFilter) -> Result<CardFilter, String> {
Ok(match query {
2023-01-27 15:48:07 +01:00
RawCardFilter(Field::Atk, op, Value::Numerical(n)) => Box::new(move |card| op.filter_number(card.atk, n)),
RawCardFilter(Field::Def, op, Value::Numerical(n)) => Box::new(move |card| op.filter_number(card.def, n)),
2023-01-26 23:07:16 +01:00
// ? ATK/DEF is modeled as None in the source json. At least for some monsters.
// Let’s at least find those.
2023-01-27 15:48:07 +01:00
RawCardFilter(Field::Atk, _, Value::String(s)) if s == "?" => {
Box::new(move |card| card.atk.is_none() && card.card_type.contains("monster"))
}
RawCardFilter(Field::Def, _, Value::String(s)) if s == "?" => {
2023-01-26 23:07:16 +01:00
Box::new(move |card| card.def.is_none() && card.link_rating.is_none() && card.card_type.contains("monster"))
}
2023-01-27 15:48:07 +01:00
RawCardFilter(Field::Level, op, Value::Numerical(n)) => Box::new(move |card| op.filter_number(card.level, n)),
2023-01-30 11:51:36 +01:00
RawCardFilter(Field::LinkRating, op, Value::Numerical(n)) => Box::new(move |card| op.filter_number(card.link_rating, n)),
2023-02-02 11:34:58 +01:00
RawCardFilter(Field::Year, op, Value::Numerical(n)) => Box::new(move |card| op.filter_number(card.original_year, n)),
2023-02-03 16:19:26 +01:00
RawCardFilter(Field::Legal, op, Value::Numerical(n)) => Box::new(move |card| op.filter_number(Some(card.legal_copies), n)),
2023-01-27 15:48:07 +01:00
RawCardFilter(Field::Type, Operator::Equal, Value::String(s)) => Box::new(move |card| card.r#type == s),
RawCardFilter(Field::Type, Operator::NotEqual, Value::String(s)) => Box::new(move |card| card.r#type != s),
RawCardFilter(Field::Attribute, Operator::Equal, Value::String(s)) => Box::new(move |card| card.attribute.contains(&s)),
RawCardFilter(Field::Attribute, Operator::NotEqual, Value::String(s)) => Box::new(move |card| !card.attribute.contains(&s)),
RawCardFilter(Field::Class, Operator::Equal, Value::String(s)) => Box::new(move |card| card.card_type.contains(&s)),
RawCardFilter(Field::Class, Operator::NotEqual, Value::String(s)) => Box::new(move |card| !card.card_type.contains(&s)),
RawCardFilter(Field::Text, Operator::Equal, Value::String(s)) => Box::new(move |card| card.text.contains(&s)),
2023-01-30 17:40:19 +01:00
RawCardFilter(Field::Text, Operator::NotEqual, Value::String(s)) => Box::new(move |card| !card.text.contains(&s)),
2023-01-27 15:48:07 +01:00
RawCardFilter(Field::Name, Operator::Equal, Value::String(s)) => Box::new(move |card| card.name.contains(&s)),
2023-01-30 17:40:19 +01:00
RawCardFilter(Field::Name, Operator::NotEqual, Value::String(s)) => Box::new(move |card| !card.name.contains(&s)),
RawCardFilter(Field::Set, Operator::Equal, Value::String(s)) => Box::new(move |card| card.sets.contains(&s)),
2023-01-26 23:07:16 +01:00
q => Err(format!("unknown query: {q:?}"))?,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{data::tests::RAW_MONSTER, parser::parse_filters};
#[test]
fn level_filter_test() {
let lacooda = SearchCard::from(&serde_json::from_str::<Card>(RAW_MONSTER).unwrap());
let filter_level_3 = parse_filters("l=3").unwrap();
assert!(filter_level_3.1[0](&lacooda));
2023-01-26 23:07:16 +01:00
let filter_level_5 = parse_filters("l=5").unwrap();
assert!(!filter_level_5.1[0](&lacooda));
2023-01-26 23:07:16 +01:00
}
}