use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct SearchRom {
pub name: String,
#[serde(default)]
pub slug: Option<String>,
#[serde(default)]
pub summary: Option<String>,
pub platform_id: u64,
#[serde(default)]
pub id: Option<i64>,
#[serde(default)]
pub igdb_id: Option<i64>,
#[serde(default)]
pub moby_id: Option<i64>,
#[serde(default)]
pub ss_id: Option<i64>,
#[serde(default)]
pub launchbox_id: Option<i64>,
#[serde(default)]
pub flashpoint_id: Option<String>,
#[serde(default)]
pub sgdb_id: Option<i64>,
#[serde(default)]
pub libretro_id: Option<String>,
#[serde(default)]
pub is_identified: bool,
#[serde(default)]
pub is_unidentified: bool,
#[serde(default)]
pub igdb_url_cover: Option<String>,
#[serde(default)]
pub ss_url_cover: Option<String>,
#[serde(default)]
pub moby_url_cover: Option<String>,
#[serde(flatten)]
pub extra: Value,
}
#[derive(Debug, Clone, Default, Serialize, PartialEq, Eq)]
pub struct RomMatchFields {
pub igdb_id: Option<i64>,
pub moby_id: Option<i64>,
pub ss_id: Option<i64>,
pub launchbox_id: Option<i64>,
pub flashpoint_id: Option<String>,
pub sgdb_id: Option<i64>,
pub ra_id: Option<i64>,
pub hasheous_id: Option<i64>,
pub tgdb_id: Option<i64>,
pub hltb_id: Option<i64>,
pub libretro_id: Option<String>,
}
impl SearchRom {
pub fn primary_match_fields(&self) -> RomMatchFields {
RomMatchFields {
igdb_id: self.igdb_id.or(self.id),
moby_id: self.moby_id,
ss_id: self.ss_id,
launchbox_id: self.launchbox_id,
flashpoint_id: self.flashpoint_id.clone(),
sgdb_id: self.sgdb_id,
libretro_id: self.libretro_id.clone(),
..Default::default()
}
}
pub fn best_url_cover(&self) -> Option<String> {
[
self.igdb_url_cover.as_deref(),
self.ss_url_cover.as_deref(),
self.moby_url_cover.as_deref(),
]
.into_iter()
.flatten()
.find(|url| !url.is_empty())
.map(str::to_string)
}
}
impl RomMatchFields {
pub fn is_empty(&self) -> bool {
self.igdb_id.is_none()
&& self.moby_id.is_none()
&& self.ss_id.is_none()
&& self.launchbox_id.is_none()
&& self.flashpoint_id.is_none()
&& self.sgdb_id.is_none()
&& self.ra_id.is_none()
&& self.hasheous_id.is_none()
&& self.tgdb_id.is_none()
&& self.hltb_id.is_none()
&& self.libretro_id.is_none()
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct SearchCover {
pub name: String,
#[serde(default)]
pub resources: Vec<SgdbResource>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct SgdbResource {
#[serde(default)]
pub width: Option<u32>,
#[serde(default)]
pub height: Option<u32>,
#[serde(default)]
pub url: Option<String>,
#[serde(flatten)]
pub extra: Value,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct RomUpdateResponse {
pub id: u64,
pub platform_id: u64,
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub summary: Option<String>,
#[serde(default)]
pub url_cover: Option<String>,
#[serde(default)]
pub path_cover_small: Option<String>,
#[serde(default)]
pub path_cover_large: Option<String>,
#[serde(default)]
pub is_identified: bool,
#[serde(default)]
pub is_unidentified: bool,
#[serde(flatten)]
pub extra: Value,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn search_row_apply_fields_includes_name_summary_and_cover() {
let row: SearchRom = serde_json::from_str(
r#"{
"name": "Super Mario Bros.",
"summary": "A platformer.",
"platform_id": 1,
"igdb_id": 1234,
"igdb_url_cover": "https://example.com/cover.jpg",
"is_identified": true,
"is_unidentified": false
}"#,
)
.unwrap();
let fields = crate::core::metadata::search_row_apply_fields(&row);
assert_eq!(fields.name.as_deref(), Some("Super Mario Bros."));
assert_eq!(fields.summary.as_deref(), Some("A platformer."));
assert_eq!(
fields.url_cover.as_deref(),
Some("https://example.com/cover.jpg")
);
assert_eq!(fields.match_fields.igdb_id, Some(1234));
}
#[test]
fn primary_match_fields_falls_back_to_id() {
let row: SearchRom = serde_json::from_str(
r#"{"name": "Zelda", "platform_id": 1, "id": 999, "igdb_id": null}"#,
)
.unwrap();
assert_eq!(row.primary_match_fields().igdb_id, Some(999));
}
#[test]
fn search_rom_deserializes_demo_shape() {
let json = r#"{
"name": "Super Mario Bros.",
"slug": "super-mario-bros",
"summary": "A platformer.",
"platform_id": 1,
"igdb_id": 1234,
"ss_id": null,
"moby_id": null,
"is_identified": true,
"is_unidentified": false,
"igdb_url_cover": "https://example.com/cover.jpg"
}"#;
let row: SearchRom = serde_json::from_str(json).unwrap();
assert_eq!(row.name, "Super Mario Bros.");
assert_eq!(row.igdb_id, Some(1234));
assert_eq!(row.primary_match_fields().igdb_id, Some(1234));
}
}