Skip to main content

rs_plugin_common_interfaces/domain/
episode.rs

1use crate::domain::{
2    other_ids::OtherIds,
3    rs_ids::{ApplyRsIds, RsIds},
4    tools::rating_serializer,
5};
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
10#[serde(rename_all = "camelCase")]
11pub struct Episode {
12    pub serie: String,
13    pub season: u32,
14    pub number: u32,
15
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub abs: Option<u32>,
18
19    pub name: Option<String>,
20    pub overview: Option<String>,
21
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub alt: Option<Vec<String>>,
24
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub airdate: Option<i64>,
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub duration: Option<u64>,
29
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub params: Option<Value>,
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub imdb: Option<String>,
34    pub slug: Option<String>,
35    pub tmdb: Option<u64>,
36    pub trakt: Option<u64>,
37    pub tvdb: Option<u64>,
38    pub otherids: Option<OtherIds>,
39
40    #[serde(serialize_with = "rating_serializer")]
41    pub imdb_rating: Option<f32>,
42    pub imdb_votes: Option<u64>,
43    #[serde(serialize_with = "rating_serializer")]
44    pub trakt_rating: Option<f32>,
45    pub trakt_votes: Option<u64>,
46
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub watched: Option<i64>,
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub progress: Option<u64>,
51
52    #[serde(default)]
53    pub modified: u64,
54    #[serde(default)]
55    pub added: u64,
56
57    #[serde(default, skip_serializing_if = "Option::is_none")]
58    pub serie_name: Option<String>,
59}
60
61impl Episode {
62    pub fn id(&self) -> String {
63        format!("{}x{}x{}", self.serie, self.season, self.number)
64    }
65}
66
67impl From<Episode> for RsIds {
68    fn from(value: Episode) -> Self {
69        RsIds {
70            redseat: Some(value.id()),
71            trakt: value.trakt,
72            slug: value.slug,
73            tvdb: value.tvdb,
74            imdb: value.imdb,
75            tmdb: value.tmdb,
76            tvrage: None,
77            other_ids: value.otherids,
78            ..Default::default()
79        }
80    }
81}
82
83impl ApplyRsIds for Episode {
84    fn apply_rs_ids(&mut self, ids: &RsIds) {
85        if let Some(trakt) = ids.trakt {
86            self.trakt = Some(trakt);
87        }
88        if let Some(slug) = ids.slug.as_ref() {
89            self.slug = Some(slug.clone());
90        }
91        if let Some(tvdb) = ids.tvdb {
92            self.tvdb = Some(tvdb);
93        }
94        if let Some(imdb) = ids.imdb.as_ref() {
95            self.imdb = Some(imdb.clone());
96        }
97        if let Some(tmdb) = ids.tmdb {
98            self.tmdb = Some(tmdb);
99        }
100        if let Some(other_ids) = ids.other_ids.as_ref() {
101            self.otherids = Some(other_ids.clone());
102        }
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::Episode;
109    use crate::domain::{
110        other_ids::OtherIds,
111        rs_ids::{ApplyRsIds, RsIds},
112    };
113    use serde_json::json;
114
115    #[test]
116    fn episode_otherids_serializes_as_array_and_rejects_string() {
117        let episode = Episode {
118            serie: "serie-1".to_string(),
119            season: 1,
120            number: 1,
121            otherids: Some(OtherIds(vec!["tvmaze:ep-1".to_string()])),
122            ..Default::default()
123        };
124        let value = serde_json::to_value(&episode).unwrap();
125        assert_eq!(value.get("otherids"), Some(&json!(["tvmaze:ep-1"])));
126
127        let parsed: Episode = serde_json::from_value(json!({
128            "serie": "serie-1",
129            "season": 1,
130            "number": 1,
131            "otherids": ["foo:bar"]
132        }))
133        .unwrap();
134        assert_eq!(parsed.otherids, Some(OtherIds(vec!["foo:bar".to_string()])));
135
136        let invalid = serde_json::from_value::<Episode>(json!({
137            "serie": "serie-1",
138            "season": 1,
139            "number": 1,
140            "otherids": "foo:bar"
141        }));
142        assert!(invalid.is_err());
143    }
144
145    #[test]
146    fn episode_apply_rs_ids_updates_only_present_values() {
147        let mut episode = Episode {
148            serie: "serie".to_string(),
149            season: 1,
150            number: 1,
151            tmdb: Some(22),
152            ..Default::default()
153        };
154        let ids = RsIds {
155            trakt: Some(55),
156            tvdb: Some(66),
157            imdb: Some("tt0000066".to_string()),
158            slug: Some("episode-slug".to_string()),
159            other_ids: Some(OtherIds(vec!["tvmaze:ep-1".to_string()])),
160            ..Default::default()
161        };
162
163        episode.apply_rs_ids(&ids);
164
165        assert_eq!(episode.trakt, Some(55));
166        assert_eq!(episode.tvdb, Some(66));
167        assert_eq!(episode.imdb.as_deref(), Some("tt0000066"));
168        assert_eq!(episode.slug.as_deref(), Some("episode-slug"));
169        assert_eq!(episode.tmdb, Some(22));
170        assert_eq!(
171            episode.otherids,
172            Some(OtherIds(vec!["tvmaze:ep-1".to_string()]))
173        );
174    }
175}