Skip to main content

romm_api/types/
metadata.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4/// Row from `GET /api/search/roms`.
5#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
6pub struct SearchRom {
7    pub name: String,
8    #[serde(default)]
9    pub slug: Option<String>,
10    #[serde(default)]
11    pub summary: Option<String>,
12    pub platform_id: u64,
13    /// Generic provider game id (often mirrors `igdb_id` for IGDB rows).
14    #[serde(default)]
15    pub id: Option<i64>,
16    #[serde(default)]
17    pub igdb_id: Option<i64>,
18    #[serde(default)]
19    pub moby_id: Option<i64>,
20    #[serde(default)]
21    pub ss_id: Option<i64>,
22    #[serde(default)]
23    pub launchbox_id: Option<i64>,
24    #[serde(default)]
25    pub flashpoint_id: Option<String>,
26    #[serde(default)]
27    pub sgdb_id: Option<i64>,
28    #[serde(default)]
29    pub libretro_id: Option<String>,
30    #[serde(default)]
31    pub is_identified: bool,
32    #[serde(default)]
33    pub is_unidentified: bool,
34    #[serde(default)]
35    pub igdb_url_cover: Option<String>,
36    #[serde(default)]
37    pub ss_url_cover: Option<String>,
38    #[serde(default)]
39    pub moby_url_cover: Option<String>,
40    #[serde(flatten)]
41    pub extra: Value,
42}
43
44/// Provider IDs to send in `PUT /api/roms/{id}` when applying a search match.
45#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
46pub struct RomMatchFields {
47    pub igdb_id: Option<i64>,
48    pub moby_id: Option<i64>,
49    pub ss_id: Option<i64>,
50    pub launchbox_id: Option<i64>,
51    pub flashpoint_id: Option<String>,
52    pub sgdb_id: Option<i64>,
53    pub ra_id: Option<i64>,
54    pub hasheous_id: Option<i64>,
55    pub tgdb_id: Option<i64>,
56    pub hltb_id: Option<i64>,
57    pub libretro_id: Option<String>,
58}
59
60impl SearchRom {
61    /// Fields to apply when the user picks this search row (non-null IDs only).
62    pub fn primary_match_fields(&self) -> RomMatchFields {
63        RomMatchFields {
64            igdb_id: self.igdb_id.or(self.id),
65            moby_id: self.moby_id,
66            ss_id: self.ss_id,
67            launchbox_id: self.launchbox_id,
68            flashpoint_id: self.flashpoint_id.clone(),
69            sgdb_id: self.sgdb_id,
70            libretro_id: self.libretro_id.clone(),
71            ..Default::default()
72        }
73    }
74
75    /// Best cover URL from the search row (same priority as RomM web manual match).
76    pub fn best_url_cover(&self) -> Option<String> {
77        [
78            self.igdb_url_cover.as_deref(),
79            self.ss_url_cover.as_deref(),
80            self.moby_url_cover.as_deref(),
81        ]
82        .into_iter()
83        .flatten()
84        .find(|url| !url.is_empty())
85        .map(str::to_string)
86    }
87}
88
89impl RomMatchFields {
90    pub fn is_empty(&self) -> bool {
91        self.igdb_id.is_none()
92            && self.moby_id.is_none()
93            && self.ss_id.is_none()
94            && self.launchbox_id.is_none()
95            && self.flashpoint_id.is_none()
96            && self.sgdb_id.is_none()
97            && self.ra_id.is_none()
98            && self.hasheous_id.is_none()
99            && self.tgdb_id.is_none()
100            && self.hltb_id.is_none()
101            && self.libretro_id.is_none()
102    }
103}
104
105/// Row from `GET /api/search/cover` (SteamGridDB).
106#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
107pub struct SearchCover {
108    pub name: String,
109    #[serde(default)]
110    pub resources: Vec<SgdbResource>,
111}
112
113#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
114pub struct SgdbResource {
115    #[serde(default)]
116    pub width: Option<u32>,
117    #[serde(default)]
118    pub height: Option<u32>,
119    #[serde(default)]
120    pub url: Option<String>,
121    #[serde(flatten)]
122    pub extra: Value,
123}
124
125/// Subset of `DetailedRomSchema` returned by `PUT /api/roms/{id}`.
126#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
127pub struct RomUpdateResponse {
128    pub id: u64,
129    pub platform_id: u64,
130    #[serde(default)]
131    pub name: Option<String>,
132    #[serde(default)]
133    pub summary: Option<String>,
134    #[serde(default)]
135    pub url_cover: Option<String>,
136    #[serde(default)]
137    pub path_cover_small: Option<String>,
138    #[serde(default)]
139    pub path_cover_large: Option<String>,
140    #[serde(default)]
141    pub is_identified: bool,
142    #[serde(default)]
143    pub is_unidentified: bool,
144    #[serde(flatten)]
145    pub extra: Value,
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151
152    #[test]
153    fn search_row_apply_fields_includes_name_summary_and_cover() {
154        let row: SearchRom = serde_json::from_str(
155            r#"{
156            "name": "Super Mario Bros.",
157            "summary": "A platformer.",
158            "platform_id": 1,
159            "igdb_id": 1234,
160            "igdb_url_cover": "https://example.com/cover.jpg",
161            "is_identified": true,
162            "is_unidentified": false
163        }"#,
164        )
165        .unwrap();
166        let fields = crate::core::metadata::search_row_apply_fields(&row);
167        assert_eq!(fields.name.as_deref(), Some("Super Mario Bros."));
168        assert_eq!(fields.summary.as_deref(), Some("A platformer."));
169        assert_eq!(
170            fields.url_cover.as_deref(),
171            Some("https://example.com/cover.jpg")
172        );
173        assert_eq!(fields.match_fields.igdb_id, Some(1234));
174    }
175
176    #[test]
177    fn primary_match_fields_falls_back_to_id() {
178        let row: SearchRom = serde_json::from_str(
179            r#"{"name": "Zelda", "platform_id": 1, "id": 999, "igdb_id": null}"#,
180        )
181        .unwrap();
182        assert_eq!(row.primary_match_fields().igdb_id, Some(999));
183    }
184
185    #[test]
186    fn search_rom_deserializes_demo_shape() {
187        let json = r#"{
188            "name": "Super Mario Bros.",
189            "slug": "super-mario-bros",
190            "summary": "A platformer.",
191            "platform_id": 1,
192            "igdb_id": 1234,
193            "ss_id": null,
194            "moby_id": null,
195            "is_identified": true,
196            "is_unidentified": false,
197            "igdb_url_cover": "https://example.com/cover.jpg"
198        }"#;
199        let row: SearchRom = serde_json::from_str(json).unwrap();
200        assert_eq!(row.name, "Super Mario Bros.");
201        assert_eq!(row.igdb_id, Some(1234));
202        assert_eq!(row.primary_match_fields().igdb_id, Some(1234));
203    }
204}