Skip to main content

rs_plugin_common_interfaces/domain/
book.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4use crate::domain::{
5    media::MediaItemReference, other_ids::OtherIds, person::Person, rs_ids::{ApplyRsIds, RsIds}, tag::Tag
6};
7
8#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
9#[serde(rename_all = "camelCase")]
10pub struct Book {
11    #[serde(default)]
12    pub id: String,
13    pub name: String,
14    #[serde(skip_serializing_if = "Option::is_none")]
15    #[serde(rename = "type")]
16    pub kind: Option<String>,
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub serie_ref: Option<String>,
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub volume: Option<f64>,
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub chapter: Option<f64>,
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub year: Option<u16>,
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub airdate: Option<i64>,
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub overview: Option<String>,
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub pages: Option<u32>,
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub params: Option<Value>,
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub lang: Option<String>,
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub original: Option<String>,
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub isbn13: Option<String>,
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub openlibrary_edition_id: Option<String>,
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub openlibrary_work_id: Option<String>,
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub google_books_volume_id: Option<String>,
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub asin: Option<String>,
47    pub otherids: Option<OtherIds>,
48    #[serde(default)]
49    pub modified: u64,
50    #[serde(default)]
51    pub added: u64,
52}
53
54impl From<Book> for RsIds {
55    fn from(value: Book) -> Self {
56
57        let mut ids = RsIds {
58            isbn13: value.isbn13,
59            openlibrary_edition_id: value.openlibrary_edition_id,
60            openlibrary_work_id: value.openlibrary_work_id,
61            google_books_volume_id: value.google_books_volume_id,
62            asin: value.asin,
63            other_ids: value.otherids,
64            volume: value.volume,
65            chapter: value.chapter,
66            ..Default::default()
67        };
68
69        if ids.try_add(value.id.clone()).is_err() {
70            ids.redseat = Some(value.id);
71        }
72        println!("Extracted ids from book: {:?}", ids);
73        ids
74    }
75}
76
77impl ApplyRsIds for Book {
78    fn apply_rs_ids(&mut self, ids: &RsIds) {
79        if let Some(redseat) = ids.redseat.as_ref() {
80            self.id = redseat.clone();
81        }
82        if let Some(isbn13) = ids.isbn13.as_ref() {
83            self.isbn13 = Some(isbn13.clone());
84        }
85        if let Some(openlibrary_edition_id) = ids.openlibrary_edition_id.as_ref() {
86            self.openlibrary_edition_id = Some(openlibrary_edition_id.clone());
87        }
88        if let Some(openlibrary_work_id) = ids.openlibrary_work_id.as_ref() {
89            self.openlibrary_work_id = Some(openlibrary_work_id.clone());
90        }
91        if let Some(google_books_volume_id) = ids.google_books_volume_id.as_ref() {
92            self.google_books_volume_id = Some(google_books_volume_id.clone());
93        }
94        if let Some(asin) = ids.asin.as_ref() {
95            self.asin = Some(asin.clone());
96        }
97        if let Some(other_ids) = ids.other_ids.as_ref() {
98            self.otherids = Some(other_ids.clone());
99        }
100        if let Some(volume) = ids.volume {
101            self.volume = Some(volume);
102        }
103        if let Some(chapter) = ids.chapter {
104            self.chapter = Some(chapter);
105        }
106    }
107}
108
109#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
110#[serde(rename_all = "camelCase")]
111pub struct BookForUpdate {
112    pub name: Option<String>,
113    #[serde(rename = "type")]
114    pub kind: Option<String>,
115    pub serie_ref: Option<String>,
116    pub volume: Option<f64>,
117    pub chapter: Option<f64>,
118    pub year: Option<u16>,
119    pub airdate: Option<i64>,
120    pub overview: Option<String>,
121    pub pages: Option<u32>,
122    pub params: Option<Value>,
123    pub lang: Option<String>,
124    pub original: Option<String>,
125    pub isbn13: Option<String>,
126    pub openlibrary_edition_id: Option<String>,
127    pub openlibrary_work_id: Option<String>,
128    pub google_books_volume_id: Option<String>,
129    pub asin: Option<String>,
130    pub otherids: Option<OtherIds>,
131
132
133    pub add_tags: Option<Vec<MediaItemReference>>,
134    pub remove_tags: Option<Vec<String>>,
135    pub tags_lookup: Option<Vec<Tag>>,
136
137    pub add_people: Option<Vec<MediaItemReference>>,
138    pub remove_people: Option<Vec<String>>,
139    pub people_lookup: Option<Vec<Person>>,
140}
141
142impl BookForUpdate {
143    pub fn has_update(&self) -> bool {
144        self != &BookForUpdate::default()
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::Book;
151    use crate::domain::{
152        other_ids::OtherIds,
153        rs_ids::{ApplyRsIds, RsIds},
154    };
155    use serde_json::json;
156
157    #[test]
158    fn book_otherids_serializes_as_array_and_rejects_string() {
159        let book = Book {
160            id: "book-1".to_string(),
161            name: "Book 1".to_string(),
162            otherids: Some(OtherIds(vec!["goodreads:321".to_string()])),
163            ..Default::default()
164        };
165        let value = serde_json::to_value(&book).unwrap();
166        assert_eq!(value.get("otherids"), Some(&json!(["goodreads:321"])));
167
168        let parsed: Book = serde_json::from_value(json!({
169            "id": "book-1",
170            "name": "Book 1",
171            "otherids": ["custom:1"]
172        }))
173        .unwrap();
174        assert_eq!(
175            parsed.otherids,
176            Some(OtherIds(vec!["custom:1".to_string()]))
177        );
178
179        let invalid = serde_json::from_value::<Book>(json!({
180            "id": "book-1",
181            "name": "Book 1",
182            "otherids": "custom:1"
183        }));
184        assert!(invalid.is_err());
185    }
186
187    #[test]
188    fn book_apply_rs_ids_updates_only_present_values() {
189        let mut book = Book {
190            id: "book-old".to_string(),
191            name: "Book 1".to_string(),
192            openlibrary_work_id: Some("olw-old".to_string()),
193            chapter: Some(7.0),
194            ..Default::default()
195        };
196        let ids = RsIds {
197            redseat: Some("book-new".to_string()),
198            isbn13: Some("9783161484100".to_string()),
199            openlibrary_edition_id: Some("ole-new".to_string()),
200            google_books_volume_id: Some("gb-1".to_string()),
201            asin: Some("B00TEST".to_string()),
202            other_ids: Some(OtherIds(vec!["goodreads:42".to_string()])),
203            volume: Some(3.0),
204            ..Default::default()
205        };
206
207        book.apply_rs_ids(&ids);
208
209        assert_eq!(book.id, "book-new");
210        assert_eq!(book.isbn13.as_deref(), Some("9783161484100"));
211        assert_eq!(book.openlibrary_edition_id.as_deref(), Some("ole-new"));
212        assert_eq!(book.openlibrary_work_id.as_deref(), Some("olw-old"));
213        assert_eq!(book.google_books_volume_id.as_deref(), Some("gb-1"));
214        assert_eq!(book.asin.as_deref(), Some("B00TEST"));
215        assert_eq!(
216            book.otherids,
217            Some(OtherIds(vec!["goodreads:42".to_string()]))
218        );
219        assert_eq!(book.volume, Some(3.0));
220        assert_eq!(book.chapter, Some(7.0));
221    }
222}