Skip to main content

romm_cli/
types.rs

1use serde::{Deserialize, Serialize};
2
3/// Represents a firmware file associated with a platform.
4#[derive(Debug, Clone, Deserialize, Serialize)]
5pub struct Firmware {
6    /// Unique identifier for the firmware.
7    pub id: u64,
8    /// Original file name of the firmware.
9    pub file_name: String,
10    /// File name without RomM tags.
11    pub file_name_no_tags: String,
12    /// File name without extension.
13    pub file_name_no_ext: String,
14    /// File extension (e.g., ".bin").
15    pub file_extension: String,
16    /// Relative file path within the RomM storage.
17    pub file_path: String,
18    /// File size in bytes.
19    pub file_size_bytes: u64,
20    /// Full absolute path to the file.
21    pub full_path: String,
22    /// Whether the firmware hash has been verified against a database.
23    pub is_verified: bool,
24    /// CRC32 hash of the file.
25    pub crc_hash: String,
26    /// MD5 hash of the file.
27    pub md5_hash: String,
28    /// SHA1 hash of the file.
29    pub sha1_hash: String,
30    /// True if the file is missing from the filesystem.
31    pub missing_from_fs: bool,
32    /// ISO 8601 creation timestamp.
33    pub created_at: String,
34    /// ISO 8601 update timestamp.
35    pub updated_at: String,
36}
37
38/// A gaming platform (console or system) supported by RomM.
39#[derive(Debug, Clone, Deserialize, Serialize)]
40pub struct Platform {
41    /// Unique identifier for the platform.
42    pub id: u64,
43    /// URL-friendly slug (e.g., "nes").
44    pub slug: String,
45    /// Filesystem-friendly slug used for directory naming.
46    pub fs_slug: String,
47    /// Total number of ROMs assigned to this platform.
48    pub rom_count: u64,
49    /// Canonical name of the platform.
50    pub name: String,
51    /// IGDB slug for metadata lookup.
52    pub igdb_slug: Option<String>,
53    /// MobyGames slug for metadata lookup.
54    pub moby_slug: Option<String>,
55    /// HowLongToBeat slug for metadata lookup.
56    pub hltb_slug: Option<String>,
57    /// Custom user-defined name for the platform.
58    pub custom_name: Option<String>,
59    /// IGDB ID for metadata lookup.
60    pub igdb_id: Option<i64>,
61    /// ScreenScraper ID for metadata lookup.
62    pub sgdb_id: Option<i64>,
63    /// MobyGames ID for metadata lookup.
64    pub moby_id: Option<i64>,
65    /// LaunchBox ID for metadata lookup.
66    pub launchbox_id: Option<i64>,
67    /// ScreenScraper ID for metadata lookup.
68    pub ss_id: Option<i64>,
69    /// RetroAchievements ID for metadata lookup.
70    pub ra_id: Option<i64>,
71    /// Hasheous ID for metadata lookup.
72    pub hasheous_id: Option<i64>,
73    /// The Games DB ID for metadata lookup.
74    pub tgdb_id: Option<i64>,
75    /// Flashpoint ID for metadata lookup.
76    pub flashpoint_id: Option<i64>,
77    /// Category of the platform (e.g., "Console", "Handheld").
78    pub category: Option<String>,
79    /// Console generation (e.g., 3).
80    pub generation: Option<i64>,
81    /// Name of the platform family (e.g., "Nintendo").
82    pub family_name: Option<String>,
83    /// Slug of the platform family (e.g., "nintendo").
84    pub family_slug: Option<String>,
85    /// Official website URL.
86    pub url: Option<String>,
87    /// URL to the platform logo image.
88    pub url_logo: Option<String>,
89    /// List of firmware files required or associated with this platform.
90    pub firmware: Vec<Firmware>,
91    /// Preferred aspect ratio for the platform.
92    pub aspect_ratio: Option<String>,
93    /// ISO 8601 creation timestamp.
94    pub created_at: String,
95    /// ISO 8601 update timestamp.
96    pub updated_at: String,
97    /// Total size of all ROMs for this platform in bytes.
98    pub fs_size_bytes: u64,
99    /// True if the platform is not yet fully identified in the RomM database.
100    pub is_unidentified: bool,
101    /// True if the platform has been identified and linked to metadata.
102    pub is_identified: bool,
103    /// True if the platform directory is missing from the filesystem.
104    pub missing_from_fs: bool,
105    /// Name used for display in the UI (custom name or original name).
106    pub display_name: Option<String>,
107}
108
109/// Category of an internal file within a multi-file RomM ROM (see `Rom::files`).
110#[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/// One file row under a RomM `Rom` (base game, update, DLC, etc.).
127#[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/// Represents a single ROM file and its associated metadata.
139#[derive(Debug, Clone, Deserialize, Serialize)]
140pub struct Rom {
141    /// Unique identifier for the ROM.
142    pub id: u64,
143    /// ID of the parent platform.
144    pub platform_id: u64,
145    /// Slug of the parent platform.
146    pub platform_slug: Option<String>,
147    /// Filesystem slug of the parent platform.
148    pub platform_fs_slug: Option<String>,
149    /// Custom name of the parent platform.
150    pub platform_custom_name: Option<String>,
151    /// Display name of the parent platform.
152    pub platform_display_name: Option<String>,
153    /// Name of the ROM file on disk.
154    pub fs_name: String,
155    /// ROM file name without RomM tags.
156    pub fs_name_no_tags: String,
157    /// ROM file name without extension.
158    pub fs_name_no_ext: String,
159    /// File extension of the ROM (e.g., ".nes").
160    pub fs_extension: String,
161    /// Relative path to the ROM file.
162    pub fs_path: String,
163    /// Size of the ROM file in bytes.
164    pub fs_size_bytes: u64,
165    /// Canonical name of the game.
166    pub name: String,
167    /// URL-friendly slug for the game.
168    pub slug: Option<String>,
169    /// Brief description or summary of the game.
170    pub summary: Option<String>,
171    /// Path to a small thumbnail cover image.
172    pub path_cover_small: Option<String>,
173    /// Path to a large cover image.
174    pub path_cover_large: Option<String>,
175    /// Original URL of the cover image.
176    pub url_cover: Option<String>,
177    /// True if the ROM has an associated manual.
178    #[serde(default)]
179    pub has_manual: bool,
180    /// Path to the manual file.
181    #[serde(default)]
182    pub path_manual: Option<String>,
183    /// Original URL of the manual file.
184    #[serde(default)]
185    pub url_manual: Option<String>,
186    /// True if the ROM is not yet fully identified.
187    pub is_unidentified: bool,
188    /// True if the ROM has been identified and linked to metadata.
189    pub is_identified: bool,
190    /// Internal files for multi-part ROMs (Switch, PS3, etc.); empty for legacy single-file ROMs.
191    #[serde(default)]
192    pub files: Vec<RomFile>,
193}
194
195/// A paginated list of ROMs returned by the API.
196#[derive(Debug, Clone, Deserialize, Serialize)]
197pub struct RomList {
198    /// The list of ROM items in this page.
199    pub items: Vec<Rom>,
200    /// Total number of ROMs matching the query across all pages.
201    pub total: u64,
202    /// Maximum number of items returned in this request.
203    pub limit: u64,
204    /// Number of items skipped from the beginning.
205    pub offset: u64,
206}
207
208/// Response row from [`GET /api/collections/virtual`](crate::endpoints::collections::ListVirtualCollections).
209#[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/// Manual / smart / virtual row for the library collections pane.
236///
237/// Virtual (autogenerated) collections use string IDs from RomM; see [`virtual_id`](Self::virtual_id)
238/// and [`is_virtual`](Self::is_virtual). Numeric [`id`](Self::id) is unused (0) for virtual rows.
239#[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    /// Smart collections are listed separately by RomM; used for ROM filter/cache keys.
247    #[serde(default)]
248    pub is_smart: bool,
249    /// Autogenerated / virtual collections from `GET /api/collections/virtual`.
250    #[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}