mfs 0.2.1

Fetcher for scholarly metadata
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(),
        }
    }
}

// type id result source data
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", "-", "-")
        }
    }
}