1use crate::models::{Link, OpenLibraryModel, OpenLibraryResource};
2use crate::OpenLibraryError;
3use serde::de::Error;
4use serde::{Deserialize, Deserializer, Serialize};
5use std::collections::HashMap;
6use std::fmt::{Display, Formatter};
7use std::str::FromStr;
8use url::Url;
9
10#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
11pub struct Book {
12 #[serde(default)]
13 #[serde(deserialize_with = "url_or_list")]
14 #[serde(skip_serializing_if = "Vec::is_empty")]
15 pub url: Vec<Url>,
16 pub key: OpenLibraryResource,
17 pub title: String,
18 #[serde(skip_serializing_if = "Option::is_none")]
19 pub subtitle: Option<String>,
20 pub pagination: Option<String>,
21 pub by_statement: Option<String>,
22 pub notes: Option<String>,
23 #[serde(default)]
24 #[serde(skip_serializing_if = "Vec::is_empty")]
25 pub authors: Vec<Author>,
26 #[serde(default)]
27 pub identifiers: HashMap<BookIdentifierKey, Vec<String>>,
28 #[serde(default)]
29 #[serde(skip_serializing_if = "Classifications::is_default")]
30 pub classifications: Classifications,
31 #[serde(default)]
32 #[serde(deserialize_with = "strings_or_entities")]
33 #[serde(skip_serializing_if = "Vec::is_empty")]
34 pub subjects: Vec<String>,
35 #[serde(default)]
36 #[serde(skip_serializing_if = "Vec::is_empty")]
37 pub subject_places: Vec<Entity>,
38 #[serde(default)]
39 #[serde(skip_serializing_if = "Vec::is_empty")]
40 pub subject_people: Vec<Entity>,
41 #[serde(default)]
42 #[serde(skip_serializing_if = "Vec::is_empty")]
43 pub subject_times: Vec<Entity>,
44 #[serde(default)]
45 #[serde(deserialize_with = "strings_or_entities")]
46 #[serde(skip_serializing_if = "Vec::is_empty")]
47 pub publishers: Vec<String>,
48 #[serde(default)]
49 #[serde(deserialize_with = "strings_or_entities")]
50 #[serde(skip_serializing_if = "Vec::is_empty")]
51 pub publish_places: Vec<String>,
52 pub publish_date: String,
53 #[serde(default)]
54 #[serde(skip_serializing_if = "Vec::is_empty")]
55 pub excerpts: Vec<Excerpt>,
56 #[serde(default)]
57 #[serde(skip_serializing_if = "Vec::is_empty")]
58 pub links: Vec<Link>,
59 #[serde(default)]
60 #[serde(rename = "covers")]
61 pub cover_images: Vec<u32>,
62 #[serde(default)]
63 #[serde(skip_serializing_if = "Vec::is_empty")]
64 pub ebooks: Vec<ElectronicBook>,
65 pub number_of_pages: Option<u32>,
66 #[serde(skip_serializing_if = "Option::is_none")]
67 pub weight: Option<String>,
68}
69
70impl OpenLibraryModel for Book {}
71
72#[derive(Clone, Debug, Eq, Hash, PartialEq)]
73pub enum BibliographyKey {
74 ISBN(String),
75 LCCN(String),
76 OCLC(String),
77 OLID(String),
78}
79
80impl BibliographyKey {
81 pub fn from_tuple((key, value): (String, String)) -> Result<Self, OpenLibraryError> {
82 match key.as_str() {
83 "ISBN" => Ok(BibliographyKey::ISBN(value)),
84 "LCCN" => Ok(BibliographyKey::LCCN(value)),
85 "OCLC" => Ok(BibliographyKey::OCLC(value)),
86 "OLID" => Ok(BibliographyKey::OLID(value)),
87 _ => Err(OpenLibraryError::ParsingError {
88 reason: format!(
89 "Unable to determine bibliography key from specific value ({})",
90 value
91 ),
92 }),
93 }
94 }
95}
96
97impl Display for BibliographyKey {
98 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
99 match self {
100 BibliographyKey::ISBN(value) => write!(f, "ISBN:{}", value)?,
101 BibliographyKey::LCCN(value) => write!(f, "LCCN:{}", value)?,
102 BibliographyKey::OCLC(value) => write!(f, "OCLC:{}", value)?,
103 BibliographyKey::OLID(value) => write!(f, "OLID:{}", value)?,
104 }
105 Ok(())
106 }
107}
108
109impl<'de> serde::Deserialize<'de> for BibliographyKey {
110 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
111 where
112 D: Deserializer<'de>,
113 {
114 let value: String =
115 serde::Deserialize::deserialize(deserializer).map_err(D::Error::custom)?;
116 let chunks: Vec<&str> = value.split(':').collect();
117
118 if chunks.len() != 2 {
119 return Err(D::Error::custom("The specified value {} has improper form"));
120 }
121
122 let key = match chunks.get(0) {
123 Some(value) => Ok(*value),
124 None => Err(D::Error::custom(format!(
125 "Supplied identifier string has improper format {}",
126 &value
127 ))),
128 }?
129 .to_string();
130
131 let value = match chunks.get(1) {
132 Some(string) => Ok(*string),
133 None => Err(D::Error::custom(format!(
134 "Supplied identifier string has improper format {}",
135 &value
136 ))),
137 }?
138 .to_string();
139
140 BibliographyKey::from_tuple((key, value)).map_err(D::Error::custom)
141 }
142}
143
144impl serde::Serialize for BibliographyKey {
145 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
146 where
147 S: serde::Serializer,
148 {
149 serializer.serialize_str(format!("{}", self).as_str())
150 }
151}
152
153#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
154pub enum BookIdentifierKey {
155 #[serde(alias = "isbn_10")]
156 InternationalStandard10, #[serde(alias = "isbn_13")]
158 InternationalStandard13, #[serde(alias = "lccn")]
160 LibraryOfCongress, #[serde(alias = "oclc")]
162 OhioCollegeLibraryCenter, #[serde(alias = "goodreads")]
164 GoodReads,
165 #[serde(alias = "openlibrary")]
166 OpenLibrary,
167 #[serde(alias = "librarything")]
168 LibraryThing,
169 #[serde(alias = "project_gutenberg")]
170 ProjectGutenberg,
171 #[serde(alias = "wikidata")]
172 WikiData,
173}
174
175impl BookIdentifierKey {
176 pub fn from_isbn(value: &str) -> Result<Self, OpenLibraryError> {
177 match value.len() {
178 10 => Ok(BookIdentifierKey::InternationalStandard10),
179 13 => Ok(BookIdentifierKey::InternationalStandard13),
180 _ => Err(OpenLibraryError::ParsingError {
181 reason: format!("Invalid length ({}) for ISBN", value.len()),
182 }),
183 }
184 }
185}
186
187impl Display for BookIdentifierKey {
188 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
189 match self {
190 BookIdentifierKey::InternationalStandard10 => Ok(write!(f, "isbn_10")?),
191 BookIdentifierKey::InternationalStandard13 => Ok(write!(f, "isbn_13")?),
192 BookIdentifierKey::LibraryOfCongress => Ok(write!(f, "lccn")?),
193 BookIdentifierKey::OhioCollegeLibraryCenter => Ok(write!(f, "oclc")?),
194 BookIdentifierKey::GoodReads => Ok(write!(f, "goodreads")?),
195 BookIdentifierKey::OpenLibrary => Ok(write!(f, "openlibrary")?),
196 BookIdentifierKey::LibraryThing => Ok(write!(f, "librarything")?),
197 BookIdentifierKey::ProjectGutenberg => Ok(write!(f, "project_gutenberg")?),
198 BookIdentifierKey::WikiData => Ok(write!(f, "wikidata")?),
199 }
200 }
201}
202
203impl FromStr for BookIdentifierKey {
204 type Err = OpenLibraryError;
205
206 fn from_str(s: &str) -> Result<Self, Self::Err> {
207 match s {
208 "isbn_10" => Ok(BookIdentifierKey::InternationalStandard10),
209 "isbn_13" => Ok(BookIdentifierKey::InternationalStandard13),
210 "lccn" => Ok(BookIdentifierKey::LibraryOfCongress),
211 "oclc" => Ok(BookIdentifierKey::OhioCollegeLibraryCenter),
212 "goodreads" => Ok(BookIdentifierKey::GoodReads),
213 "openlibrary" => Ok(BookIdentifierKey::OpenLibrary),
214 "librarything" => Ok(BookIdentifierKey::LibraryThing),
215 "project_gutenberg" => Ok(BookIdentifierKey::ProjectGutenberg),
216 "wikidata" => Ok(BookIdentifierKey::WikiData),
217 _ => Err(OpenLibraryError::ParsingError {
218 reason: format!(
219 "Unable to parse supplied value ({}) into a book identifier!",
220 s
221 ),
222 }),
223 }
224 }
225}
226
227#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
230pub struct Author {
231 #[serde(skip_serializing_if = "Option::is_none")]
232 pub key: Option<OpenLibraryResource>,
233 #[serde(skip_serializing_if = "Option::is_none")]
234 pub name: Option<String>,
235 #[serde(skip_serializing_if = "Option::is_none")]
236 pub url: Option<String>,
237}
238
239#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
240pub struct Entity {
241 #[serde(skip_serializing_if = "String::is_empty")]
242 pub name: String,
243 #[serde(skip_serializing_if = "Option::is_none")]
244 pub url: Option<String>,
245}
246
247#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
248pub struct Classifications {
249 #[serde(default)]
250 #[serde(rename(deserialize = "dewey_decimal_class"))]
251 pub dewey_decimal: Vec<String>,
252 #[serde(default)]
253 #[serde(rename(deserialize = "lc_classifications"))]
254 pub library_of_congress: Vec<String>,
255}
256
257impl Classifications {
258 pub fn is_default(&self) -> bool {
259 self.dewey_decimal.is_empty() && self.library_of_congress.is_empty()
260 }
261}
262
263#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
264pub struct ElectronicBook {
265 preview_url: String,
266 availability: String, }
269
270#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
271pub struct Excerpt {
272 comment: String,
273 text: String,
274}
275
276fn strings_or_entities<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
288where
289 D: Deserializer<'de>,
290{
291 #[derive(Deserialize)]
292 #[serde(untagged)]
293 enum StrOrEntity {
294 Str(Vec<String>),
295 Entity(Vec<Entity>),
296 }
297
298 Ok(match StrOrEntity::deserialize(deserializer)? {
299 StrOrEntity::Str(v) => v,
300 StrOrEntity::Entity(v) => v.into_iter().map(|x| x.name).collect(),
301 })
302}
303
304fn url_or_list<'de, D>(deserializer: D) -> Result<Vec<Url>, D::Error>
316where
317 D: Deserializer<'de>,
318{
319 #[derive(Deserialize)]
320 #[serde(untagged)]
321 enum UrlOrVector {
322 Url(Url),
323 Vector(Vec<Url>),
324 }
325
326 Ok(match UrlOrVector::deserialize(deserializer)? {
327 UrlOrVector::Url(value) => vec![value],
328 UrlOrVector::Vector(values) => values,
329 })
330}