use crate::error::{Error, Result};
use crate::query::facet::FacetCount;
pub use crate::query::funders::{Funders, FundersQuery};
pub use crate::query::journals::Journals;
pub use crate::query::members::{Members, MembersQuery};
pub use crate::query::prefixes::Prefixes;
pub use crate::query::types::{Type, Types};
use crate::query::works::{Works, WorksFilter};
pub use crate::query::works::{WorksCombined, WorksQuery};
use chrono::NaiveDate;
use core::fmt::Debug;
use serde::Serialize;
use std::borrow::Cow;
use std::fmt;
pub trait CrossrefParams {
type Filter: Filter;
fn query_terms(&self) -> &[String];
fn filters(&self) -> &[Self::Filter];
fn sort(&self) -> Option<&Sort>;
fn order(&self) -> Option<&Order>;
fn facets(&self) -> &[FacetCount];
fn result_control(&self) -> Option<&ResultControl>;
}
macro_rules! impl_common_query {
($i:ident, $filter:ident) => {
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct $i {
pub queries: Vec<String>,
pub filter: Vec<$filter>,
pub sort: Option<Sort>,
pub order: Option<Order>,
pub facets: Vec<FacetCount>,
pub result_control: Option<ResultControl>,
}
impl $i {
pub fn empty() -> Self {
$i::default()
}
pub fn new() -> Self {
$i::default()
}
pub fn query(mut self, query: &str) -> Self {
self.queries.push(query.to_string());
self
}
pub fn filter(mut self, filter: $filter) -> Self {
self.filter.push(filter);
self
}
pub fn sort(mut self, sort: Sort) -> Self {
self.sort = Some(sort);
self
}
pub fn order_asc(mut self) -> Self {
self.order = Some(Order::Asc);
self
}
pub fn order_desc(mut self) -> Self {
self.order = Some(Order::Desc);
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 result_control(mut self, result_control: ResultControl) -> Self {
self.result_control = Some(result_control);
self
}
}
impl CrossrefParams for $i {
type Filter = $filter;
fn query_terms(&self) -> &[String] {
&self.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> {
self.result_control.as_ref()
}
}
impl CrossrefRoute for $i {
fn route(&self) -> Result<String> {
let mut params = Vec::new();
if !self.queries.is_empty() {
params.push(Cow::Owned(format!(
"query={}",
format_queries(&self.queries)
)));
}
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("&"))
}
}
};
}
pub mod facet;
pub mod funders;
pub mod journals;
pub mod members;
pub mod prefixes;
pub mod types;
pub mod works;
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
#[allow(missing_docs)]
pub enum Visibility {
Open,
Limited,
Closed,
}
impl Visibility {
pub fn as_str(&self) -> &str {
match self {
Visibility::Open => "open",
Visibility::Limited => "limited",
Visibility::Closed => "closed",
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
pub enum Order {
Asc,
Desc,
}
impl Order {
pub fn as_str(&self) -> &str {
match self {
Order::Asc => "asc",
Order::Desc => "desc",
}
}
}
impl CrossrefQueryParam for Order {
fn param_key(&self) -> Cow<str> {
Cow::Borrowed("order")
}
fn param_value(&self) -> Option<Cow<str>> {
Some(Cow::Borrowed(self.as_str()))
}
}
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
pub enum Sort {
Score,
Updated,
Deposited,
Indexed,
Published,
PublishedPrint,
PublishedOnline,
Issued,
IsReferencedByCount,
ReferenceCount,
}
impl Sort {
pub fn as_str(&self) -> &str {
match self {
Sort::Score => "score",
Sort::Updated => "updated",
Sort::Deposited => "deposited",
Sort::Indexed => "indexed",
Sort::Published => "published",
Sort::PublishedPrint => "published-print",
Sort::PublishedOnline => "published-online",
Sort::Issued => "issued",
Sort::IsReferencedByCount => "is-reference-by-count",
Sort::ReferenceCount => "reference-count",
}
}
}
impl CrossrefQueryParam for Sort {
fn param_key(&self) -> Cow<str> {
Cow::Borrowed("sort")
}
fn param_value(&self) -> Option<Cow<str>> {
Some(Cow::Borrowed(self.as_str()))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ResultControl {
Rows(usize),
Offset(usize),
RowsOffset {
rows: usize,
offset: usize,
},
Sample(usize),
}
impl CrossrefQueryParam for ResultControl {
fn param_key(&self) -> Cow<str> {
match self {
ResultControl::Rows(_) => Cow::Borrowed("rows"),
ResultControl::Offset(_) => Cow::Borrowed("offset"),
ResultControl::RowsOffset { rows, .. } => Cow::Owned(format!("rows={}", rows)),
ResultControl::Sample(_) => Cow::Borrowed("sample"),
}
}
fn param_value(&self) -> Option<Cow<str>> {
match self {
ResultControl::Rows(r) | ResultControl::Offset(r) | ResultControl::Sample(r) => {
Some(Cow::Owned(r.to_string()))
}
ResultControl::RowsOffset { offset, .. } => {
Some(Cow::Owned(format!("offset={}", offset)))
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Component {
Works,
Funders,
Prefixes,
Members,
Types,
Journals,
}
impl Component {
pub fn as_str(&self) -> &str {
match self {
Component::Works => "works",
Component::Funders => "funders",
Component::Prefixes => "prefixes",
Component::Members => "members",
Component::Types => "types",
Component::Journals => "journals",
}
}
}
impl CrossrefRoute for Component {
fn route(&self) -> Result<String> {
Ok(format!("/{}", self.as_str()))
}
}
#[allow(missing_docs)]
pub struct WorksRequest {
primary_component: Component,
query: WorksQuery,
id: Option<String>,
}
impl CrossrefRoute for WorksRequest {
fn route(&self) -> Result<String> {
unimplemented!()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ResourceComponent {
Works(Works),
Funders(Funders),
Prefixes(Prefixes),
Members(Members),
Types(Types),
Journals(Journals),
}
impl ResourceComponent {
pub fn primary_component(&self) -> Component {
match self {
ResourceComponent::Works(_) => Component::Works,
ResourceComponent::Funders(_) => Component::Funders,
ResourceComponent::Prefixes(_) => Component::Prefixes,
ResourceComponent::Members(_) => Component::Members,
ResourceComponent::Types(_) => Component::Types,
ResourceComponent::Journals(_) => Component::Journals,
}
}
}
impl fmt::Display for ResourceComponent {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.route().map_err(|_| fmt::Error)?)
}
}
impl CrossrefRoute for ResourceComponent {
fn route(&self) -> Result<String> {
match self {
ResourceComponent::Works(c) => c.route(),
ResourceComponent::Funders(c) => c.route(),
ResourceComponent::Prefixes(c) => c.route(),
ResourceComponent::Members(c) => c.route(),
ResourceComponent::Types(c) => c.route(),
ResourceComponent::Journals(c) => c.route(),
}
}
}
impl CrossrefQuery for ResourceComponent {
fn resource_component(self) -> ResourceComponent {
self
}
}
pub trait Filter: ParamFragment {}
impl<T: Filter> CrossrefQueryParam for Vec<T> {
fn param_key(&self) -> Cow<str> {
Cow::Borrowed("filter")
}
fn param_value(&self) -> Option<Cow<str>> {
Some(Cow::Owned(
self.iter()
.map(ParamFragment::fragment)
.collect::<Vec<_>>()
.join(","),
))
}
}
pub trait ParamFragment {
fn key(&self) -> Cow<str>;
fn value(&self) -> Option<Cow<str>>;
fn fragment(&self) -> Cow<str> {
if let Some(val) = self.value() {
Cow::Owned(format!("{}:{}", self.key(), val))
} else {
self.key()
}
}
}
pub trait CrossrefQueryParam {
fn param_key(&self) -> Cow<str>;
fn param_value(&self) -> Option<Cow<str>>;
fn param(&self) -> Cow<str> {
if let Some(val) = self.param_value() {
Cow::Owned(format!("{}={}", self.param_key(), val))
} else {
self.param_key()
}
}
}
impl<T: AsRef<str>> CrossrefQueryParam for (T, T) {
fn param_key(&self) -> Cow<str> {
Cow::Borrowed(self.0.as_ref())
}
fn param_value(&self) -> Option<Cow<str>> {
Some(Cow::Borrowed(self.1.as_ref()))
}
}
pub trait CrossrefRoute {
fn route(&self) -> Result<String>;
}
impl<T: CrossrefQueryParam> CrossrefRoute for AsRef<[T]> {
fn route(&self) -> Result<String> {
Ok(self
.as_ref()
.iter()
.map(CrossrefQueryParam::param)
.collect::<Vec<_>>()
.join("&"))
}
}
pub trait CrossrefQuery: CrossrefRoute {
fn resource_component(self) -> ResourceComponent;
fn to_url(&self, base_path: &str) -> Result<String> {
Ok(format!("{}{}", base_path, self.route()?))
}
}
pub(crate) fn format_query<T: AsRef<str>>(topic: T) -> String {
topic
.as_ref()
.split_whitespace()
.collect::<Vec<_>>()
.join("+")
}
pub(crate) fn format_queries<T: AsRef<str>>(topics: &[T]) -> String {
topics
.iter()
.map(format_query)
.collect::<Vec<_>>()
.join("+")
}