crossref/response/
mod.rs

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
11/// provides the types for a work response
12pub mod work;
13
14pub use crate::response::work::{Work, WorkList};
15
16/// Represents the whole crossref response for a any request.
17#[derive(Debug, Clone, Serialize)]
18#[serde(rename_all = "kebab-case")]
19pub struct Response {
20    /// the status of the request
21    pub status: String,
22    /// the type of the response message holds
23    pub message_type: MessageType,
24    /// the version of the service created this message
25    #[serde(default = "default_msg_version")]
26    pub message_version: String,
27    /// the actual message of the response
28    pub message: Option<Message>,
29}
30
31/// at some routes the `msg_version` is missing, this returns the default version for a crossref response
32fn default_msg_version() -> String {
33    "1.0.0".to_string()
34}
35
36/// this macro helps to generate a function that checks whether the message is of a specific type
37macro_rules! impl_msg_helper {
38    (single: $($name:ident -> $ident:ident,)*) => {
39    $(
40        /// checks if the message holds the variant
41        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    /// checks whether the `message` holds a variant of `RouteNotFound`
70    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             /// if facets where part in the request they are also included in the response
178            #[serde(default)]
179            pub facets: FacetMap,
180            /// the number of items that match the response
181            pub total_results: usize,
182            /// crossref responses for large number of items are divided in pages, number of elements to expect in `items`
183            pub items_per_page: Option<usize>,
184            /// if a query was set in the request, this will also be part in the response
185            pub query: Option<QueryResponse>,
186            /// all actual message items of the response
187            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/// the different payloads of a response
200#[derive(Debug, Clone, Deserialize, Serialize)]
201#[serde(untagged)]
202pub enum Message {
203    /// if a request failed on the server side
204    ValidationFailure(Vec<Failure>),
205    /// a route could not be found on the server side
206    RouteNotFound,
207    /// the agency for a specific work
208    WorkAgency(WorkAgency),
209    /// metadata data for the DOI owner prefix
210    Prefix(Prefix),
211    /// a valid work type
212    Type(CrossrefType),
213    /// a list of valid work types
214    TypeList(TypeList),
215    /// a publication(journal, articles...)
216    Work(Box<Work>),
217    /// a list of publications
218    WorkList(WorkList),
219    /// a crossref member (mostly publishers)
220    Member(Box<Member>),
221    /// a list of crossref members
222    MemberList(MemberList),
223    /// a Journal publication
224    Journal(Box<Journal>),
225    /// list of journal publications
226    JournalList(JournalList),
227    /// a funder in the [funder registry](https://github.com/Crossref/open-funder-registry)
228    Funder(Box<Funder>),
229    /// a list of funder
230    FunderList(FunderList),
231}
232
233#[derive(Debug, Clone, Deserialize, Serialize)]
234#[allow(missing_docs)]
235pub struct CrossrefType {
236    pub id: String,
237    /// Name of work's publisher
238    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/// response item for the `/works/{id}/agency` route
251#[derive(Debug, Clone, Deserialize, Serialize)]
252pub struct WorkAgency {
253    /// the DOI fo the work that belongs to the `agency`
254    #[serde(rename = "DOI")]
255    doi: String,
256    /// the agency that owns the work with `doi`
257    agency: Agency,
258}
259
260/// response item for the `/prefix/{id}/` route
261#[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/// all possible `message-type` of a response
270#[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    /// the type identifier for a message
292    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/// if a query was set in the request then it is also part of the result
319#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
320#[serde(rename_all = "kebab-case")]
321pub struct QueryResponse {
322    /// from which the returned items start
323    pub start_index: usize,
324    /// the terms that were initially set in the request query
325    pub search_terms: Option<String>,
326}
327
328// TODO impl CrossrefRoute for QueryResponse
329
330/// facets are returned as map
331pub type FacetMap = HashMap<String, FacetItem>;
332
333/// if a `facet` was set in a request `FacetMap` will be  in a `List` response as additional field of the message
334#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
335#[serde(rename_all = "kebab-case")]
336pub struct FacetItem {
337    /// represents the length of `values`
338    pub value_count: usize,
339    /// contains the
340    pub values: HashMap<String, usize>,
341}
342
343/// response item if a request could be processed
344#[derive(Debug, Clone, Deserialize, Serialize)]
345#[serde(rename_all = "kebab-case")]
346pub struct Failure {
347    /// identifier for a failue like `parameter-not-found`
348    #[serde(rename = "type")]
349    type_: String,
350    /// value that caused the failure
351    value: String,
352    /// the message from the server
353    message: String,
354}
355
356/// response item for the `/funder/{id}` route
357#[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/// response item for the `/member/{id}` route
377#[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/// response item for the `/journal/{id}` route
452#[derive(Debug, Clone, Deserialize, Serialize)]
453#[serde(rename_all = "kebab-case")]
454#[allow(missing_docs)]
455pub struct Journal {
456    /// could not determine type, possible PartialDateParts
457    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}