#![deny(warnings)]
#![deny(missing_docs)]
#![allow(unused)]
mod error;
pub mod query;
pub mod response;
pub mod cn;
pub mod tdm;
#[doc(inline)]
pub use self::error::{Error, Result};
#[doc(inline)]
pub use self::query::works::{
FieldQuery, WorkListQuery, WorkResultControl, Works, WorksFilter, WorksIdentQuery, WorksQuery,
};
#[doc(inline)]
pub use self::query::{Component, CrossrefQuery, CrossrefRoute, Order, Sort};
pub use self::query::{Funders, Journals, Members, Prefixes, Type, Types};
pub use self::response::{
CrossrefType, Funder, FunderList, Journal, JournalList, Member, MemberList, TypeList, Work,
WorkAgency, WorkList,
};
pub(crate) use self::response::{Message, Response};
use crate::error::ErrorKind;
use crate::query::{FundersQuery, MembersQuery, ResourceComponent};
use crate::response::{MessageType, Prefix};
use reqwest::{self, Client};
use std::iter::FlatMap;
use std::rc::Rc;
macro_rules! get_item {
($ident:ident, $value:expr, $got:expr) => {
if let Some(msg) = $value {
match msg {
Message::$ident(item) => Ok(item),
_ => Err(ErrorKind::UnexpectedItem {
expected: MessageType::$ident,
got: $got,
}
.into()),
}
} else {
Err(ErrorKind::MissingMessage {
expected: MessageType::$ident,
}
.into())
}
};
}
macro_rules! impl_combined_works_query {
($($name:ident $component:ident,)*) => {
$(
pub fn $name(&self, ident: WorksIdentQuery) -> Result<WorkList> {
let resp = self.get_response(&$component::Works(ident))?;
get_item!(WorkList, resp.message, resp.message_type)
})+
};
}
#[derive(Debug, Clone)]
pub struct Crossref {
pub base_url: String,
pub client: Rc<Client>,
}
impl Crossref {
const BASE_URL: &'static str = "https://api.crossref.org";
pub fn builder() -> CrossrefBuilder {
CrossrefBuilder::new()
}
impl_combined_works_query!(funder_works Funders, member_works Members,
type_works Types, journal_works Journals, prefix_works Prefixes,);
fn get_response<T: CrossrefQuery>(&self, query: &T) -> Result<Response> {
let resp = self
.client
.get(&query.to_url(&self.base_url)?)
.send()?
.text()?;
if resp.starts_with("Resource not found") {
Err(ErrorKind::ResourceNotFound {
resource: Box::new(query.clone().resource_component()),
}
.into())
} else {
Ok(serde_json::from_str(&resp)?)
}
}
pub fn works<T: Into<WorkListQuery>>(&self, query: T) -> Result<WorkList> {
let resp = self.get_response(&query.into())?;
get_item!(WorkList, resp.message, resp.message_type)
}
pub fn work(&self, doi: &str) -> Result<Work> {
let resp = self.get_response(&Works::Identifier(doi.to_string()))?;
get_item!(Work, resp.message, resp.message_type).map(|x| *x)
}
pub fn deep_page<T: Into<WorkListQuery>>(&self, query: T) -> WorkListIterator {
WorkListIterator {
query: query.into(),
client: self,
index: 0,
finish_next_iteration: false,
}
}
pub fn work_agency(&self, doi: &str) -> Result<WorkAgency> {
let resp = self.get_response(&Works::Agency(doi.to_string()))?;
get_item!(WorkAgency, resp.message, resp.message_type)
}
pub fn funders(&self, funders: FundersQuery) -> Result<FunderList> {
let resp = self.get_response(&Funders::Query(funders))?;
get_item!(FunderList, resp.message, resp.message_type)
}
pub fn funder(&self, id: &str) -> Result<Funder> {
let resp = self.get_response(&Funders::Identifier(id.to_string()))?;
get_item!(Funder, resp.message, resp.message_type).map(|x| *x)
}
pub fn members(&self, members: MembersQuery) -> Result<MemberList> {
let resp = self.get_response(&Members::Query(members))?;
get_item!(MemberList, resp.message, resp.message_type)
}
pub fn member(&self, member_id: &str) -> Result<Member> {
let resp = self.get_response(&Members::Identifier(member_id.to_string()))?;
get_item!(Member, resp.message, resp.message_type).map(|x| *x)
}
pub fn prefix(&self, id: &str) -> Result<Prefix> {
let resp = self.get_response(&Prefixes::Identifier(id.to_string()))?;
get_item!(Prefix, resp.message, resp.message_type)
}
pub fn journal(&self, id: &str) -> Result<Journal> {
let resp = self.get_response(&Journals::Identifier(id.to_string()))?;
get_item!(Journal, resp.message, resp.message_type).map(|x| *x)
}
pub fn types(&self) -> Result<TypeList> {
let resp = self.get_response(&Types::All)?;
get_item!(TypeList, resp.message, resp.message_type)
}
pub fn type_(&self, id: &Type) -> Result<CrossrefType> {
let resp = self.get_response(&Types::Identifier(id.id().to_string()))?;
get_item!(Type, resp.message, resp.message_type)
}
pub fn random_dois(&self, len: usize) -> Result<Vec<String>> {
self.works(WorksQuery::random(len))
.map(|x| x.items.into_iter().map(|x| x.doi).collect())
}
}
#[derive(Default)]
pub struct CrossrefBuilder {
user_agent: Option<String>,
plus_token: Option<String>,
base_url: Option<String>,
}
impl CrossrefBuilder {
pub fn new() -> CrossrefBuilder {
CrossrefBuilder::default()
}
pub fn polite(mut self, email: &str) -> Self {
self.user_agent = Some(format!("mailto:{}", email));
self
}
pub fn user_agent(mut self, user_agent: &str) -> Self {
self.user_agent = Some(user_agent.to_string());
self
}
pub fn token(mut self, token: &str) -> Self {
self.plus_token = Some(token.to_string());
self
}
pub fn build(self) -> Result<Crossref> {
use reqwest::header;
let mut headers = header::HeaderMap::new();
if let Some(agent) = &self.user_agent {
headers.insert(
header::USER_AGENT,
header::HeaderValue::from_str(agent).map_err(|_| ErrorKind::Config {
msg: format!("failed to create User Agent header for `{}`", agent),
})?,
);
}
if let Some(token) = &self.plus_token {
headers.insert(
header::AUTHORIZATION,
header::HeaderValue::from_str(token).map_err(|_| ErrorKind::Config {
msg: format!("failed to create AUTHORIZATION header for `{}`", token),
})?,
);
}
let client = reqwest::Client::builder()
.default_headers(headers)
.build()
.map_err(|_| ErrorKind::Config {
msg: "failed to initialize TLS backend".to_string(),
})?;
Ok(Crossref {
base_url: self
.base_url
.unwrap_or_else(|| Crossref::BASE_URL.to_string()),
client: Rc::new(client),
})
}
}
pub struct WorkListIterator<'a> {
query: WorkListQuery,
client: &'a Crossref,
index: usize,
finish_next_iteration: bool,
}
impl<'a> WorkListIterator<'a> {
pub fn into_work_iter(self) -> impl Iterator<Item = Work> + 'a {
self.flat_map(|x| x.items)
}
}
impl<'a> Iterator for WorkListIterator<'a> {
type Item = WorkList;
fn next(&mut self) -> Option<Self::Item> {
if self.finish_next_iteration {
return None;
}
{
let control = &mut self.query.query_mut().result_control;
if control.is_none() {
*control = Some(WorkResultControl::new_cursor());
}
}
let resp = self.client.get_response(&self.query);
if let Ok(resp) = resp {
let worklist: Result<WorkList> = get_item!(WorkList, resp.message, resp.message_type);
if let Ok(worklist) = worklist {
if let Some(cursor) = &worklist.next_cursor {
match &mut self.query.query_mut().result_control {
Some(WorkResultControl::Cursor { token, .. }) => {
*token = Some(cursor.clone())
}
Some(WorkResultControl::Standard(_)) => {
self.finish_next_iteration = true;
}
_ => (),
}
} else {
self.finish_next_iteration = true;
}
if worklist.items.is_empty() {
None
} else {
Some(worklist)
}
} else {
None
}
} else {
None
}
}
}