use crate::error::{Error, Result};
use crate::query::facet::FacetCount;
use crate::query::types::Type;
use crate::query::*;
use chrono::NaiveDate;
use serde::Serialize;
use serde::Serializer as SerdeSerializer;
use serde_json::Value;
use std::borrow::Cow;
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum WorksFilter {
HasFunder,
Funder(String),
Location(String),
Prefix(String),
Member(String),
FromIndexDate(NaiveDate),
UntilIndexDate(NaiveDate),
FromDepositDate(NaiveDate),
UntilDepositDate(NaiveDate),
FromUpdateDate(NaiveDate),
UntilUpdateDate(NaiveDate),
FromCreatedDate(NaiveDate),
UntilCreatedDate(NaiveDate),
FromPubDate(NaiveDate),
UntilPubDate(NaiveDate),
FromOnlinePubDate(NaiveDate),
UntilOnlinePubDate(NaiveDate),
FromPrintPubDate(NaiveDate),
UntilPrintPubDate(NaiveDate),
FromPostedDate(NaiveDate),
UntilPostedDate(NaiveDate),
FromAcceptedDate(NaiveDate),
UntilAcceptedDate(NaiveDate),
HasLicense,
LicenseUrl(String),
LicenseVersion(String),
LicenseDelay(i32),
HasFullText,
FullTextVersion(String),
FullTextType(String),
FullTextApplication(String),
HasReferences,
ReferenceVisibility(Visibility),
HasArchive,
Archive(String),
HasOrcid,
HasAuthenticatedOrcid,
Orcid(String),
Issn(String),
Isbn(String),
Type(Type),
Directory(String),
Doi(String),
Updates(String),
IsUpdate,
HasUpdatePolicy,
ContainerTitle(String),
CategoryName(String),
TypeName(String),
AwardNumber(String),
AwardFunder(String),
HasAssertion,
AssertionGroup(String),
Assertion(String),
HasAffiliation,
AlternativeId,
ArticleNumber,
HasAbstract,
HasClinicalTrialNumber,
ContentDomain(String),
HasContentDomain,
HasDomainRestriction,
HasRelation,
RelationType,
RelationObject,
RelationObjectType(String),
}
impl WorksFilter {
pub fn name(&self) -> &str {
match self {
WorksFilter::HasFunder => "has-funder",
WorksFilter::Funder(_) => "funder",
WorksFilter::Location(_) => "location",
WorksFilter::Prefix(_) => "prefix",
WorksFilter::Member(_) => "member",
WorksFilter::FromIndexDate(_) => "from-index-date",
WorksFilter::UntilIndexDate(_) => "until-index-date",
WorksFilter::FromDepositDate(_) => "from-deposit-date",
WorksFilter::UntilDepositDate(_) => "until-deposit-date",
WorksFilter::FromUpdateDate(_) => "from-update-date",
WorksFilter::UntilUpdateDate(_) => "until-update-date",
WorksFilter::FromCreatedDate(_) => "from-created-date",
WorksFilter::UntilCreatedDate(_) => "until-created-date",
WorksFilter::FromPubDate(_) => "from-pub-date",
WorksFilter::UntilPubDate(_) => "until-pub-date",
WorksFilter::FromOnlinePubDate(_) => "from-online-pub-date",
WorksFilter::UntilOnlinePubDate(_) => "until-online-pub-date",
WorksFilter::FromPrintPubDate(_) => "from-print-pub-date",
WorksFilter::UntilPrintPubDate(_) => "until-print-pub-date",
WorksFilter::FromPostedDate(_) => "from-posted-date",
WorksFilter::UntilPostedDate(_) => "until-posted-date",
WorksFilter::FromAcceptedDate(_) => "from-accepted-date",
WorksFilter::UntilAcceptedDate(_) => "until-accepted-date",
WorksFilter::HasLicense => "has-license",
WorksFilter::LicenseUrl(_) => "license.url",
WorksFilter::LicenseVersion(_) => "license.version",
WorksFilter::LicenseDelay(_) => "license.delay",
WorksFilter::HasFullText => "has-full-text",
WorksFilter::FullTextVersion(_) => "full-text.version",
WorksFilter::FullTextType(_) => "full-text.type",
WorksFilter::FullTextApplication(_) => "full-text.application",
WorksFilter::HasReferences => "has-references",
WorksFilter::ReferenceVisibility(_) => "reference-visibility",
WorksFilter::HasArchive => "has-archive",
WorksFilter::Archive(_) => "archive",
WorksFilter::HasOrcid => "has-orcid",
WorksFilter::HasAuthenticatedOrcid => "has-authenticated-orcid",
WorksFilter::Orcid(_) => "orcid",
WorksFilter::Issn(_) => "issn",
WorksFilter::Isbn(_) => "isbn",
WorksFilter::Type(_) => "type",
WorksFilter::Directory(_) => "directory",
WorksFilter::Doi(_) => "doi",
WorksFilter::Updates(_) => "updates",
WorksFilter::IsUpdate => "is-update",
WorksFilter::HasUpdatePolicy => "has-update-policy",
WorksFilter::ContainerTitle(_) => "container-title",
WorksFilter::CategoryName(_) => "category-name",
WorksFilter::TypeName(_) => "type-name",
WorksFilter::AwardNumber(_) => "award.number",
WorksFilter::AwardFunder(_) => "award.funder",
WorksFilter::HasAssertion => "has-assertion",
WorksFilter::AssertionGroup(_) => "assertion-group",
WorksFilter::Assertion(_) => "assertion",
WorksFilter::HasAffiliation => "has-affiliation",
WorksFilter::AlternativeId => "alternative-id",
WorksFilter::ArticleNumber => "article-number",
WorksFilter::HasAbstract => "has-abstract",
WorksFilter::HasClinicalTrialNumber => "has-clinical-trial-number ",
WorksFilter::ContentDomain(_) => "content-domain",
WorksFilter::HasContentDomain => "has-content-domain",
WorksFilter::HasDomainRestriction => "has-domain-restriction",
WorksFilter::HasRelation => "has-relation",
WorksFilter::RelationType => "relation.type",
WorksFilter::RelationObject => "relation.object",
WorksFilter::RelationObjectType(_) => "relation.object-type",
}
}
}
impl ParamFragment for WorksFilter {
fn key(&self) -> Cow<str> {
Cow::Borrowed(self.name())
}
fn value(&self) -> Option<Cow<str>> {
match self {
WorksFilter::Funder(s)
| WorksFilter::Location(s)
| WorksFilter::Prefix(s)
| WorksFilter::Member(s)
| WorksFilter::LicenseUrl(s)
| WorksFilter::LicenseVersion(s)
| WorksFilter::FullTextVersion(s)
| WorksFilter::FullTextType(s)
| WorksFilter::FullTextApplication(s)
| WorksFilter::Archive(s)
| WorksFilter::Orcid(s)
| WorksFilter::Issn(s)
| WorksFilter::Isbn(s)
| WorksFilter::Directory(s)
| WorksFilter::Doi(s)
| WorksFilter::Updates(s)
| WorksFilter::ContainerTitle(s)
| WorksFilter::CategoryName(s)
| WorksFilter::AwardNumber(s)
| WorksFilter::TypeName(s)
| WorksFilter::AwardFunder(s)
| WorksFilter::AssertionGroup(s)
| WorksFilter::Assertion(s)
| WorksFilter::ContentDomain(s)
| WorksFilter::RelationObjectType(s) => Some(Cow::Borrowed(s.as_str())),
WorksFilter::ReferenceVisibility(vis) => Some(Cow::Borrowed(vis.as_str())),
WorksFilter::FromIndexDate(d)
| WorksFilter::UntilIndexDate(d)
| WorksFilter::FromDepositDate(d)
| WorksFilter::UntilDepositDate(d)
| WorksFilter::FromUpdateDate(d)
| WorksFilter::UntilUpdateDate(d)
| WorksFilter::FromCreatedDate(d)
| WorksFilter::UntilCreatedDate(d)
| WorksFilter::FromPubDate(d)
| WorksFilter::UntilPubDate(d)
| WorksFilter::FromOnlinePubDate(d)
| WorksFilter::UntilOnlinePubDate(d)
| WorksFilter::FromPrintPubDate(d)
| WorksFilter::UntilPrintPubDate(d)
| WorksFilter::FromPostedDate(d)
| WorksFilter::UntilPostedDate(d)
| WorksFilter::FromAcceptedDate(d)
| WorksFilter::UntilAcceptedDate(d) => {
Some(Cow::Owned(d.format("%Y-%m-%d").to_string()))
}
WorksFilter::Type(t) => Some(Cow::Borrowed(t.id())),
_ => Some(Cow::Borrowed("true")),
}
}
}
impl Filter for WorksFilter {}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FieldQuery {
pub name: String,
pub value: String,
}
impl FieldQuery {
pub fn title(title: &str) -> Self {
Self {
name: "title".to_string(),
value: title.to_string(),
}
}
pub fn container_title(container_title: &str) -> Self {
Self {
name: "container-title".to_string(),
value: container_title.to_string(),
}
}
pub fn author(author: &str) -> Self {
Self {
name: "author".to_string(),
value: author.to_string(),
}
}
pub fn editor(editor: &str) -> Self {
Self {
name: "editor".to_string(),
value: editor.to_string(),
}
}
pub fn chair(chair: &str) -> Self {
Self {
name: "chair".to_string(),
value: chair.to_string(),
}
}
pub fn translator(translator: &str) -> Self {
Self {
name: "translator".to_string(),
value: translator.to_string(),
}
}
pub fn contributor(contributor: &str) -> Self {
Self {
name: "contributor".to_string(),
value: contributor.to_string(),
}
}
pub fn bibliographic(bibliographic: &str) -> Self {
Self {
name: "bibliographic".to_string(),
value: bibliographic.to_string(),
}
}
pub fn affiliation(affiliation: &str) -> Self {
Self {
name: "affiliation".to_string(),
value: affiliation.to_string(),
}
}
}
impl CrossrefQueryParam for FieldQuery {
fn param_key(&self) -> Cow<str> {
Cow::Borrowed(&self.name)
}
fn param_value(&self) -> Option<Cow<str>> {
Some(Cow::Owned(format_query(&self.value)))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum WorkResultControl {
Standard(ResultControl),
Cursor {
token: Option<String>,
rows: Option<usize>,
},
}
impl WorkResultControl {
pub fn new_cursor() -> Self {
WorkResultControl::Cursor {
token: None,
rows: None,
}
}
pub fn cursor(token: &str) -> Self {
WorkResultControl::Cursor {
token: Some(token.to_string()),
rows: None,
}
}
}
impl CrossrefQueryParam for WorkResultControl {
fn param_key(&self) -> Cow<str> {
match self {
WorkResultControl::Standard(s) => s.param_key(),
WorkResultControl::Cursor { token, .. } => Cow::Owned(format!(
"cursor={}",
token.as_ref().map(String::as_str).unwrap_or("*")
)),
}
}
fn param_value(&self) -> Option<Cow<str>> {
match self {
WorkResultControl::Standard(s) => s.param_value(),
WorkResultControl::Cursor { rows, .. } => match rows {
Some(r) => Some(Cow::Owned(format!("rows={}", r))),
_ => None,
},
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Works {
Identifier(String),
Query(WorksQuery),
Agency(String),
}
impl Works {
pub fn doi(doi: &str) -> Self {
Works::Identifier(doi.to_string())
}
pub fn agency_for_doi(doi: &str) -> Self {
Works::Agency(doi.to_string())
}
}
impl CrossrefRoute for Works {
fn route(&self) -> Result<String> {
match self {
Works::Identifier(s) => Ok(format!("{}/{}", Component::Works.route()?, s)),
Works::Agency(s) => Ok(format!("{}/{}/agency", Component::Works.route()?, s)),
Works::Query(query) => {
let query = query.route()?;
if query.is_empty() {
Component::Works.route()
} else {
Ok(format!("{}?{}", Component::Works.route()?, query))
}
}
}
}
}
impl CrossrefQuery for Works {
fn resource_component(self) -> ResourceComponent {
ResourceComponent::Works(self)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorksCombined {
pub id: String,
pub query: WorksQuery,
}
impl WorksCombined {
pub fn new(id: &str, query: WorksQuery) -> Self {
WorksCombined {
id: id.to_string(),
query,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct WorksQuery {
pub free_form_queries: Vec<String>,
pub field_queries: Vec<FieldQuery>,
pub filter: Vec<WorksFilter>,
pub sort: Option<Sort>,
pub order: Option<Order>,
pub facets: Vec<FacetCount>,
pub result_control: Option<WorkResultControl>,
pub sample: Option<usize>,
}
impl WorksQuery {
pub fn empty() -> Self {
WorksQuery::default()
}
pub fn random(len: usize) -> Self {
WorksQuery::default().sample(len)
}
pub fn new() -> Self {
WorksQuery::default()
}
pub fn sample(mut self, len: usize) -> Self {
self.sample = Some(len);
self
}
pub fn query(mut self, query: &str) -> Self {
self.free_form_queries.push(query.to_string());
self
}
pub fn queries<T: ToString>(mut self, queries: &[T]) -> Self {
self.free_form_queries
.extend(queries.iter().map(T::to_string));
self
}
pub fn field_query(mut self, query: FieldQuery) -> Self {
self.field_queries.push(query);
self
}
pub fn field_queries(mut self, queries: Vec<FieldQuery>) -> Self {
self.field_queries.extend(queries.into_iter());
self
}
pub fn filter(mut self, filter: WorksFilter) -> Self {
self.filter.push(filter);
self
}
pub fn sort(mut self, sort: Sort) -> Self {
self.sort = Some(sort);
self
}
pub fn order(mut self, order: Order) -> Self {
self.order = Some(order);
self
}
pub fn facet(mut self, facet: FacetCount) -> Self {
self.facets.push(facet);
self
}
pub fn next_cursor(mut self, cursor: &str) -> Self {
let rows = match self.result_control {
Some(WorkResultControl::Standard(ResultControl::Rows(rows))) => Some(rows),
_ => None,
};
self.result_control = Some(WorkResultControl::Cursor {
token: Some(cursor.to_string()),
rows,
});
self
}
pub fn new_cursor(mut self) -> Self {
self.result_control = Some(WorkResultControl::new_cursor());
self
}
pub fn result_control(mut self, result_control: WorkResultControl) -> Self {
self.result_control = Some(result_control);
self
}
}
impl CrossrefRoute for WorksQuery {
fn route(&self) -> Result<String> {
let mut params = Vec::new();
if let Some(sample) = self.sample {
return Ok(format!("sample={}", sample));
}
if !self.free_form_queries.is_empty() {
params.push(Cow::Owned(format!(
"query={}",
format_queries(&self.free_form_queries)
)));
}
if !self.field_queries.is_empty() {
params.extend(self.field_queries.iter().map(CrossrefQueryParam::param))
}
if !self.filter.is_empty() {
params.push(self.filter.param());
}
if !self.facets.is_empty() {
params.push(self.facets.param());
}
if let Some(sort) = &self.sort {
params.push(sort.param());
}
if let Some(order) = &self.order {
params.push(order.param());
}
if let Some(rc) = &self.result_control {
params.push(rc.param());
}
Ok(params.join("&"))
}
}
impl CrossrefParams for WorksQuery {
type Filter = WorksFilter;
fn query_terms(&self) -> &[String] {
&self.free_form_queries
}
fn filters(&self) -> &[Self::Filter] {
&self.filter
}
fn sort(&self) -> Option<&Sort> {
self.sort.as_ref()
}
fn order(&self) -> Option<&Order> {
self.order.as_ref()
}
fn facets(&self) -> &[FacetCount] {
&self.facets
}
fn result_control(&self) -> Option<&ResultControl> {
if let Some(WorkResultControl::Standard(ref std)) = self.result_control {
Some(std)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serialize_works_ident() {
let works = Works::doi("10.1037/0003-066X.59.1.29");
assert_eq!("/works/10.1037/0003-066X.59.1.29", &works.route().unwrap())
}
}