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