rs_plugin_common_interfaces/domain/
rs_ids.rs1use 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
12impl 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 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}