1use crate::query::facet::Facet;
2use crate::query::facet::FacetCount;
3use crate::query::Visibility;
4use crate::response::work::*;
5use serde::de::Deserializer;
6use serde::{Deserialize, Serialize};
7use serde_json::{from_value, Value};
8use std::collections::HashMap;
9use std::fmt;
10
11pub mod work;
13
14pub use crate::response::work::{Work, WorkList};
15
16#[derive(Debug, Clone, Serialize)]
18#[serde(rename_all = "kebab-case")]
19pub struct Response {
20 pub status: String,
22 pub message_type: MessageType,
24 #[serde(default = "default_msg_version")]
26 pub message_version: String,
27 pub message: Option<Message>,
29}
30
31fn default_msg_version() -> String {
33 "1.0.0".to_string()
34}
35
36macro_rules! impl_msg_helper {
38 (single: $($name:ident -> $ident:ident,)*) => {
39 $(
40 pub fn $name(&self) -> bool {
42 if let Some(Message::$ident(_)) = &self.message {
43 true
44 } else {
45 false
46 }
47 }
48 )+
49 };
50}
51
52impl Response {
53 impl_msg_helper!(single:
54 is_work_ageny -> WorkAgency,
55 is_funder -> Funder,
56 is_prefix -> Prefix,
57 is_work -> Work,
58 is_type -> Type,
59 is_journal -> Journal,
60 is_member -> Member,
61 is_validation_failure -> ValidationFailure,
62 is_type_list -> TypeList,
63 is_work_list -> WorkList,
64 is_member_list -> MemberList,
65 is_journal_list -> JournalList,
66 is_funder_list -> FunderList,
67 );
68
69 pub fn is_route_not_found(&self) -> bool {
71 match &self.message {
72 Some(Message::RouteNotFound) => true,
73 _ => false,
74 }
75 }
76}
77
78impl<'de> Deserialize<'de> for Response {
79 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
80 where
81 D: Deserializer<'de>,
82 {
83 #[derive(Deserialize)]
84 #[serde(rename_all = "kebab-case")]
85 struct ResponseFragment {
86 status: String,
87 message_type: MessageType,
88 #[serde(default = "default_msg_version")]
89 message_version: String,
90 message: Option<Value>,
91 }
92
93 #[derive(Deserialize)]
94 #[serde(rename_all = "kebab-case")]
95 struct ListResp {
96 #[serde(default)]
97 facets: FacetMap,
98 next_cursor: Option<String>,
99 total_results: usize,
100 items_per_page: Option<usize>,
101 query: Option<QueryResponse>,
102 items: Value,
103 }
104
105 let fragment = ResponseFragment::deserialize(deserializer)?;
106
107 macro_rules! msg_arm {
108 ($ident:ident, $value:expr) => {{
109 Message::$ident(
110 ::serde_json::from_value($value).map_err(::serde::de::Error::custom)?,
111 )
112 }};
113 ($ident:ident, $value:expr, $ty:ty) => {{
114 let list_resp: ListResp =
115 ::serde_json::from_value($value).map_err(::serde::de::Error::custom)?;
116 let items: Vec<$ty> = ::serde_json::from_value(list_resp.items)
117 .map_err(::serde::de::Error::custom)?;
118 Message::$ident($ident {
119 facets: list_resp.facets,
120 total_results: list_resp.total_results,
121 items_per_page: list_resp.items_per_page,
122 query: list_resp.query,
123 items,
124 })
125 }};
126 }
127
128 fn work_list(msg: Value) -> Result<Message, serde_json::Error> {
129 let list_resp: ListResp = ::serde_json::from_value(msg)?;
130 let items: Vec<Work> = ::serde_json::from_value(list_resp.items)?;
131
132 Ok(Message::WorkList(WorkList {
133 facets: list_resp.facets,
134 total_results: list_resp.total_results,
135 items_per_page: list_resp.items_per_page,
136 query: list_resp.query,
137 items,
138 next_cursor: list_resp.next_cursor,
139 }))
140 }
141
142 let message = match fragment.message {
143 Some(msg) => Some(match &fragment.message_type {
144 MessageType::ValidationFailure => msg_arm!(ValidationFailure, msg),
145 MessageType::WorkAgency => msg_arm!(WorkAgency, msg),
146 MessageType::Prefix => msg_arm!(Prefix, msg),
147 MessageType::Type => msg_arm!(Type, msg),
148 MessageType::TypeList => msg_arm!(TypeList, msg, CrossrefType),
149 MessageType::Work => msg_arm!(Work, msg),
150 MessageType::WorkList => work_list(msg).map_err(::serde::de::Error::custom)?,
151 MessageType::Member => msg_arm!(Member, msg),
152 MessageType::MemberList => msg_arm!(MemberList, msg, Member),
153 MessageType::Journal => msg_arm!(Journal, msg),
154 MessageType::JournalList => msg_arm!(JournalList, msg, Journal),
155 MessageType::Funder => msg_arm!(Funder, msg),
156 MessageType::FunderList => msg_arm!(FunderList, msg, Funder),
157 MessageType::RouteNotFound => Message::RouteNotFound,
158 }),
159 _ => None,
160 };
161 Ok(Response {
162 status: fragment.status,
163 message_type: fragment.message_type,
164 message_version: fragment.message_version,
165 message,
166 })
167 }
168}
169
170macro_rules! impl_list_response {
171 ($($name:ident<$ty:ty>,)*) => {
172 $(
173 #[derive(Debug, Clone, Deserialize, Serialize)]
174 #[serde(rename_all = "kebab-case")]
175 #[allow(missing_docs)]
176 pub struct $name {
177 #[serde(default)]
179 pub facets: FacetMap,
180 pub total_results: usize,
182 pub items_per_page: Option<usize>,
184 pub query: Option<QueryResponse>,
186 pub items: Vec<$ty>,
188 }
189 )+
190 };
191}
192impl_list_response!(
193 TypeList<CrossrefType>,
194 MemberList<Member>,
195 JournalList<Journal>,
196 FunderList<Funder>,
197);
198
199#[derive(Debug, Clone, Deserialize, Serialize)]
201#[serde(untagged)]
202pub enum Message {
203 ValidationFailure(Vec<Failure>),
205 RouteNotFound,
207 WorkAgency(WorkAgency),
209 Prefix(Prefix),
211 Type(CrossrefType),
213 TypeList(TypeList),
215 Work(Box<Work>),
217 WorkList(WorkList),
219 Member(Box<Member>),
221 MemberList(MemberList),
223 Journal(Box<Journal>),
225 JournalList(JournalList),
227 Funder(Box<Funder>),
229 FunderList(FunderList),
231}
232
233#[derive(Debug, Clone, Deserialize, Serialize)]
234#[allow(missing_docs)]
235pub struct CrossrefType {
236 pub id: String,
237 pub label: String,
239}
240
241impl Into<CrossrefType> for crate::query::types::Type {
242 fn into(self) -> CrossrefType {
243 CrossrefType {
244 id: self.id().to_string(),
245 label: self.label().to_string(),
246 }
247 }
248}
249
250#[derive(Debug, Clone, Deserialize, Serialize)]
252pub struct WorkAgency {
253 #[serde(rename = "DOI")]
255 doi: String,
256 agency: Agency,
258}
259
260#[derive(Debug, Clone, Deserialize, Serialize)]
262#[allow(missing_docs)]
263pub struct Prefix {
264 pub member: String,
265 pub name: String,
266 pub prefix: String,
267}
268
269#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
271#[serde(rename_all = "kebab-case")]
272#[allow(missing_docs)]
273pub enum MessageType {
274 WorkAgency,
275 Funder,
276 Prefix,
277 Member,
278 Work,
279 WorkList,
280 FunderList,
281 Type,
282 TypeList,
283 MemberList,
284 Journal,
285 JournalList,
286 ValidationFailure,
287 RouteNotFound,
288}
289
290impl MessageType {
291 pub fn as_str(&self) -> &str {
293 match self {
294 MessageType::WorkAgency => "work-agency",
295 MessageType::Funder => "funder",
296 MessageType::Prefix => "prefix",
297 MessageType::Member => "member",
298 MessageType::MemberList => "member-list",
299 MessageType::Work => "work",
300 MessageType::WorkList => "work-list",
301 MessageType::FunderList => "funder-list",
302 MessageType::Type => "type",
303 MessageType::TypeList => "type-list",
304 MessageType::Journal => "journal",
305 MessageType::JournalList => "journal-list",
306 MessageType::ValidationFailure => "validation-failure",
307 MessageType::RouteNotFound => "route-not-found",
308 }
309 }
310}
311
312impl fmt::Display for MessageType {
313 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
314 self.as_str().fmt(f)
315 }
316}
317
318#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
320#[serde(rename_all = "kebab-case")]
321pub struct QueryResponse {
322 pub start_index: usize,
324 pub search_terms: Option<String>,
326}
327
328pub type FacetMap = HashMap<String, FacetItem>;
332
333#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
335#[serde(rename_all = "kebab-case")]
336pub struct FacetItem {
337 pub value_count: usize,
339 pub values: HashMap<String, usize>,
341}
342
343#[derive(Debug, Clone, Deserialize, Serialize)]
345#[serde(rename_all = "kebab-case")]
346pub struct Failure {
347 #[serde(rename = "type")]
349 type_: String,
350 value: String,
352 message: String,
354}
355
356#[derive(Debug, Clone, Default, Deserialize, Serialize)]
358#[serde(rename_all = "kebab-case", default)]
359#[allow(missing_docs)]
360pub struct Funder {
361 pub hierarchy_names: HashMap<String, Option<String>>,
362 pub hierarchy: HashMap<String, HashMap<String, HashMap<String, bool>>>,
363 pub id: String,
364 pub location: String,
365 pub work_count: Option<usize>,
366 pub descendant_work_count: Option<usize>,
367 pub descendants: Vec<String>,
368 pub name: String,
369 pub alt_names: Vec<String>,
370 pub uri: String,
371 pub replaces: Vec<String>,
372 pub replaced_by: Vec<String>,
373 pub tokens: Vec<String>,
374}
375
376#[derive(Debug, Clone, Default, Deserialize, Serialize)]
378#[serde(rename_all = "kebab-case", default)]
379#[allow(missing_docs)]
380pub struct Member {
381 pub primary_name: String,
382 pub last_status_check_time: usize,
383 pub counts: Counts,
384 pub breakdowns: Breakdowns,
385 pub prefixes: Vec<String>,
386 pub coverage: Coverage,
387 pub prefix: Vec<RefPrefix>,
388 pub id: usize,
389 pub tokens: Vec<String>,
390 pub counts_type: HashMap<String, HashMap<String, usize>>,
391 pub coverage_type: Value,
392 pub flags: HashMap<String, bool>,
393 pub location: String,
394 pub names: Vec<String>,
395}
396
397#[derive(Debug, Clone, Default, Deserialize, Serialize)]
398#[serde(rename_all = "kebab-case", default)]
399#[allow(missing_docs)]
400pub struct Counts {
401 pub total_dois: usize,
402 pub current_dois: usize,
403 pub backfile_dois: usize,
404}
405
406#[derive(Debug, Clone, Default, Deserialize, Serialize)]
407#[serde(rename_all = "kebab-case", default)]
408#[allow(missing_docs)]
409pub struct Breakdowns {
410 pub dois_by_issued_year: Vec<Vec<u32>>,
411}
412
413#[derive(Debug, Clone, Default, Deserialize, Serialize)]
414#[serde(rename_all = "kebab-case", default)]
415#[allow(missing_docs)]
416pub struct Coverage {
417 pub affiliations_current: f32,
418 pub similarity_checking_current: f32,
419 pub funders_backfile: f32,
420 pub licenses_backfile: f32,
421 pub funders_current: f32,
422 pub affiliations_backfile: f32,
423 pub resource_links_backfile: f32,
424 pub orcids_backfile: f32,
425 pub update_policies_current: f32,
426 pub open_references_backfile: f32,
427 pub orcids_current: f32,
428 pub similarity_checking_backfile: f32,
429 pub references_backfile: f32,
430 pub award_numbers_backfile: f32,
431 pub update_policies_backfile: f32,
432 pub licenses_current: f32,
433 pub award_numbers_current: f32,
434 pub abstracts_backfile: f32,
435 pub resource_links_current: f32,
436 pub abstracts_current: f32,
437 pub open_references_current: f32,
438 pub references_current: f32,
439}
440
441#[derive(Debug, Clone, Default, Deserialize, Serialize)]
442#[serde(rename_all = "kebab-case", default)]
443#[allow(missing_docs)]
444pub struct RefPrefix {
445 pub value: String,
446 pub name: String,
447 pub public_references: bool,
448 pub reference_visibility: Option<Visibility>,
449}
450
451#[derive(Debug, Clone, Deserialize, Serialize)]
453#[serde(rename_all = "kebab-case")]
454#[allow(missing_docs)]
455pub struct Journal {
456 pub last_status_check_time: Option<Value>,
458 pub counts: Option<usize>,
459 pub breakdowns: Option<Value>,
460 pub publisher: Option<String>,
461 pub coverage: Option<Value>,
462 pub title: Option<String>,
463 pub subjects: Vec<Value>,
464 pub coverage_type: Option<Value>,
465 pub flags: Option<Value>,
466 #[serde(rename = "ISSN")]
467 pub issn: Vec<String>,
468 pub issn_type: Vec<String>,
469}
470
471#[cfg(test)]
472mod tests {
473 use super::*;
474 use serde_json::*;
475
476 #[test]
477 fn facets_deserialize() {
478 let facets = r#"{
479 "affiliation": {
480 "value-count": 5,
481 "values": {
482 "of": 177247,
483 "university": 147649,
484 "department": 128741,
485 "and": 102652,
486 "medicine": 96232
487 }
488 },
489 "orcid": {
490 "value-count": 10,
491 "values": {
492 "http:\/\/orcid.org\/0000-0002-0270-1711": 67
493 }
494 }
495 }"#;
496
497 let _: FacetMap = from_str(facets).unwrap();
498 }
499
500 #[test]
501 fn agency_msg_deserialize() {
502 let agency_str =
503 r#"{"status":"ok","message-type":"work-agency","message-version":"1.0.0","message":{"DOI":"10.1037\/0003-066x.59.1.29","agency":{"id":"crossref","label":"Crossref"}}}"#;
504
505 let agency: Response = from_str(agency_str).unwrap();
506
507 assert!(agency.is_work_ageny());
508 }
509
510 #[test]
511 fn funder_list_msg_deserialize() {
512 let funders_str = r#"{"status":"ok","message-type":"funder-list","message-version":"1.0.0","message":{"items-per-page":2,"query":{"start-index":0,"search-terms":"NSF"},"total-results":9,"items":[{ "id": "501100004190",
513 "location": "Norway",
514 "name": "Norsk Sykepleierforbund",
515 "alt-names": [
516 "NSF"
517 ],
518 "uri": "http:\/\/dx.doi.org\/10.13039\/501100004190",
519 "replaces": [],
520 "replaced-by": [],
521 "tokens": [
522 "norsk"
523 ]
524}]}}"#;
525
526 let funders: Response = from_str(funders_str).unwrap();
527
528 assert!(funders.is_funder_list());
529 }
530
531 #[test]
532 fn funder_msg_deserialize() {
533 let funder_str = r#"{"status":"ok","message-type":"funder","message-version":"1.0.0","message":{ "id": "501100004190",
534 "location": "Norway",
535 "name": "Norsk Sykepleierforbund",
536 "alt-names": [
537 "NSF"
538 ],
539 "uri": "http:\/\/dx.doi.org\/10.13039\/501100004190",
540 "replaces": [],
541 "replaced-by": [],
542 "tokens": [
543 "norsk"
544 ],
545 "work-count": 43,
546 "descendants": [],
547 "hierarchy-names": {
548 "100000019": "National Hemophilia Foundation"
549 },
550 "descendant-work-count": 43,
551 "hierarchy": {
552 "100000019": {}
553 }
554}}"#;
555
556 let funder: Response = from_str(funder_str).unwrap();
557
558 assert!(funder.is_funder());
559 }
560
561 #[test]
562 fn funder_msg_deserialize2() {
563 let funder_str = r#"{"status":"ok","message-type":"funder","message-version":"1.0.0","message":{"hierarchy-names":{"100006130":"Office","100000015":"U.S. Department of Energy","100013165":"National"},"replaced-by":[],"work-count":44026,"name":"U.S. Department of Energy","descendants":["100006166"],"descendant-work-count":68704,"id":"100000015","tokens":["us"],"replaces":[],"uri":"http:\/\/dx.doi.org\/10.13039\/100000015","hierarchy":{"100000015":{"100006130":{"more":true},"100013165":{},"100006138":{"more":true}}},"alt-names":["DOE"],"location":"United States"}}"#;
564
565 let funder: Response = from_str(funder_str).unwrap();
566
567 assert!(funder.is_funder());
568 }
569
570 #[test]
571 fn prefix_msg_deserialize() {
572 let prefix_str = r#"{"status":"ok","message-type":"prefix","message-version":"1.0.0","message":{"member":"http:\/\/id.crossref.org\/member\/78","name":"Elsevier BV","prefix":"http:\/\/id.crossref.org\/prefix\/10.1016"}}"#;
573
574 let prefix: Response = from_str(prefix_str).unwrap();
575
576 assert!(prefix.is_prefix());
577 }
578
579 #[test]
580 fn members_list_msg_deserialize() {
581 let members_list_str = r#"{"status":"ok","message-type":"member-list","message-version":"1.0.0","message":{"items-per-page":2,"query":{"start-index":0,"search-terms":null},"total-results":10257,"items":[{"last-status-check-time":1551766727771,"primary-name":"Society for Leukocyte Biology","counts":{"total-dois":0,"current-dois":0,"backfile-dois":0},"breakdowns":{"dois-by-issued-year":[]},"prefixes":["10.1189"],"coverage":{"affiliations-current":0,"similarity-checking-current":0,"funders-backfile":0,"licenses-backfile":0,"funders-current":0,"affiliations-backfile":0,"resource-links-backfile":0,"orcids-backfile":0,"update-policies-current":0,"open-references-backfile":0,"orcids-current":0,"similarity-checking-backfile":0,"references-backfile":0,"award-numbers-backfile":0,"update-policies-backfile":0,"licenses-current":0,"award-numbers-current":0,"abstracts-backfile":0,"resource-links-current":0,"abstracts-current":0,"open-references-current":0,"references-current":0},"prefix":[{"value":"10.1189","name":"Society for Leukocyte Biology","public-references":false,"reference-visibility":"limited"}],"id":183,"tokens":["society","for","leukocyte","biology"],"counts-type":{"all":{},"current":{},"backfile":{}},"coverage-type":{"all":null,"backfile":null,"current":null},"flags":{"deposits-abstracts-current":false,"deposits-orcids-current":false,"deposits":false,"deposits-affiliations-backfile":false,"deposits-update-policies-backfile":false,"deposits-similarity-checking-backfile":false,"deposits-award-numbers-current":false,"deposits-resource-links-current":false,"deposits-articles":false,"deposits-affiliations-current":false,"deposits-funders-current":false,"deposits-references-backfile":false,"deposits-abstracts-backfile":false,"deposits-licenses-backfile":false,"deposits-award-numbers-backfile":false,"deposits-open-references-backfile":false,"deposits-open-references-current":false,"deposits-references-current":false,"deposits-resource-links-backfile":false,"deposits-orcids-backfile":false,"deposits-funders-backfile":false,"deposits-update-policies-current":false,"deposits-similarity-checking-current":false,"deposits-licenses-current":false},"location":"9650 Rockville Pike Attn: Lynn Willis Bethesda MD 20814 United States","names":["Society for Leukocyte Biology"]}]}}"#;
582
583 let members_list: Response = from_str(members_list_str).unwrap();
584
585 assert!(members_list.is_member_list());
586 }
587
588 #[test]
589 fn member_msg_deserialize() {
590 let member_str = r#"{"status":"ok","message-type":"member","message-version":"1.0.0","message":{"last-status-check-time":1551766727771,"primary-name":"Society for Leukocyte Biology","counts":{"total-dois":0,"current-dois":0,"backfile-dois":0},"breakdowns":{"dois-by-issued-year":[]},"prefixes":["10.1189"],"coverage":{"affiliations-current":0,"similarity-checking-current":0,"funders-backfile":0,"licenses-backfile":0,"funders-current":0,"affiliations-backfile":0,"resource-links-backfile":0,"orcids-backfile":0,"update-policies-current":0,"open-references-backfile":0,"orcids-current":0,"similarity-checking-backfile":0,"references-backfile":0,"award-numbers-backfile":0,"update-policies-backfile":0,"licenses-current":0,"award-numbers-current":0,"abstracts-backfile":0,"resource-links-current":0,"abstracts-current":0,"open-references-current":0,"references-current":0},"prefix":[{"value":"10.1189","name":"Society for Leukocyte Biology","public-references":false,"reference-visibility":"limited"}],"id":183,"tokens":["society","for","leukocyte","biology"],"counts-type":{"all":{},"current":{},"backfile":{}},"coverage-type":{"all":null,"backfile":null,"current":null},"flags":{"deposits-abstracts-current":false,"deposits-orcids-current":false,"deposits":false,"deposits-affiliations-backfile":false,"deposits-update-policies-backfile":false,"deposits-similarity-checking-backfile":false,"deposits-award-numbers-current":false,"deposits-resource-links-current":false,"deposits-articles":false,"deposits-affiliations-current":false,"deposits-funders-current":false,"deposits-references-backfile":false,"deposits-abstracts-backfile":false,"deposits-licenses-backfile":false,"deposits-award-numbers-backfile":false,"deposits-open-references-backfile":false,"deposits-open-references-current":false,"deposits-references-current":false,"deposits-resource-links-backfile":false,"deposits-orcids-backfile":false,"deposits-funders-backfile":false,"deposits-update-policies-current":false,"deposits-similarity-checking-current":false,"deposits-licenses-current":false},"location":"9650 Rockville Pike Attn: Lynn Willis Bethesda MD 20814 United States","names":["Society for Leukocyte Biology"]}}"#;
591
592 let member: Response = from_str(member_str).unwrap();
593
594 assert!(member.is_member());
595 }
596
597 #[test]
598 fn journals_list_msg_deserialize() {
599 let journal_list_str = r#"{"status":"ok","message-type":"journal-list","message-version":"1.0.0","message":{"items-per-page":2,"query":{"start-index":0,"search-terms":null},"total-results":10257,"items":[{"last-status-check-time":null,"counts":null,"breakdowns":null,"publisher":"Fundacao Educacional de Criciuma- FUCRI","coverage":null,"title":"A INFLU\u00caNCIA DA PUBLICIDADE NA TRANSI\u00c7\u00c3O NUTRICIONAL UMA S\u00cdNTESE PARA ENTENDER A OBESIDADE","subjects":[],"coverage-type":null,"flags":null,"ISSN":[],"issn-type":[]}]}}"#;
600
601 let journal_list: Response = from_str(journal_list_str).unwrap();
602
603 assert!(journal_list.is_journal_list());
604 }
605
606 #[test]
607 fn journal_msg_deserialize() {
608 let journal_str = r#"{"status":"ok","message-type":"journal","message-version":"1.0.0","message":{"last-status-check-time":null,"counts":null,"breakdowns":null,"publisher":"Fundacao Educacional de Criciuma- FUCRI","coverage":null,"title":"A INFLU\u00caNCIA DA PUBLICIDADE NA TRANSI\u00c7\u00c3O NUTRICIONAL UMA S\u00cdNTESE PARA ENTENDER A OBESIDADE","subjects":[],"coverage-type":null,"flags":null,"ISSN":[],"issn-type":[]}}"#;
609
610 let journal: Response = from_str(journal_str).unwrap();
611
612 assert!(journal.is_journal());
613 }
614
615 #[test]
616 fn type_list_msg_deserialize() {
617 let type_list_str = r#"{"status":"ok","message-type":"type-list","message-version":"1.0.0","message":{"total-results":27,"items":[{"id":"book-section","label":"Book Section"},{"id":"monograph","label":"Monograph"}]}}"#;
618 let type_list: Response = from_str(type_list_str).unwrap();
619
620 assert!(type_list.is_type_list());
621 }
622
623 #[test]
624 fn type_msg_deserialize() {
625 let type_str = r#"{"status":"ok","message-type":"type","message-version":"1.0.0","message":{"id":"book-section","label":"Book Section"}}"#;
626 let type_: Response = from_str(type_str).unwrap();
627
628 assert!(type_.is_type());
629 }
630
631 #[test]
632 fn validation_failure_deserialize() {
633 let failure_str = r#"{"status":"failed","message-type":"validation-failure","message":[{"type":"parameter-not-allowed","value":"query.*","message":"This route does not support field query parameters"}]}"#;
634 let failure: Response = from_str(failure_str).unwrap();
635
636 assert!(failure.is_validation_failure());
637 }
638
639 #[test]
640 fn work_msg_deserialize() {
641 let failure_str = r#"{"status":"ok","message-type":"work","message-version":"1.0.0","message":{"indexed":{"date-parts":[[2019,2,14]],"date-time":"2019-02-14T05:10:15Z","timestamp":1550121015066},"reference-count":105,"publisher":"American Psychological Association (APA)","issue":"1","content-domain":{"domain":[],"crossmark-restriction":false},"short-container-title":["American Psychologist"],"DOI":"10.1037\/0003-066x.59.1.29","type":"journal-article","created":{"date-parts":[[2004,1,21]],"date-time":"2004-01-21T14:31:19Z","timestamp":1074695479000},"page":"29-40","source":"Crossref","is-referenced-by-count":83,"title":["How the Mind Hurts and Heals the Body."],"prefix":"10.1037","volume":"59","author":[{"given":"Oakley","family":"Ray","sequence":"first","affiliation":[]}],"member":"15","published-online":{"date-parts":[[2004]]},"container-title":["American Psychologist"],"original-title":[],"language":"en","link":[{"URL":"http:\/\/psycnet.apa.org\/journals\/amp\/59\/1\/29.pdf","content-type":"unspecified","content-version":"vor","intended-application":"similarity-checking"}],"deposited":{"date-parts":[[2018,4,8]],"date-time":"2018-04-08T18:56:17Z","timestamp":1523213777000},"score":1.0,"subtitle":[],"short-title":[],"issued":{"date-parts":[[2004]]},"references-count":105,"journal-issue":{"published-online":{"date-parts":[[2004]]},"issue":"1"},"alternative-id":["2004-10043-004","14736318"],"URL":"http:\/\/dx.doi.org\/10.1037\/0003-066x.59.1.29","relation":{},"ISSN":["1935-990X","0003-066X"],"issn-type":[{"value":"0003-066X","type":"print"},{"value":"1935-990X","type":"electronic"}]}}"#;
642 let work: Response = from_str(failure_str).unwrap();
643
644 assert!(work.is_work());
645 }
646}