mtgjson_sdk/queries/decks.rs
1//! Deck queries backed by the `DeckList.json` file loaded via the cache manager.
2//!
3//! Unlike parquet-backed queries, deck data is stored as a JSON array in memory.
4//! The `DeckQuery` loads the deck list from the cache on first access and performs
5//! in-memory filtering.
6
7use serde_json::Value;
8
9use crate::connection::Connection;
10use crate::error::Result;
11
12// ---------------------------------------------------------------------------
13// DeckQuery
14// ---------------------------------------------------------------------------
15
16/// Query interface for MTG decks backed by the cached `DeckList.json` data.
17pub struct DeckQuery<'a> {
18 conn: &'a Connection,
19}
20
21impl<'a> DeckQuery<'a> {
22 /// Create a new `DeckQuery` bound to the given connection.
23 pub fn new(conn: &'a Connection) -> Self {
24 Self { conn }
25 }
26
27 /// Load the deck list from the cache.
28 ///
29 /// Returns a `Vec<Value>` representing the array of deck objects.
30 fn load_decks(&self) -> Result<Vec<Value>> {
31 let data = self.conn.cache.borrow_mut().load_json("deck_list")?;
32 match data {
33 Value::Object(map) => {
34 // DeckList.json has { "data": [...] } structure
35 if let Some(Value::Array(arr)) = map.get("data") {
36 Ok(arr.clone())
37 } else {
38 // Try the top-level value as an array
39 Ok(Vec::new())
40 }
41 }
42 Value::Array(arr) => Ok(arr),
43 _ => Ok(Vec::new()),
44 }
45 }
46
47 /// List all decks, optionally filtered by set code and/or deck type.
48 pub fn list(
49 &self,
50 set_code: Option<&str>,
51 deck_type: Option<&str>,
52 ) -> Result<Vec<Value>> {
53 let decks = self.load_decks()?;
54
55 let filtered: Vec<Value> = decks
56 .into_iter()
57 .filter(|d| {
58 if let Some(sc) = set_code {
59 let matches = d
60 .get("code")
61 .and_then(|v| v.as_str())
62 .map(|c| c.eq_ignore_ascii_case(sc))
63 .unwrap_or(false);
64 if !matches {
65 return false;
66 }
67 }
68 if let Some(dt) = deck_type {
69 let matches = d
70 .get("type")
71 .and_then(|v| v.as_str())
72 .map(|t| t.eq_ignore_ascii_case(dt))
73 .unwrap_or(false);
74 if !matches {
75 return false;
76 }
77 }
78 true
79 })
80 .collect();
81
82 Ok(filtered)
83 }
84
85 /// Search for decks by name substring, optionally filtered by set code.
86 pub fn search(&self, name: &str, set_code: Option<&str>) -> Result<Vec<Value>> {
87 let decks = self.load_decks()?;
88 let name_lower = name.to_lowercase();
89
90 let filtered: Vec<Value> = decks
91 .into_iter()
92 .filter(|d| {
93 let name_match = d
94 .get("name")
95 .and_then(|v| v.as_str())
96 .map(|n| n.to_lowercase().contains(&name_lower))
97 .unwrap_or(false);
98 if !name_match {
99 return false;
100 }
101 if let Some(sc) = set_code {
102 let sc_match = d
103 .get("code")
104 .and_then(|v| v.as_str())
105 .map(|c| c.eq_ignore_ascii_case(sc))
106 .unwrap_or(false);
107 if !sc_match {
108 return false;
109 }
110 }
111 true
112 })
113 .collect();
114
115 Ok(filtered)
116 }
117
118 /// Count decks, optionally filtered by set code and/or deck type.
119 pub fn count(
120 &self,
121 set_code: Option<&str>,
122 deck_type: Option<&str>,
123 ) -> Result<usize> {
124 let filtered = self.list(set_code, deck_type)?;
125 Ok(filtered.len())
126 }
127}