#![deny(missing_docs)]
#![allow(unused)]
#[macro_use]
extern crate serde_derive;
mod error;
pub mod query;
pub mod response;
pub mod cn;
pub mod tdm;
#[cfg(feature = "client")]
pub mod client;
#[doc(inline)]
pub use self::error::{Error, Result};
#[doc(inline)]
pub use self::query::works::{
FieldQuery, WorkResultControl, Works, WorksCombined, WorksFilter, WorksQuery,
};
#[doc(inline)]
pub use self::query::{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};
use crate::response::{MessageType, Prefix};
use reqwest::{self, Client};
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_query {
($($name:ident $component:ident,)*) => {
$( pub fn $name(&self, id: &str, query: WorksQuery) -> Result<WorkList> {
let resp = self.get_response($component::Works(WorksCombined::new(id, query)))?;
get_item!(WorkList, resp.message, resp.message_type)
})+
};
}
#[derive(Debug, Clone)]
pub struct Crossref {
pub base_url: String,
pub client: Client,
}
impl Crossref {
const BASE_URL: &'static str = "https://api.crossref.org";
impl_query!(funder_works_query Funders, member_works_query Members,
type_works_query Types, journal_works_query Journals,);
pub fn builder() -> CrossrefBuilder {
CrossrefBuilder::new()
}
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.resource_component()),
}
.into())
} else {
Ok(serde_json::from_str(&resp)?)
}
}
pub fn works(&self, query: WorksQuery) -> Result<WorkList> {
let resp = self.get_response(Works::Query(query))?;
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 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 query_works(&self, term: &str) -> Result<WorkList> {
self.works(WorksQuery::new().query(term))
}
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 funder_works(&self, funder_id: &str, term: &str) -> Result<WorkList> {
let resp = self.get_response(Funders::Works(WorksCombined::new(
funder_id,
WorksQuery::new().query(term),
)))?;
get_item!(WorkList, resp.message, resp.message_type)
}
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, id: &str) -> Result<Member> {
let resp = self.get_response(Members::Identifier(id.to_string()))?;
get_item!(Member, resp.message, resp.message_type).map(|x| *x)
}
pub fn member_works(&self, member_id: &str, term: &str) -> Result<WorkList> {
let resp = self.get_response(Members::Works(WorksCombined::new(
member_id,
WorksQuery::new().query(term),
)))?;
get_item!(WorkList, resp.message, resp.message_type)
}
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 prefix_works(&self, prefix_id: &str, term: &str) -> Result<WorkList> {
let resp = self.get_response(Prefixes::Works(WorksCombined::new(
prefix_id,
WorksQuery::new().query(term),
)))?;
get_item!(WorkList, resp.message, resp.message_type)
}
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: &str) -> Result<CrossrefType> {
let resp = self.get_response(Types::Identifier(id.to_string()))?;
get_item!(Type, resp.message, resp.message_type)
}
pub fn type_works(&self, type_: Type, term: &str) -> Result<WorkList> {
let resp = self.get_response(Types::Works(WorksCombined::new(
type_.id(),
WorksQuery::new().query(term),
)))?;
get_item!(WorkList, 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,
})
}
}