Skip to main content

rs_plugin_common_interfaces/domain/
book.rs

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