use axum::body::Body;
use axum::response::{IntoResponse, Response};
use clap::ValueEnum;
use hyper::StatusCode;
use serde::Deserialize;
use std::io;
use std::{fmt::Display, path::PathBuf};
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
#[derive(ValueEnum, Clone, Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum MetadataSource {
Crossref,
#[value(name = "datacite")]
DataCite,
#[value(name = "openalex")]
OpenAlex,
}
impl MetadataSource {
pub fn url_for_doi(&self, doi: &String) -> String {
match self {
MetadataSource::Crossref => format!("https://api.crossref.org/works/{}", doi),
MetadataSource::DataCite => format!("https://api.datacite.org/dois/{}", doi),
MetadataSource::OpenAlex => format!("https://api.openalex.org/works/doi:{}", doi),
}
}
}
impl Display for MetadataSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MetadataSource::Crossref => write!(f, "Crossref"),
MetadataSource::DataCite => write!(f, "DataCite"),
MetadataSource::OpenAlex => write!(f, "OpenAlex"),
}
}
}
#[derive(Clone)]
pub enum ReportStatus {
Ok(MetadataSource, String),
NotFound,
Error,
}
impl Display for ReportStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ReportStatus::Ok(_, _) => write!(f, "OK"),
ReportStatus::NotFound => write!(f, "Not found"),
ReportStatus::Error => write!(f, "Error"),
}
}
}
#[derive(Clone)]
pub struct ReportEntry {
pub doi: String,
pub status: ReportStatus,
}
impl IntoResponse for ReportEntry {
fn into_response(self) -> axum::response::Response {
match self.status {
ReportStatus::Ok(_, data) => Response::builder()
.status(StatusCode::OK)
.body(Body::from(data))
.unwrap(),
ReportStatus::NotFound => Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Body::from("Not found"))
.unwrap(),
ReportStatus::Error => Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(Body::from("Internal Server Error"))
.unwrap(),
}
}
}
pub async fn write_data(report_data: &Vec<ReportEntry>, path: &Option<PathBuf>) -> io::Result<()> {
if let Some(path) = path {
let mut file = File::create(path).await?;
for entry in report_data {
let _ = file.write_all(prepare_entry(entry).as_bytes()).await;
}
} else {
for entry in report_data {
print!("{}", prepare_entry(entry));
}
}
Ok(())
}
fn prepare_entry(entry: &ReportEntry) -> String {
match &entry.status {
ReportStatus::Ok(metadata_source, data) => {
format!(
"{}\t{}\t{}\t{}\t{}\n",
"pub", &entry.doi, "ok", metadata_source, data
)
}
ReportStatus::NotFound => {
format!(
"{}\t{}\t{}\t{}\t{}\n",
"pub", &entry.doi, "not_found", "-", "-"
)
}
ReportStatus::Error => {
format!("{}\t{}\t{}\t{}\t{}\n", "pub", &entry.doi, "error", "-", "-")
}
}
}