rs_plugin_common_interfaces/domain/
rs_ids.rs

1use serde::{Deserialize, Serialize};
2
3
4#[derive(Debug, Serialize, strum_macros::AsRefStr)]
5pub enum RsIdsError {
6    InvalidId(),
7	NotAMediaId(String),
8    NoMediaIdRequired(Box<RsIds>)
9
10}
11
12// region:    --- Error Boilerplate
13
14impl core::fmt::Display for RsIdsError {
15	fn fmt(
16		&self,
17		fmt: &mut core::fmt::Formatter,
18	) -> core::result::Result<(), core::fmt::Error> {
19		write!(fmt, "{self:?}")
20	}
21}
22
23impl std::error::Error for RsIdsError {}
24
25#[derive(Debug, Clone, Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq, Default)]
26#[serde(rename_all = "camelCase")]
27pub struct RsIds {
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub redseat: Option<String>,
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub trakt: Option<u64>,
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub slug: Option<String>,
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub tvdb: Option<u64>,
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub imdb: Option<String>,
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub tmdb: Option<u64>,
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub tvrage: Option<u64>,
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub other_ids: Option<Vec<String>>,
44}
45
46
47
48impl RsIds {
49    pub fn try_add(&mut self, value: String) -> Result<(), RsIdsError> {
50        if !Self::is_id(&value) {
51            return Err(RsIdsError::NotAMediaId(value))
52        }
53        let elements = value.split(":").collect::<Vec<_>>();
54        let source = elements.first().ok_or(RsIdsError::InvalidId())?;
55        let id = elements.get(1).ok_or(RsIdsError::InvalidId())?;
56
57        if *source == "redseat" {
58            self.redseat = Some(id.to_string());
59            Ok(())
60        } else if *source == "imdb" {
61            self.imdb = Some(id.to_string());
62            Ok(())
63        } else if *source == "trakt" {
64            let id: u64 = id.parse().map_err(|_| RsIdsError::NotAMediaId(value))?;
65            self.trakt = Some(id);
66            Ok(())
67        } else if *source == "tmdb" {
68            let id: u64 = id.parse().map_err(|_| RsIdsError::NotAMediaId(value))?;
69            self.tmdb = Some(id);
70            Ok(())
71        } else if *source == "tvdb" {
72            let id: u64 = id.parse().map_err(|_| RsIdsError::NotAMediaId(value))?;
73            self.tvdb = Some(id);
74            Ok(())
75        } else if *source == "tvrage" {
76            let id: u64 = id.parse().map_err(|_| RsIdsError::NotAMediaId(value))?;
77            self.tvrage = Some(id);
78            Ok(())
79        } else{
80            Err(RsIdsError::NotAMediaId(value))
81        }  
82    }
83
84    pub fn into_best(self) -> Option<String> {
85        self.as_redseat().or(self.into_best_external())
86    }
87
88    pub fn into_best_external(self) -> Option<String> {
89        self.as_trakt().or(self.as_imdb()).or(self.as_tmdb()).or(self.as_tvdb())
90    }
91    pub fn as_best_external(&self) -> Option<String> {
92        self.as_trakt().or(self.as_imdb()).or(self.as_tmdb()).or(self.as_tvdb())
93    }
94
95
96    pub fn into_best_external_or_local(self) -> Option<String> {
97        self.as_best_external().or(self.as_redseat())
98    }
99
100
101    pub fn from_imdb(imdb: String) -> Self {
102        Self {
103            imdb: Some(imdb),
104            ..Default::default()
105        }
106    }
107    pub fn as_imdb(&self) -> Option<String> {
108        self.imdb.as_ref().map(|i| format!("imdb:{}", i))
109    }
110    
111    pub fn from_trakt(trakt: u64) -> Self {
112        Self {
113            trakt: Some(trakt),
114            ..Default::default()
115        }
116    }
117    pub fn as_trakt(&self) -> Option<String> {
118        self.trakt.map(|i| format!("trakt:{}", i))
119    }
120    pub fn as_id_for_trakt(&self) -> Option<String> {
121        if let Some(trakt) = self.trakt {
122            Some(trakt.to_string())
123        } else { self.imdb.as_ref().map(|imdb| imdb.to_string()) }
124    }
125
126    pub fn from_tvdb(tvdb: u64) -> Self {
127        Self {
128            tvdb: Some(tvdb),
129            ..Default::default()
130        }
131    }
132    pub fn as_tvdb(&self) -> Option<String> {
133        self.tvdb.map(|i| format!("tvdb:{}", i))
134    }
135    pub fn try_tvdb(self) -> Result<u64, RsIdsError> {
136        self.tvdb.ok_or(RsIdsError::NoMediaIdRequired(Box::new(self.clone())))
137    }
138
139    pub fn from_tmdb(tmdb: u64) -> Self {
140        Self {
141            tmdb: Some(tmdb),
142            ..Default::default()
143        }
144    }
145    pub fn as_tmdb(&self) -> Option<String> {
146        self.tmdb.map(|i| format!("tmdb:{}", i))
147    }
148    pub fn try_tmdb(self) -> Result<u64, RsIdsError> {
149        self.tmdb.ok_or(RsIdsError::NoMediaIdRequired(Box::new(self.clone())))
150    }
151
152    pub fn from_redseat(redseat: String) -> Self {
153        Self {
154            redseat: Some(redseat),
155            ..Default::default()
156        }
157    }
158    pub fn as_redseat(&self) -> Option<String> {
159        self.redseat.as_ref().map(|i| format!("redseat:{}", i))
160    }
161
162    pub fn as_id(&self) -> Result<String, RsIdsError> {
163        if let Some(imdb) = &self.imdb {
164            Ok(format!("imdb:{}", imdb))
165        } else if let Some(trakt) = &self.trakt {
166            Ok(format!("trakt:{}", trakt))
167        } else if let Some(tmdb) = &self.tmdb {
168            Ok(format!("tmdb:{}", tmdb))
169        } else if let Some(tvdb) = &self.tvdb {
170            Ok(format!("tmdb:{}", tvdb))
171        } else {
172            Err(RsIdsError::NoMediaIdRequired(Box::new(self.clone())))
173        }
174    }   
175
176    /// check if the provided id need parsing like "trakt:xxxxx" and is not directly the local id from this server
177    pub fn is_id(id: &str) -> bool {
178        id.contains(":") && id.split(":").count() == 2
179    }
180}
181
182impl TryFrom<Vec<String>> for RsIds {
183    type Error = RsIdsError;
184    
185    fn try_from(values: Vec<String>) -> Result<Self, RsIdsError> {
186        let mut ids = Self::default();
187        for value in values {
188            ids.try_add(value)?;
189        }
190        Ok(ids)
191    }
192}
193
194impl TryFrom<String> for RsIds {
195    type Error = RsIdsError;
196    fn try_from(value: String) -> Result<Self, RsIdsError> {
197        let mut id = RsIds::default();
198        id.try_add(value)?;
199        Ok(id)
200    }
201}
202
203impl From<RsIds> for Vec<String> {
204    
205    fn from(value: RsIds) -> Self {
206        let mut ids = vec![];
207        if let Some(id) = value.as_redseat() {
208            ids.push(id)
209        }
210        if let Some(id) = value.as_imdb() {
211            ids.push(id)
212        }
213        if let Some(id) = value.as_tmdb() {
214            ids.push(id.to_string())
215        }
216        if let Some(id) = value.as_trakt() {
217            ids.push(id.to_string())
218        }
219        if let Some(id) = value.as_tvdb() {
220            ids.push(id.to_string())
221        }
222        ids
223    }
224}
225
226
227#[cfg(feature = "rusqlite")]
228pub mod external_images_rusqlite {
229    use rusqlite::{types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef}, ToSql};
230
231    use super::RsIds;
232
233    impl FromSql for RsIds {
234        fn column_result(value: ValueRef) -> FromSqlResult<Self> {
235            String::column_result(value).and_then(|as_string| {
236                let r = serde_json::from_str(&as_string).map_err(|_| FromSqlError::InvalidType);
237                r
238            })
239        }
240    }
241    impl ToSql for RsIds {
242        fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
243            let r = serde_json::to_string(self).map_err(|_| FromSqlError::InvalidType)?;
244            Ok(ToSqlOutput::from(r))
245        }
246    }
247
248
249    
250  
251}
252
253
254
255#[cfg(test)]
256mod tests {
257
258    use self::RsIdsError;
259
260    use super::*;
261
262    #[test]
263    fn test_parse() -> Result<(), RsIdsError> {
264        let toparse = String::from("trakt:905982");
265        let parsed: Result<RsIds, _> = toparse.try_into();
266        assert!(parsed.is_ok() == true);
267        assert!(parsed.unwrap().trakt == Some(905982));
268        Ok(())
269    }
270}