mod field;
mod search;
#[cfg(test)]
mod tests;
use std::fmt::Write as _;
use url::Url;
pub use self::{
field::{BooleanOp, Combine, Field, FieldGroup, FieldType},
search::{NonEmptySearchQuery, SearchQuery},
};
use crate::id::Identifier;
#[derive(Debug, Clone, Copy, Default)]
pub enum SortBy {
#[default]
Relevance,
LastUpdatedDate,
SubmittedDate,
}
#[derive(Debug, Clone, Copy, Default)]
pub enum SortOrder {
Ascending,
#[default]
Descending,
}
pub struct IdList<'q> {
buffer: &'q mut String,
}
impl IdList<'_> {
pub fn push<I: Identifier>(&mut self, id: &I) -> &mut Self {
if !self.buffer.is_empty() {
self.buffer.push(',');
}
id.write_identifier(self.buffer);
self
}
pub fn extend<I: Identifier, T: IntoIterator<Item = I>>(&mut self, ids: T) -> &mut Self {
let mut id_iter = ids.into_iter();
if self.buffer.is_empty() {
match id_iter.next() {
Some(first) => {
first.write_identifier(self.buffer);
}
None => return self,
}
}
for id in id_iter {
id.write_identifier(self.buffer);
}
self
}
pub fn clear(&mut self) -> &mut Self {
self.buffer.clear();
self
}
}
#[derive(Debug, Default, Clone)]
pub struct Query {
search_query: String,
id_list: String,
pagination: Option<(u16, u16)>,
sort: Option<(SortBy, SortOrder)>,
http: bool,
}
impl Query {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.search_query.is_empty() && self.id_list.is_empty()
}
#[must_use]
pub fn url(&self) -> Url {
let mut url = Url::parse("https://export.arxiv.org/api/query").unwrap();
if self.http {
let _ = url.set_scheme("http");
}
let mut query_pairs = url.query_pairs_mut();
if !self.search_query.is_empty() {
query_pairs.append_pair("search_query", &self.search_query);
}
if !self.id_list.is_empty() {
query_pairs.append_pair("id_list", &self.id_list);
}
if let Some((start, max_results)) = self.pagination {
let mut scratch: String = String::with_capacity(5);
let _ = write!(&mut scratch, "{start}");
query_pairs.append_pair("start", &scratch);
scratch.clear();
let _ = write!(&mut scratch, "{max_results}");
query_pairs.append_pair("max_results", &scratch);
}
if let Some((sort_by, sort_order)) = self.sort {
let s = match sort_by {
SortBy::Relevance => "relevance",
SortBy::LastUpdatedDate => "lastUpdatedDate",
SortBy::SubmittedDate => "submittedDate",
};
query_pairs.append_pair("sortBy", s);
let s = match sort_order {
SortOrder::Ascending => "ascending",
SortOrder::Descending => "descending",
};
query_pairs.append_pair("sortOrder", s);
}
drop(query_pairs);
url
}
pub fn http(&mut self) -> &mut Self {
self.http = true;
self
}
pub fn https(&mut self) -> &mut Self {
self.http = false;
self
}
pub fn search_query(&mut self) -> SearchQuery<'_> {
SearchQuery {
buffer: &mut self.search_query,
}
}
pub fn id_list(&mut self) -> IdList<'_> {
IdList {
buffer: &mut self.id_list,
}
}
pub fn paginate(&mut self, start: u16, max_results: u16) -> Option<&mut Self> {
if start <= 30000 && max_results <= 2000 {
self.pagination = Some((start, max_results));
Some(self)
} else {
None
}
}
pub fn sort(&mut self, by: SortBy, order: SortOrder) -> &mut Self {
self.sort = Some((by, order));
self
}
}