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-04-17 23:45:59 +02:00
parser ::{ Field , Operator , RawCardFilter , Value } ,
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 ( )
2023-02-04 15:18:56 +01:00
. 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 > ;
2023-04-17 23:45:59 +02:00
fn get_field_value ( card : & SearchCard , field : Field ) -> Value {
match field {
2023-06-26 14:27:53 +02:00
Field ::Atk = > card . atk . map ( Value ::Numerical ) . unwrap_or_default ( ) ,
Field ::Def = > card . def . map ( Value ::Numerical ) . unwrap_or_default ( ) ,
2023-04-17 23:45:59 +02:00
Field ::Legal = > Value ::Numerical ( card . legal_copies ) ,
2023-06-26 14:27:53 +02:00
Field ::Level = > card . level . map ( Value ::Numerical ) . unwrap_or_default ( ) ,
Field ::LinkRating = > card . link_rating . map ( Value ::Numerical ) . unwrap_or_default ( ) ,
Field ::Year = > card . original_year . map ( Value ::Numerical ) . unwrap_or_default ( ) ,
2023-04-24 11:44:02 +02:00
Field ::Set = > Value ::Multiple ( card . sets . clone ( ) . into_iter ( ) . map ( Value ::String ) . collect ( ) ) ,
2023-04-17 23:45:59 +02:00
Field ::Type = > Value ::String ( card . r#type . clone ( ) ) ,
2023-04-18 00:27:31 +02:00
Field ::Attribute = > Value ::String ( card . attribute . clone ( ) . unwrap_or_default ( ) ) ,
2023-04-17 23:45:59 +02:00
Field ::Class = > Value ::String ( card . card_type . clone ( ) ) ,
Field ::Name = > Value ::String ( card . name . clone ( ) ) ,
Field ::Text = > Value ::String ( card . text . clone ( ) ) ,
2023-01-26 23:07:16 +01:00
}
}
2023-04-17 23:45:59 +02:00
fn filter_value ( op : & Operator , field_value : & Value , query_value : & Value ) -> bool {
match ( field_value , query_value ) {
2023-06-26 14:27:53 +02:00
( Value ::None , _ ) = > false ,
2023-04-17 23:45:59 +02:00
( Value ::Numerical ( field ) , Value ::Numerical ( query ) ) = > op . filter_number ( Some ( * field ) , * query ) ,
( Value ::String ( field ) , Value ::String ( query ) ) = > match op {
Operator ::Equal = > field . contains ( query ) ,
Operator ::NotEqual = > ! field . contains ( query ) ,
2023-06-26 14:27:53 +02:00
// greater/less than aren’t supported for string fields.
2023-04-17 23:45:59 +02:00
_ = > false ,
} ,
2023-09-26 13:40:47 +02:00
( 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 ,
} ,
2023-04-24 11:44:02 +02:00
// Currently only for sets the card was released in.
( Value ::Multiple ( field ) , query @ Value ::String ( _ ) ) = > match op {
Operator ::Equal = > field . iter ( ) . any ( | f | f = = query ) ,
Operator ::NotEqual = > ! field . iter ( ) . any ( | f | f = = query ) ,
_ = > false ,
} ,
2023-04-17 23:45:59 +02:00
_ = > false ,
}
}
pub fn build_filter ( RawCardFilter ( field , op , value ) : RawCardFilter ) -> Result < CardFilter , String > {
Ok ( match value {
Value ::Multiple ( values ) = > Box ::new ( move | card : & SearchCard | {
let field_value = get_field_value ( card , field ) ;
values . iter ( ) . any ( | query_value | filter_value ( & op , & field_value , query_value ) )
} ) ,
single_value = > Box ::new ( move | card : & SearchCard | {
let field_value = get_field_value ( card , field ) ;
filter_value ( & op , & field_value , & single_value )
} ) ,
2023-01-26 23:07:16 +01:00
} )
}
#[ cfg(test) ]
mod tests {
use super ::* ;
2023-06-26 14:27:53 +02:00
use crate ::{
data ::tests ::{ RAW_LINK_MONSTER , RAW_MONSTER } ,
parser ::parse_filters ,
} ;
2023-01-26 23:07:16 +01:00
#[ test ]
fn level_filter_test ( ) {
let lacooda = SearchCard ::from ( & serde_json ::from_str ::< Card > ( RAW_MONSTER ) . unwrap ( ) ) ;
2023-04-18 00:27:31 +02:00
let lacooda_but_level_4 = SearchCard { level : Some ( 4 ) , .. lacooda . clone ( ) } ;
2023-04-24 11:44:02 +02:00
let filter_level_3 = parse_filters ( " l=3 " ) . unwrap ( ) . 1 ;
assert! ( filter_level_3 [ 0 ] ( & lacooda ) ) ;
let filter_level_3_4 = parse_filters ( " l=3|4 " ) . unwrap ( ) . 1 ;
assert! ( filter_level_3_4 [ 0 ] ( & lacooda ) ) ;
assert! ( filter_level_3_4 [ 0 ] ( & lacooda_but_level_4 ) ) ;
let filter_level_5 = parse_filters ( " l=5 " ) . unwrap ( ) . 1 ;
assert! ( ! filter_level_5 [ 0 ] ( & lacooda ) ) ;
}
2023-06-26 14:27:53 +02:00
#[ test ]
fn filter_by_level_should_exclude_link_monsters ( ) {
let bls = SearchCard ::from ( & serde_json ::from_str ::< Card > ( RAW_LINK_MONSTER ) . unwrap ( ) ) ;
let filter = parse_filters ( " l<=4 " ) . unwrap ( ) . 1 ;
assert! ( ! filter [ 0 ] ( & bls ) ) ;
}
2023-04-24 11:44:02 +02:00
#[ test ]
fn set_filter_test ( ) {
let lacooda = SearchCard ::from ( & serde_json ::from_str ::< Card > ( RAW_MONSTER ) . unwrap ( ) ) ;
let astral_pack_filter = parse_filters ( " set:ap03 " ) . unwrap ( ) . 1 ;
assert! ( astral_pack_filter [ 0 ] ( & lacooda ) ) ;
let partial_filter = parse_filters ( " set:ap0 " ) . unwrap ( ) . 1 ;
assert! ( ! partial_filter [ 0 ] ( & lacooda ) ) ;
2023-04-18 00:27:31 +02:00
2023-04-24 11:44:02 +02:00
let not_astral_pack_filter = parse_filters ( " set!=ap03 " ) . unwrap ( ) . 1 ;
assert! ( ! not_astral_pack_filter [ 0 ] ( & lacooda ) ) ;
2023-04-18 00:27:31 +02:00
2023-04-24 11:44:02 +02:00
let astral_pack_4_filter = parse_filters ( " set:ap04 " ) . unwrap ( ) . 1 ;
assert! ( ! astral_pack_4_filter [ 0 ] ( & lacooda ) ) ;
2023-01-26 23:07:16 +01:00
}
2023-09-26 13:40:47 +02:00
#[ 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 ) ) ;
}
2023-01-26 23:07:16 +01:00
}