Skip to main content

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}