romm_cli/endpoints/
collections.rs1use crate::types::{Collection, VirtualCollectionRow};
2
3use super::Endpoint;
4use serde_json::Value;
5
6#[derive(Debug, serde::Deserialize)]
8#[serde(untagged)]
9pub enum CollectionsList {
10 List(Vec<Collection>),
11 Paged { items: Vec<Collection> },
12}
13
14impl CollectionsList {
15 pub fn into_vec(self) -> Vec<Collection> {
16 match self {
17 CollectionsList::List(v) => v,
18 CollectionsList::Paged { items } => items,
19 }
20 }
21}
22
23pub fn merge_all_collection_sources(
25 mut manual: Vec<Collection>,
26 mut smart: Vec<Collection>,
27 virtual_rows: Vec<VirtualCollectionRow>,
28) -> Vec<Collection> {
29 for c in &mut manual {
30 c.is_smart = false;
31 c.is_virtual = false;
32 c.virtual_id = None;
33 }
34 for c in &mut smart {
35 c.is_smart = true;
36 c.is_virtual = false;
37 c.virtual_id = None;
38 }
39 let mut virtual_collections: Vec<Collection> =
40 virtual_rows.into_iter().map(Collection::from).collect();
41 manual.append(&mut smart);
42 manual.append(&mut virtual_collections);
43 manual
44}
45
46pub fn merge_manual_and_smart(manual: Vec<Collection>, smart: Vec<Collection>) -> Vec<Collection> {
48 merge_all_collection_sources(manual, smart, Vec::new())
49}
50
51#[derive(Debug, Default, Clone)]
53pub struct ListCollections;
54
55impl Endpoint for ListCollections {
56 type Output = CollectionsList;
57
58 fn method(&self) -> &'static str {
59 "GET"
60 }
61
62 fn path(&self) -> String {
63 "/api/collections".into()
64 }
65}
66
67#[derive(Debug, Default, Clone)]
69pub struct ListSmartCollections;
70
71impl Endpoint for ListSmartCollections {
72 type Output = CollectionsList;
73
74 fn method(&self) -> &'static str {
75 "GET"
76 }
77
78 fn path(&self) -> String {
79 "/api/collections/smart".into()
80 }
81}
82
83#[derive(Debug, Default, Clone)]
85pub struct ListVirtualCollections;
86
87impl Endpoint for ListVirtualCollections {
88 type Output = Vec<VirtualCollectionRow>;
89
90 fn method(&self) -> &'static str {
91 "GET"
92 }
93
94 fn path(&self) -> String {
95 "/api/collections/virtual".into()
96 }
97
98 fn query(&self) -> Vec<(String, String)> {
99 vec![("type".into(), "all".into())]
100 }
101}
102
103#[derive(Debug, Clone)]
105pub struct GetManualCollection {
106 pub id: u64,
107}
108
109impl Endpoint for GetManualCollection {
110 type Output = Value;
111
112 fn method(&self) -> &'static str {
113 "GET"
114 }
115
116 fn path(&self) -> String {
117 format!("/api/collections/{}", self.id)
118 }
119}
120
121#[derive(Debug, Clone)]
123pub struct GetSmartCollection {
124 pub id: u64,
125}
126
127impl Endpoint for GetSmartCollection {
128 type Output = Value;
129
130 fn method(&self) -> &'static str {
131 "GET"
132 }
133
134 fn path(&self) -> String {
135 format!("/api/collections/smart/{}", self.id)
136 }
137}
138
139#[derive(Debug, Clone)]
141pub struct GetVirtualCollection {
142 pub id: String,
143}
144
145impl Endpoint for GetVirtualCollection {
146 type Output = Value;
147
148 fn method(&self) -> &'static str {
149 "GET"
150 }
151
152 fn path(&self) -> String {
153 format!("/api/collections/virtual/{}", self.id)
154 }
155}
156
157#[derive(Debug, Clone)]
159pub struct DeleteManualCollection {
160 pub id: u64,
161}
162
163impl Endpoint for DeleteManualCollection {
164 type Output = Value;
165
166 fn method(&self) -> &'static str {
167 "DELETE"
168 }
169
170 fn path(&self) -> String {
171 format!("/api/collections/{}", self.id)
172 }
173}
174
175#[derive(Debug, Clone)]
177pub struct DeleteSmartCollection {
178 pub id: u64,
179}
180
181impl Endpoint for DeleteSmartCollection {
182 type Output = Value;
183
184 fn method(&self) -> &'static str {
185 "DELETE"
186 }
187
188 fn path(&self) -> String {
189 format!("/api/collections/smart/{}", self.id)
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196 use crate::types::{Collection, VirtualCollectionRow};
197
198 #[test]
199 fn decode_bare_array() {
200 let v = serde_json::json!([
201 {"id": 1, "name": "A", "rom_count": 2}
202 ]);
203 let list: CollectionsList = serde_json::from_value(v).unwrap();
204 let list = list.into_vec();
205 assert_eq!(list.len(), 1);
206 assert_eq!(list[0].id, 1);
207 assert_eq!(list[0].name, "A");
208 }
209
210 #[test]
211 fn decode_paged_object() {
212 let v = serde_json::json!({
213 "items": [{"id": 2, "name": "B", "rom_count": 0}],
214 "total": 1
215 });
216 let list: CollectionsList = serde_json::from_value(v).unwrap();
217 let list = list.into_vec();
218 assert_eq!(list.len(), 1);
219 assert_eq!(list[0].id, 2);
220 }
221
222 #[test]
223 fn merge_manual_and_smart_marks_flags() {
224 let manual = vec![Collection {
225 id: 1,
226 name: "m".into(),
227 collection_type: None,
228 rom_count: Some(1),
229 is_smart: true,
230 is_virtual: false,
231 virtual_id: None,
232 }];
233 let smart = vec![Collection {
234 id: 2,
235 name: "s".into(),
236 collection_type: None,
237 rom_count: Some(2),
238 is_smart: false,
239 is_virtual: false,
240 virtual_id: None,
241 }];
242 let merged = super::merge_manual_and_smart(manual, smart);
243 assert_eq!(merged.len(), 2);
244 assert!(!merged[0].is_smart);
245 assert!(merged[1].is_smart);
246 }
247
248 #[test]
249 fn merge_all_includes_virtual() {
250 let manual = vec![Collection {
251 id: 1,
252 name: "m".into(),
253 collection_type: None,
254 rom_count: Some(1),
255 is_smart: false,
256 is_virtual: false,
257 virtual_id: None,
258 }];
259 let virtual_rows = vec![VirtualCollectionRow {
260 id: "recent".into(),
261 name: "Recent".into(),
262 collection_type: "recent".into(),
263 rom_count: 3,
264 is_virtual: true,
265 }];
266 let merged = super::merge_all_collection_sources(manual, Vec::new(), virtual_rows);
267 assert_eq!(merged.len(), 2);
268 assert!(merged[1].is_virtual);
269 assert_eq!(merged[1].virtual_id.as_deref(), Some("recent"));
270 }
271}