use crate::analyzer::host::discovery::CitationFormat;
use crate::io::api::{ApiResult, Configuration, Param, Params, RemoteResource, INCLUDED_ENDPOINTS};
use crate::param;
use crate::util::constants::app::DEFAULT_CITEAS_DOMAIN;
use crate::util::constants::env::{CITEAS_API_TOKEN, CITEAS_SERVER_HOST};
use acorn_core::options::{ApiExtension, ApiOptions};
use acorn_core::util::Searchable;
use acorn_schema::pid::{Arxiv, PersistentIdentifier, PublicationIdentifierType, DOI};
use async_trait::async_trait;
use color_eyre::eyre::eyre;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
pub type Options = ApiOptions<Extension, Param>;
#[async_trait]
pub trait ToCitations {
async fn to_citations(&self) -> ApiResult<Citations>;
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Author {
pub given: String,
pub family: String,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Citation {
#[serde(alias = "citation")]
pub text: String,
pub style_fullname: String,
pub style_shortname: String,
}
#[derive(Clone, Debug, Deserialize, Serialize, Default)]
pub struct Citations {
pub citations: Vec<Citation>,
pub exports: Vec<Export>,
pub metadata: Metadata,
pub name: String,
pub provenance: Vec<Provenance>,
pub url: String,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Export {
pub export: String,
pub export_name: String,
}
#[derive(Clone, Debug, Default)]
pub struct Extension;
#[derive(Clone, Debug, Deserialize, Serialize, Default)]
pub struct Metadata {
pub author: Vec<Author>,
pub categories: Vec<String>,
pub contributor: Vec<Author>,
#[serde(alias = "DOI")]
pub doi: String,
pub id: String,
pub publisher: String,
#[serde(rename = "type")]
pub resource_type: String,
pub title: String,
#[serde(alias = "URL")]
pub url: String,
pub year: u16,
}
#[skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Provenance {
pub additional_content_url: Option<String>,
pub content_url: Option<String>,
pub original_url: Option<String>,
pub found_via_proxy_type: Option<String>,
pub has_content: bool,
pub host: Option<String>,
pub name: String,
pub parent_step_name: String,
pub parent_subject: Option<String>,
pub subject: String,
pub key_word: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct StatusResponse {
pub documentation_url: String,
pub msg: String,
pub version: String,
}
#[async_trait]
impl ToCitations for Arxiv {
async fn to_citations(&self) -> ApiResult<Citations> {
PublicationIdentifierType::from(self).to_citations().await
}
}
impl Citations {
pub fn match_style(self, value: CitationFormat) -> Option<Citation> {
let citations = self.citations;
let normalized_value = value.to_string();
let result = citations
.iter()
.find(|citation| citation.style_shortname.eq_ignore_ascii_case(&normalized_value));
result.cloned().or_else(|| citations.first().cloned())
}
}
#[async_trait]
impl ToCitations for DOI {
async fn to_citations(&self) -> ApiResult<Citations> {
PublicationIdentifierType::from(self).to_citations().await
}
}
impl ApiExtension for Extension {
fn default_domain() -> String {
String::from(DEFAULT_CITEAS_DOMAIN)
}
fn env_token_var() -> &'static str {
CITEAS_API_TOKEN
}
fn env_domain_var() -> &'static str {
CITEAS_SERVER_HOST
}
}
#[async_trait]
impl ToCitations for PublicationIdentifierType {
async fn to_citations(&self) -> ApiResult<Citations> {
let value = match self {
| Self::Doi(doi) => Some(doi.to_string()),
| Self::Arxiv(arxiv) => Some(arxiv.url()),
| Self::Unknown => None,
};
match value {
| Some(value) => {
let identifier = urlencoding::encode(&value);
let options = Options::from_env().with_params(vec![param!(TemplateValue, "identifier", identifier.as_ref())]);
search(&options).await
}
| None => Err(eyre!("Unsupported publication identifier")),
}
}
}
pub async fn is_healthy() -> bool {
match status().await {
| Ok(StatusResponse { msg, .. }) => msg.eq_ignore_ascii_case("Don't panic"),
| Err(_) => false,
}
}
pub async fn search(options: &Options) -> ApiResult<Citations> {
let name = "CiteAs";
let action = "record";
let params = Params::new().with_custom(options.params()).build();
let data = Some(params);
match INCLUDED_ENDPOINTS.find_by_name(name) {
| Some(endpoint) => {
let response = endpoint.invoke(action, data).await;
endpoint.handle::<Citations>(response)
}
| None => Err(eyre!("{name} API endpoint not found")),
}
}
pub async fn status() -> ApiResult<StatusResponse> {
let name = "CiteAs";
let action = "status";
let data = None;
match INCLUDED_ENDPOINTS.find_by_name(name) {
| Some(endpoint) => {
let response = endpoint.invoke(action, data).await;
endpoint.handle::<StatusResponse>(response)
}
| None => Err(eyre!("{name} API endpoint not found")),
}
}