1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
use crate::models::identifiers::OpenLibraryIdentifer;
use crate::models::{Link, LinkName, OpenLibraryModel, Resource};
use crate::OpenLibraryError;
use chrono::NaiveDateTime;
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::borrow::Cow;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fmt;
use std::fmt::Display;
use std::str::FromStr;
use url::Url;
#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Author {
pub key: String,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub text: Vec<String>,
#[serde(rename(deserialize = "type"))]
pub r#type: String,
pub name: String,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub alternate_names: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub birth_date: Option<String>,
pub top_work: String,
pub work_count: i32,
pub top_subjects: Vec<String>,
pub _version_: u64,
}
#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct AuthorLink {
#[serde(rename = "type")]
#[serde(with = "crate::format::keyed_value")]
pub author_type: AuthorType,
#[serde(with = "crate::format::keyed_value")]
pub author: Resource,
}
#[derive(Debug, Eq, PartialEq)]
pub enum AuthorType {
AuthorRole,
}
impl Display for AuthorType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AuthorType::AuthorRole => write!(f, "author_role"),
}
}
}
impl FromStr for AuthorType {
type Err = OpenLibraryError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"author_role" => Ok(Self::AuthorRole),
_ => Err(OpenLibraryError::ParsingError {
reason: format!("Unable to parse string ({}) into an Author Type", &value),
}),
}
}
}
impl<'de> Deserialize<'de> for AuthorType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value: String = Deserialize::deserialize(deserializer).map_err(D::Error::custom)?;
let chunks = value
.split('/')
.filter(|str| !str.is_empty())
.collect::<Vec<&str>>();
match chunks.get(0) {
Some(&"type") => match chunks.get(1) {
Some(value) => Ok(AuthorType::from_str(*value).map_err(D::Error::custom)?),
None => Err(D::Error::custom("No Author Type was provided!")),
},
_ => Err(D::Error::custom(format!(
"Invalid format for Author Type: {}",
&value
))),
}
}
}
impl Serialize for AuthorType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.to_string().as_str())
}
}
#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct AuthorDetails {
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub source_records: Vec<String>,
pub key: Resource,
#[serde(default)]
#[serde(with = "crate::format::value")]
#[serde(skip_serializing_if = "Option::is_none")]
pub bio: Option<String>,
pub photos: Vec<i32>,
pub birth_date: String,
pub personal_name: String,
pub remote_ids: HashMap<String, String>,
pub entity_type: Option<String>,
pub links: Vec<Link>,
pub name: String,
pub alternate_names: Vec<String>,
pub wikipedia: Option<Url>,
pub latest_revision: u16,
pub revision: u16,
#[serde(with = "crate::format::value")]
pub created: Option<NaiveDateTime>,
#[serde(with = "crate::format::value")]
pub last_modified: Option<NaiveDateTime>,
}
impl OpenLibraryModel for AuthorDetails {}
#[derive(Deserialize, Debug, Eq, PartialEq, Serialize)]
pub struct AuthorWorksRequest {
pub identifier: OpenLibraryIdentifer,
pub limit: Option<u32>,
pub offset: Option<u32>,
}
impl TryFrom<OpenLibraryIdentifer> for AuthorWorksRequest {
type Error = OpenLibraryError;
fn try_from(identifier: OpenLibraryIdentifer) -> Result<Self, OpenLibraryError> {
Ok(Self {
identifier,
limit: None,
offset: None,
})
}
}
impl TryFrom<Url> for AuthorWorksRequest {
type Error = OpenLibraryError;
fn try_from(value: Url) -> Result<Self, Self::Error> {
let path_segments = value
.path_segments()
.ok_or(OpenLibraryError::ParsingError {
reason: "Invalid URL supplied, no path segments found".to_string(),
})?
.collect::<Vec<&str>>();
let path_index = path_segments.iter().position(|x| *x == "authors").ok_or(
OpenLibraryError::ParsingError {
reason: "Invalid URL supplied, unable to determine author identifier".to_string(),
},
)?;
let query_parameters = value
.query_pairs()
.collect::<HashMap<Cow<'_, str>, Cow<'_, str>>>();
let result = *path_segments
.get(path_index + 1)
.ok_or(OpenLibraryError::ParsingError {
reason: "Unable to find an author identifier within the URL path".to_string(),
})?;
let limit = match query_parameters.get("limit") {
Some(x) => Some(x.clone().into_owned().parse::<u32>().map_err(|e| {
OpenLibraryError::ParsingError {
reason: e.to_string(),
}
})?),
None => None,
};
let offset = match query_parameters.get("offset") {
Some(z) => Some(z.clone().into_owned().parse::<u32>().map_err(|e| {
OpenLibraryError::ParsingError {
reason: e.to_string(),
}
})?),
None => None,
};
Ok(Self {
identifier: OpenLibraryIdentifer::from_str(result)?,
limit: limit,
offset: offset,
})
}
}
#[derive(Deserialize, Debug, Eq, PartialEq, Serialize)]
pub struct AuthorWorksResponse {
#[serde(default)]
#[serde(skip_serializing_if = "HashMap::is_empty")]
pub links: HashMap<LinkName, String>,
pub size: u32,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub entries: Vec<AuthorWorks>,
}
impl OpenLibraryModel for AuthorWorksResponse {}
#[derive(Deserialize, Debug, Eq, PartialEq, Serialize)]
pub struct AuthorWorks {
#[serde(default)]
#[serde(deserialize_with = "string_or_value")]
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub title: String,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub covers: Vec<i32>,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub subject_places: Vec<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub subjects: Vec<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub subject_people: Vec<String>,
pub key: Resource,
pub authors: Vec<AuthorLink>,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub subject_times: Vec<String>,
pub latest_revision: u32,
pub revision: u32,
}
impl OpenLibraryModel for AuthorWorks {}
#[derive(Debug, Deserialize, Serialize)]
pub struct AuthorResponse {
#[serde(rename = "numFound")]
pub num_found: i32,
pub start: i32,
#[serde(rename = "numFoundExact")]
pub num_found_exact: bool,
pub docs: Vec<Author>,
}
impl OpenLibraryModel for AuthorResponse {}
fn string_or_value<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Value<T> {
value: T,
}
#[derive(Deserialize)]
#[serde(untagged)]
enum StrOrValue {
Str(String),
Value(Value<String>),
}
Ok(match StrOrValue::deserialize(deserializer)? {
StrOrValue::Str(v) => Some(v),
StrOrValue::Value(v) => Some(v.value),
})
}