1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Deserialize, Serialize)]
5pub struct Firmware {
6 pub id: u64,
8 pub file_name: String,
10 pub file_name_no_tags: String,
12 pub file_name_no_ext: String,
14 pub file_extension: String,
16 pub file_path: String,
18 pub file_size_bytes: u64,
20 pub full_path: String,
22 pub is_verified: bool,
24 pub crc_hash: String,
26 pub md5_hash: String,
28 pub sha1_hash: String,
30 pub missing_from_fs: bool,
32 pub created_at: String,
34 pub updated_at: String,
36}
37
38#[derive(Debug, Clone, Deserialize, Serialize)]
40pub struct Platform {
41 pub id: u64,
43 pub slug: String,
45 pub fs_slug: String,
47 pub rom_count: u64,
49 pub name: String,
51 pub igdb_slug: Option<String>,
53 pub moby_slug: Option<String>,
55 pub hltb_slug: Option<String>,
57 pub custom_name: Option<String>,
59 pub igdb_id: Option<i64>,
61 pub sgdb_id: Option<i64>,
63 pub moby_id: Option<i64>,
65 pub launchbox_id: Option<i64>,
67 pub ss_id: Option<i64>,
69 pub ra_id: Option<i64>,
71 pub hasheous_id: Option<i64>,
73 pub tgdb_id: Option<i64>,
75 pub flashpoint_id: Option<i64>,
77 pub category: Option<String>,
79 pub generation: Option<i64>,
81 pub family_name: Option<String>,
83 pub family_slug: Option<String>,
85 pub url: Option<String>,
87 pub url_logo: Option<String>,
89 pub firmware: Vec<Firmware>,
91 pub aspect_ratio: Option<String>,
93 pub created_at: String,
95 pub updated_at: String,
97 pub fs_size_bytes: u64,
99 pub is_unidentified: bool,
101 pub is_identified: bool,
103 pub missing_from_fs: bool,
105 pub display_name: Option<String>,
107}
108
109#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash)]
111#[serde(rename_all = "lowercase")]
112pub enum RomFileCategory {
113 Game,
114 Dlc,
115 Hack,
116 Manual,
117 Patch,
118 Update,
119 Mod,
120 Demo,
121 Translation,
122 Prototype,
123 Cheat,
124}
125
126#[derive(Debug, Clone, Deserialize, Serialize)]
128pub struct RomFile {
129 pub id: u64,
130 pub rom_id: u64,
131 pub file_name: String,
132 pub file_path: String,
133 pub file_size_bytes: u64,
134 #[serde(default)]
135 pub category: Option<RomFileCategory>,
136}
137
138#[derive(Debug, Clone, Deserialize, Serialize)]
140pub struct Rom {
141 pub id: u64,
143 pub platform_id: u64,
145 pub platform_slug: Option<String>,
147 pub platform_fs_slug: Option<String>,
149 pub platform_custom_name: Option<String>,
151 pub platform_display_name: Option<String>,
153 pub fs_name: String,
155 pub fs_name_no_tags: String,
157 pub fs_name_no_ext: String,
159 pub fs_extension: String,
161 pub fs_path: String,
163 pub fs_size_bytes: u64,
165 pub name: String,
167 pub slug: Option<String>,
169 pub summary: Option<String>,
171 pub path_cover_small: Option<String>,
173 pub path_cover_large: Option<String>,
175 pub url_cover: Option<String>,
177 #[serde(default)]
179 pub has_manual: bool,
180 #[serde(default)]
182 pub path_manual: Option<String>,
183 #[serde(default)]
185 pub url_manual: Option<String>,
186 pub is_unidentified: bool,
188 pub is_identified: bool,
190 #[serde(default)]
192 pub files: Vec<RomFile>,
193}
194
195#[derive(Debug, Clone, Deserialize, Serialize)]
197pub struct RomList {
198 pub items: Vec<Rom>,
200 pub total: u64,
202 pub limit: u64,
204 pub offset: u64,
206}
207
208#[derive(Debug, Clone, Deserialize, Serialize)]
210pub struct VirtualCollectionRow {
211 pub id: String,
212 pub name: String,
213 #[serde(rename = "type")]
214 pub collection_type: String,
215 #[serde(default)]
216 pub rom_count: u64,
217 #[serde(default)]
218 pub is_virtual: bool,
219}
220
221impl From<VirtualCollectionRow> for Collection {
222 fn from(v: VirtualCollectionRow) -> Self {
223 Self {
224 id: 0,
225 name: v.name,
226 collection_type: Some(v.collection_type),
227 rom_count: Some(v.rom_count),
228 is_smart: false,
229 is_virtual: true,
230 virtual_id: Some(v.id),
231 }
232 }
233}
234
235#[derive(Debug, Clone, Deserialize, Serialize)]
240pub struct Collection {
241 pub id: u64,
242 pub name: String,
243 #[serde(rename = "type")]
244 pub collection_type: Option<String>,
245 pub rom_count: Option<u64>,
246 #[serde(default)]
248 pub is_smart: bool,
249 #[serde(default)]
251 pub is_virtual: bool,
252 #[serde(default)]
253 pub virtual_id: Option<String>,
254}
255
256#[cfg(test)]
257mod rom_files_serde_tests {
258 use super::{Rom, RomFile, RomFileCategory};
259 use serde_json::json;
260
261 fn minimal_rom_json() -> serde_json::Value {
262 json!({
263 "id": 1,
264 "platform_id": 2,
265 "platform_slug": null,
266 "platform_fs_slug": null,
267 "platform_custom_name": null,
268 "platform_display_name": null,
269 "fs_name": "game.nsp",
270 "fs_name_no_tags": "game",
271 "fs_name_no_ext": "game",
272 "fs_extension": "nsp",
273 "fs_path": "/game.nsp",
274 "fs_size_bytes": 100,
275 "name": "Game",
276 "slug": null,
277 "summary": null,
278 "path_cover_small": null,
279 "path_cover_large": null,
280 "url_cover": null,
281 "has_manual": false,
282 "path_manual": null,
283 "url_manual": null,
284 "is_unidentified": false,
285 "is_identified": true
286 })
287 }
288
289 #[test]
290 fn rom_deserializes_empty_files_when_field_missing() {
291 let rom: Rom = serde_json::from_value(minimal_rom_json()).expect("rom");
292 assert!(rom.files.is_empty());
293 }
294
295 #[test]
296 fn rom_deserializes_when_manual_fields_missing() {
297 let mut v = minimal_rom_json();
298 let obj = v.as_object_mut().expect("object");
299 obj.remove("has_manual");
300 obj.remove("path_manual");
301 obj.remove("url_manual");
302
303 let rom: Rom = serde_json::from_value(v).expect("rom");
304 assert!(!rom.has_manual);
305 assert_eq!(rom.path_manual, None);
306 assert_eq!(rom.url_manual, None);
307 }
308
309 #[test]
310 fn rom_deserializes_files_array() {
311 let mut v = minimal_rom_json();
312 v["files"] = json!([
313 {
314 "id": 10,
315 "rom_id": 1,
316 "file_name": "base.nsp",
317 "file_path": "/base.nsp",
318 "file_size_bytes": 60,
319 "category": "game"
320 },
321 {
322 "id": 11,
323 "rom_id": 1,
324 "file_name": "upd.nsp",
325 "file_path": "/upd.nsp",
326 "file_size_bytes": 40,
327 "category": "update"
328 }
329 ]);
330 let rom: Rom = serde_json::from_value(v).expect("rom");
331 assert_eq!(rom.files.len(), 2);
332 assert_eq!(rom.files[0].category, Some(RomFileCategory::Game));
333 assert_eq!(rom.files[1].category, Some(RomFileCategory::Update));
334 }
335
336 #[test]
337 fn rom_file_category_none_deserializes() {
338 let f: RomFile = serde_json::from_value(json!({
339 "id": 1,
340 "rom_id": 2,
341 "file_name": "x.bin",
342 "file_path": "/x.bin",
343 "file_size_bytes": 1
344 }))
345 .expect("romfile");
346 assert_eq!(f.category, None);
347 }
348}