From 37e0d47ded08106d4fb23db3ea5efe9e010d0545 Mon Sep 17 00:00:00 2001 From: Miguel Palhas Date: Sun, 23 Aug 2026 22:25:27 +0100 Subject: [PATCH] fix(meta): detail types and metadata endpoint carry unrated as null MovieDetail and SeriesDetail normalise TMDB's vote_average: 0 to None like the persisted structs (#156), and #146's metadata endpoint passes the Option through so no surface reports 0.0 as a rating. vote_count is untouched. --- crates/arr-api/src/metadata.rs | 15 ++++++++++++--- crates/arr-meta/src/model.rs | 16 ++++++++++------ crates/arr-meta/tests/tmdb.rs | 2 +- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/crates/arr-api/src/metadata.rs b/crates/arr-api/src/metadata.rs index 1b8bb74..4601861 100644 --- a/crates/arr-api/src/metadata.rs +++ b/crates/arr-api/src/metadata.rs @@ -46,7 +46,9 @@ pub struct MovieMetadata { pub status: String, pub poster_path: Option, pub backdrop_path: Option, - pub vote_average: f64, + /// TMDB's rating when votes exist; an unrated title is `null`, never `0.0` + /// (#156). + pub vote_average: Option, pub vote_count: u32, pub homepage: Option, /// §9.6 links out to `IMDb` for movies. @@ -68,7 +70,9 @@ pub struct SeriesMetadata { pub status: String, pub poster_path: Option, pub backdrop_path: Option, - pub vote_average: f64, + /// TMDB's rating when votes exist; an unrated title is `null`, never `0.0` + /// (#156). + pub vote_average: Option, pub vote_count: u32, pub homepage: Option, /// §9.6 links out to TVDB for series. @@ -366,7 +370,7 @@ mod tests { "genres": [{"id": 18, "name": "Drama"}], "backdrop_path": "/eMhDKZscBd07OLpAeeAyu3N3U8c.jpg", "poster_path": "/uKvVjHNqB5VmOrdxqAt2F7J78ED.jpg", - "vote_average": 8.5, + "vote_average": 0.0, "vote_count": 2_911, "homepage": "", "status": "Returning Series", @@ -397,6 +401,11 @@ mod tests { assert_eq!(body["tmdb_id"], 82_728); assert_eq!(body["tvdb_id"], 392_256); + // TMDB reports vote_average: 0 for an unrated title (#156): it leaves + // as null, never as a rating of zero. The movie case covers the rated + // pass-through. + assert_eq!(body["vote_average"], serde_json::Value::Null); + assert_eq!(body["vote_count"], 2_911); // TMDB sends a list of episode lengths; the API reports one runtime. assert_eq!(body["runtime"], 55); assert_eq!(body["status"], "Returning Series"); diff --git a/crates/arr-meta/src/model.rs b/crates/arr-meta/src/model.rs index dc68591..f87b12d 100644 --- a/crates/arr-meta/src/model.rs +++ b/crates/arr-meta/src/model.rs @@ -213,8 +213,8 @@ pub struct Video { /// `append_to_response`, served through the same cache as everything else; /// nothing here is persisted. /// -/// No float fields are involved in equality except `vote_average`, so this is -/// `PartialEq` only. +/// No float fields are involved in equality except [`MovieDetail::vote_average`], +/// so this is `PartialEq` only. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct MovieDetail { pub tmdb_id: u32, @@ -223,7 +223,9 @@ pub struct MovieDetail { pub genres: Vec, pub backdrop_path: Option, pub poster_path: Option, - pub vote_average: f64, + /// TMDB's rating when votes exist; zero is normalised away like + /// [`Movie::vote_average`]. + pub vote_average: Option, pub vote_count: u32, pub homepage: Option, pub status: String, @@ -244,7 +246,9 @@ pub struct SeriesDetail { pub genres: Vec, pub backdrop_path: Option, pub poster_path: Option, - pub vote_average: f64, + /// TMDB's rating when votes exist; zero is normalised away like + /// [`Movie::vote_average`]. + pub vote_average: Option, pub vote_count: u32, pub homepage: Option, pub status: String, @@ -771,7 +775,7 @@ impl From for MovieDetail { .collect(), backdrop_path: non_empty(raw.backdrop_path), poster_path: non_empty(raw.poster_path), - vote_average: raw.vote_average, + vote_average: rating(raw.vote_average), vote_count: raw.vote_count, homepage: non_empty(raw.homepage), status: raw.status, @@ -799,7 +803,7 @@ impl From for SeriesDetail { .collect(), backdrop_path: non_empty(raw.backdrop_path), poster_path: non_empty(raw.poster_path), - vote_average: raw.vote_average, + vote_average: rating(raw.vote_average), vote_count: raw.vote_count, homepage: non_empty(raw.homepage), status: raw.status, diff --git a/crates/arr-meta/tests/tmdb.rs b/crates/arr-meta/tests/tmdb.rs index 335b8ac..e802c51 100644 --- a/crates/arr-meta/tests/tmdb.rs +++ b/crates/arr-meta/tests/tmdb.rs @@ -613,7 +613,7 @@ async fn movie_detail_parses_the_rich_fields() { assert_eq!(movie.genres[0].name, "Science Fiction"); assert!(movie.poster_path.is_some()); assert!(movie.backdrop_path.is_some()); - assert!((movie.vote_average - 8.152).abs() < f64::EPSILON); + assert_eq!(movie.vote_average, Some(8.152)); assert_eq!(movie.vote_count, 6_249); assert_eq!(movie.homepage.as_deref(), Some("https://www.dunemovie.com")); assert_eq!(movie.status, "Released");