Skip to main content

tmdb_rs/
common.rs

1use std::collections::HashMap;
2use std::str::FromStr;
3
4use serde::{Deserialize, Deserializer};
5use time::macros::format_description;
6use time::Date;
7
8use crate::append::appendable;
9use crate::endpoints::credit::Credits;
10use crate::endpoints::keyword::{MovieKeywords, TvKeywords};
11use crate::endpoints::movie::ReleaseDates;
12use crate::{Backdrop, CountryCode, Language, Logo, Poster};
13
14#[derive(Debug, Clone, Deserialize)]
15pub struct Video {
16    pub id: String,
17    pub key: String,
18    pub name: String,
19    pub site: String,
20    #[serde(rename = "type")]
21    pub kind: String,
22    pub size: u32,
23    pub official: bool,
24}
25
26#[derive(Debug, Clone, Deserialize)]
27pub struct Videos {
28    pub results: Vec<Video>,
29}
30
31#[derive(Debug, Clone, Deserialize)]
32pub struct Image<K> {
33    #[serde(rename = "file_path")]
34    pub file: K,
35    pub width: u32,
36    pub height: u32,
37    pub aspect_ratio: f64,
38    pub vote_average: f64,
39    pub vote_count: u32,
40    /// textless images have no language
41    #[serde(rename = "iso_639_1")]
42    pub language: Option<Language>,
43}
44
45#[derive(Debug, Clone, Default, Deserialize)]
46#[serde(default)]
47pub struct Images {
48    pub backdrops: Vec<Image<Backdrop>>,
49    pub posters: Vec<Image<Poster>>,
50    pub logos: Vec<Image<Logo>>,
51}
52
53#[derive(Debug, Clone, Deserialize)]
54pub struct ExternalIds {
55    pub imdb_id: Option<String>,
56    pub wikidata_id: Option<String>,
57    pub tvdb_id: Option<u64>,
58    pub facebook_id: Option<String>,
59    pub instagram_id: Option<String>,
60    pub twitter_id: Option<String>,
61}
62
63/// movie or series, for write-endpoint bodies
64#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
65#[serde(rename_all = "snake_case")]
66pub enum MediaType {
67    Movie,
68    Tv,
69}
70
71/// the success envelope of write endpoints
72#[derive(Debug, Clone, Deserialize)]
73pub struct StatusResponse {
74    pub success: bool,
75    pub status_code: i32,
76    pub status_message: String,
77}
78
79#[derive(Debug, Clone, Deserialize)]
80pub struct Translation {
81    #[serde(rename = "iso_3166_1")]
82    pub country: CountryCode,
83    #[serde(rename = "iso_639_1")]
84    pub language: Language,
85    pub name: String,
86    pub english_name: String,
87    pub data: TranslationData,
88}
89
90/// movies fill `title`, series `name`
91#[derive(Debug, Clone, Deserialize)]
92pub struct TranslationData {
93    pub title: Option<String>,
94    pub name: Option<String>,
95    pub overview: Option<String>,
96    #[serde(rename = "homepage")]
97    pub website: Option<String>,
98    pub tagline: Option<String>,
99}
100
101impl TranslationData {
102    /// the translated title, whichever of the movie/series keys it came in
103    pub fn display_title(&self) -> Option<&str> {
104        self.title.as_deref().or(self.name.as_deref())
105    }
106}
107
108#[derive(Debug, Clone, Deserialize)]
109pub struct Translations {
110    pub translations: Vec<Translation>,
111}
112
113#[derive(Debug, Clone, Deserialize)]
114pub struct AlternativeTitle {
115    #[serde(rename = "iso_3166_1")]
116    pub country: CountryCode,
117    #[serde(rename = "title")]
118    pub name: String,
119    #[serde(rename = "type")]
120    pub kind: String,
121}
122
123/// `/movie` or `/tv` `alternative_titles` — movies wrap it in `titles`, series in `results`
124#[derive(Debug, Clone, Deserialize)]
125#[serde(from = "RawAlternativeTitles")]
126pub struct AlternativeTitles {
127    pub titles: Vec<AlternativeTitle>,
128}
129
130#[derive(Deserialize)]
131struct RawAlternativeTitles {
132    titles: Option<Vec<AlternativeTitle>>,
133    results: Option<Vec<AlternativeTitle>>,
134}
135
136impl From<RawAlternativeTitles> for AlternativeTitles {
137    fn from(raw: RawAlternativeTitles) -> Self {
138        Self {
139            titles: raw.titles.or(raw.results).unwrap_or_default(),
140        }
141    }
142}
143
144#[derive(Debug, Clone, Deserialize)]
145pub struct AlternativeName {
146    pub name: String,
147    #[serde(rename = "type")]
148    pub kind: String,
149}
150
151#[derive(Debug, Clone, Deserialize)]
152pub struct ChangeItem {
153    pub id: String,
154    pub action: String,
155    pub time: String,
156    #[serde(rename = "iso_639_1")]
157    pub language: Option<Language>,
158    #[serde(rename = "iso_3166_1")]
159    pub country: Option<CountryCode>,
160    /// free-form: the shape depends on the changed key
161    pub value: serde_json::Value,
162    pub original_value: Option<serde_json::Value>,
163}
164
165#[derive(Debug, Clone, Deserialize)]
166pub struct Change {
167    pub key: String,
168    pub items: Vec<ChangeItem>,
169}
170
171#[derive(Debug, Clone, Deserialize)]
172pub struct Changes {
173    pub changes: Vec<Change>,
174}
175
176/// `rated` is `false` until the user rates the title, then an object
177#[derive(Debug, Clone, Copy, Deserialize)]
178#[serde(untagged)]
179pub enum Rated {
180    Unrated(bool),
181    Rated { value: f64 },
182}
183
184impl Rated {
185    pub fn value(&self) -> Option<f64> {
186        match self {
187            Self::Unrated(_) => None,
188            Self::Rated { value } => Some(*value),
189        }
190    }
191}
192
193#[derive(Debug, Clone, Deserialize)]
194pub struct AccountStates {
195    pub favorite: bool,
196    pub watchlist: bool,
197    pub rated: Rated,
198}
199
200#[derive(Debug, Clone, Deserialize)]
201pub struct WatchProvider {
202    pub provider_id: u64,
203    pub provider_name: String,
204    #[serde(rename = "logo_path")]
205    pub logo: Option<Logo>,
206    pub display_priority: Option<u32>,
207}
208
209/// one country's watch options for a title
210#[derive(Debug, Clone, Default, Deserialize)]
211#[serde(default)]
212pub struct CountryProviders {
213    pub link: Option<String>,
214    pub flatrate: Vec<WatchProvider>,
215    pub rent: Vec<WatchProvider>,
216    pub buy: Vec<WatchProvider>,
217    pub ads: Vec<WatchProvider>,
218    pub free: Vec<WatchProvider>,
219}
220
221/// the `watch/providers` payload, keyed by country code
222#[derive(Debug, Clone, Deserialize)]
223pub struct WatchProviders {
224    pub results: HashMap<String, CountryProviders>,
225}
226
227appendable! {
228    Credits,
229    ReleaseDates,
230    MovieKeywords,
231    TvKeywords,
232    ExternalIds,
233    Videos,
234    Images,
235    Translations,
236    AlternativeTitles,
237    Changes,
238    AccountStates,
239    WatchProviders,
240}
241
242/// one page of a paginated list endpoint
243#[derive(Debug, Clone, Deserialize)]
244pub struct Page<T> {
245    pub page: u32,
246    pub results: Vec<T>,
247    pub total_pages: u32,
248    pub total_results: u32,
249}
250
251impl<T> Page<T> {
252    pub fn has_next(&self) -> bool {
253        self.page < self.total_pages
254    }
255}
256
257/// TMDB writes unknown languages as "" or codes outside ISO 639-1 ("xx", legacy "cn")
258pub(crate) fn opt_language<'de, D: Deserializer<'de>>(d: D) -> Result<Option<Language>, D::Error> {
259    Ok(Option::<String>::deserialize(d)?.and_then(|s| Language::from_str(&s).ok()))
260}
261
262/// skips codes outside ISO 639-1, see opt_language
263pub(crate) fn language_list<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<Language>, D::Error> {
264    Ok(Vec::<String>::deserialize(d)?
265        .into_iter()
266        .filter_map(|s| Language::from_str(&s).ok())
267        .collect())
268}
269
270/// TMDB writes unknown dates as "" rather than null
271pub(crate) fn opt_date<'de, D: Deserializer<'de>>(d: D) -> Result<Option<Date>, D::Error> {
272    match Option::<String>::deserialize(d)?.filter(|s| !s.is_empty()) {
273        Some(s) => Date::parse(&s, format_description!("[year]-[month]-[day]"))
274            .map(Some)
275            .map_err(serde::de::Error::custom),
276        None => Ok(None),
277    }
278}