use crate::error::Result;
use crate::response::{FacetMap, QueryResponse};
use crate::{Crossref, WorksQuery};
use chrono::NaiveDate;
use serde_json::Value;
pub type Relations = std::collections::HashMap<String, Value>;
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
#[allow(missing_docs)]
pub struct WorkList {
pub facets: FacetMap,
pub total_results: usize,
pub items_per_page: Option<usize>,
pub query: Option<QueryResponse>,
pub items: Vec<Work>,
pub next_cursor: Option<String>,
}
pub struct WorkIterator<'a> {
query: WorksQuery,
client: &'a Crossref,
index: usize,
}
impl<'a> Iterator for WorkIterator<'a> {
type Item = WorkList;
fn next(&mut self) -> Option<Self::Item> {
unimplemented!()
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
#[allow(missing_docs)]
pub struct Work {
pub publisher: String,
pub title: Vec<String>,
pub original_title: Option<Vec<String>>,
pub language: Option<String>,
pub short_title: Option<Vec<String>>,
#[serde(rename = "abstract")]
pub abstract_: Option<String>,
pub references_count: i32,
pub is_referenced_by_count: i32,
pub source: String,
pub journal_issue: Option<Issue>,
pub prefix: String,
#[serde(rename = "DOI")]
pub doi: String,
#[serde(rename = "URL")]
pub url: String,
pub member: String,
#[serde(rename = "type")]
pub type_: String,
pub created: Option<Date>,
pub date: Option<Date>,
pub deposited: Option<Date>,
pub score: Option<f32>,
pub indexed: Date,
pub issued: PartialDate,
pub posted: Option<PartialDate>,
pub accepted: Option<PartialDate>,
pub subtitle: Option<Vec<String>>,
pub container_title: Option<Vec<String>>,
pub short_container_title: Option<Vec<String>>,
pub group_title: Option<String>,
pub issue: Option<String>,
pub volume: Option<String>,
pub page: Option<String>,
pub article_number: Option<String>,
pub published_print: Option<PartialDate>,
pub published_online: Option<PartialDate>,
pub subject: Option<Vec<String>>,
#[serde(rename = "ISSN")]
pub issn: Option<Vec<String>>,
pub issn_type: Option<Vec<ISSN>>,
#[serde(rename = "ISBN")]
pub isbn: Option<Vec<String>>,
pub archive: Option<Vec<String>>,
pub license: Option<Vec<License>>,
pub funder: Option<Vec<FundingBody>>,
pub assertion: Option<Vec<Assertion>>,
pub author: Option<Vec<Contributor>>,
pub editor: Option<Vec<Contributor>>,
pub chair: Option<Vec<Contributor>>,
pub translator: Option<Vec<Contributor>>,
pub update_to: Option<Vec<Update>>,
pub update_policy: Option<String>,
pub link: Option<Vec<ResourceLink>>,
pub clinical_trial_number: Option<Vec<ClinicalTrialNumber>>,
pub alternative_id: Option<Vec<String>>,
pub reference: Option<Vec<Reference>>,
pub content_domain: Option<ContentDomain>,
pub relation: Option<Relations>,
pub review: Option<Relations>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct DateParts(pub Vec<Vec<Option<u32>>>);
impl DateParts {
pub fn as_date(&self) -> Option<DateField> {
fn naive(v: &[Option<u32>]) -> Option<NaiveDate> {
match v.len() {
0 => None,
1 => Some(NaiveDate::from_ymd(v[0]? as i32, 0, 0)),
2 => Some(NaiveDate::from_ymd(v[0]? as i32, v[1]?, 0)),
3 => Some(NaiveDate::from_ymd(v[0]? as i32, v[1]?, v[2]?)),
_ => None,
}
}
match self.0.len() {
0 => None,
1 => Some(DateField::Single(naive(&self.0[0])?)),
2 => Some(DateField::Range {
from: naive(&self.0[0])?,
to: naive(&self.0[1])?,
}),
_ => Some(DateField::Multi(
self.0
.iter()
.map(|x| naive(x))
.collect::<Option<Vec<_>>>()?,
)),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[allow(missing_docs)]
pub struct FundingBody {
pub name: String,
#[serde(rename = "DOI")]
pub doi: Option<String>,
pub award: Option<Vec<String>>,
#[serde(rename = "doi-asserted-by")]
pub doi_asserted_by: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[allow(missing_docs)]
pub struct ClinicalTrialNumber {
#[serde(rename = "clinical-trial-number")]
pub clinical_trial_number: String,
pub registry: String,
#[serde(rename = "type")]
pub type_: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[allow(missing_docs)]
pub struct Contributor {
pub family: String,
pub given: Option<String>,
#[serde(rename = "ORCID")]
pub orcid: Option<String>,
#[serde(rename = "authenticated-orcid")]
pub authenticated_orcid: Option<bool>,
pub affiliation: Option<Vec<Affiliation>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[allow(missing_docs)]
pub struct Affiliation {
pub name: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct Date {
pub date_parts: DateParts,
pub timestamp: usize,
pub date_time: String,
}
impl Date {
pub fn as_date_field(&self) -> Option<DateField> {
self.date_parts.as_date()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct PartialDate {
#[serde(rename = "date-parts")]
pub date_parts: DateParts,
}
impl PartialDate {
pub fn as_date_field(&self) -> Option<DateField> {
self.date_parts.as_date()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub enum DateField {
Single(NaiveDate),
Range {
from: NaiveDate,
to: NaiveDate,
},
Multi(Vec<NaiveDate>),
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct Update {
pub updated: PartialDate,
#[serde(rename = "DOI")]
pub doi: String,
#[serde(rename = "type")]
pub type_: String,
pub label: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[allow(missing_docs)]
pub struct Assertion {
pub name: String,
pub value: String,
#[serde(rename = "URL")]
pub url: Option<String>,
pub explanation: Option<String>,
pub label: Option<String>,
pub order: Option<i32>,
pub group: Option<AssertionGroup>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
#[allow(missing_docs)]
pub struct Issue {
pub published_print: Option<PartialDate>,
pub published_online: Option<PartialDate>,
pub issue: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[allow(missing_docs)]
pub struct AssertionGroup {
pub name: String,
pub label: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[allow(missing_docs)]
pub struct Agency {
pub id: String,
pub label: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct License {
pub content_version: String,
pub delay_in_days: i32,
pub start: PartialDate,
#[serde(rename = "URL")]
pub url: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct ResourceLink {
pub intended_application: String,
pub content_version: String,
#[serde(rename = "URL")]
pub url: String,
pub content_type: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
#[allow(missing_docs)]
pub struct Reference {
pub key: String,
#[serde(rename = "DOI")]
pub doi: Option<String>,
pub doi_asserted_by: Option<String>,
pub issue: Option<String>,
pub first_page: Option<String>,
pub volume: Option<String>,
pub edition: Option<String>,
pub component: Option<String>,
pub standard_designator: Option<String>,
pub standards_body: Option<String>,
pub author: Option<String>,
pub year: Option<String>,
pub unstructured: Option<String>,
pub journal_title: Option<String>,
pub article_title: Option<String>,
pub series_title: Option<String>,
pub volume_title: Option<String>,
#[serde(rename = "ISSN")]
pub issn: Option<String>,
pub issn_type: Option<String>,
#[serde(rename = "ISBN")]
pub isbn: Option<String>,
pub isbn_type: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct ISSN {
pub value: String,
#[serde(rename = "type")]
pub type_: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
#[allow(missing_docs)]
pub struct ContentDomain {
pub domain: Vec<String>,
pub crossmark_restriction: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
#[allow(missing_docs)]
pub struct Relation {
pub id_type: Option<String>,
pub id: Option<String>,
pub asserted_by: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
#[allow(missing_docs)]
pub struct Review {
pub running_number: Option<String>,
pub revision_round: Option<String>,
pub stage: Option<String>,
pub recommendation: Option<String>,
#[serde(rename = "type")]
pub type_: String,
pub competing_interest_statement: Option<String>,
pub language: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::*;
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
struct Demo {
pub date_parts: DateParts,
}
#[test]
fn date_parts_serde() {
let demo = Demo {
date_parts: DateParts(vec![vec![Some(2017), Some(10), Some(11)]]),
};
let expected = r##"{"date_parts":[[2017,10,11]]}"##;
assert_eq!(expected, &to_string(&demo).unwrap());
assert_eq!(demo, from_str::<Demo>(expected).unwrap());
}
#[test]
fn serialize_work() {
let work_str = r##"{
"indexed": {
"date-parts": [
[
2019,
2,
26
]
],
"date-time": "2019-02-26T10:43:14Z",
"timestamp": 1551177794515
},
"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": 84,
"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,
"subtitle": [],
"short-title": [],
"issued": {
"date-parts": [
[
null
]
]
},
"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"
}
]
}
"##;
let work: Work = from_str(work_str).unwrap();
}
}