mehari 0.42.0

Variant effect prediction all in Rust
use super::CustomError;
use crate::annotate::seqvars::provider::Provider;
use actix_web::{
    get,
    web::{self, Data, Json, Path},
};
use itertools::Itertools;

/// Assembly to be passed on the command line.
///
/// Copy from annonars with extension to derive `utoipa::ToSchema`.
#[derive(
    Debug,
    Clone,
    Copy,
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    clap::ValueEnum,
    serde::Deserialize,
    serde::Serialize,
    utoipa::ToSchema,
)]
#[serde(rename_all = "snake_case")]
pub enum Assembly {
    /// GRCh37
    Grch37,
    /// GRCh38
    Grch38,
}

impl From<biocommons_bioutils::assemblies::Assembly> for Assembly {
    fn from(assembly: biocommons_bioutils::assemblies::Assembly) -> Self {
        match assembly {
            biocommons_bioutils::assemblies::Assembly::Grch37
            | biocommons_bioutils::assemblies::Assembly::Grch37p10 => Assembly::Grch37,
            biocommons_bioutils::assemblies::Assembly::Grch38 => Assembly::Grch38,
        }
    }
}

impl TryFrom<&str> for Assembly {
    type Error = anyhow::Error;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        match value.to_lowercase().as_str() {
            "grch37" | "grch37p10" => Ok(Assembly::Grch37),
            "grch38" => Ok(Assembly::Grch38),
            _ => Err(anyhow::anyhow!("Unsupported assembly: {}", value)),
        }
    }
}

/// Software version specification.
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize, utoipa::ToSchema)]
pub struct SoftwareVersions {
    /// Version of `mehari`.
    pub mehari: String,
    /// Version of the `hgvs` crate.
    pub hgvs_rs: String,
}

impl SoftwareVersions {
    /// Create a new `SoftwareVersions` instance.
    pub fn new() -> Result<Self, anyhow::Error> {
        let mehari = crate::built_info::PKG_VERSION.to_string();
        let hgvs_rs = crate::built_info::DEPENDENCIES
            .iter()
            .find(|(name, _)| name == &"hgvs")
            .map(|(_, version)| version.to_string())
            .ok_or_else(|| anyhow::anyhow!("Failed to find hgvs version"))?;

        Ok(Self { mehari, hgvs_rs })
    }
}

/// Specification of data version for a given genome build.
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize, utoipa::ToSchema)]
pub struct DataVersionEntry {
    /// Assembly for which the data version is specified.
    pub assembly: String,
    /// Version of the RefSeq database, if any.
    pub version_refseq: Option<String>,
    /// Version of the Ensembl database, if any.
    pub version_ensembl: Option<String>,
    /// Version of annotation used.
    pub version_annotation: String,
    /// Name of the annotation used.
    pub annotation_name: String,
}

impl DataVersionEntry {
    /// Create a new `DataVersionEntry` instance from `Provider`.`
    pub fn from_provider(provider: &Provider) -> Self {
        let assembly = provider.assembly().clone();
        let versions = &provider.tx_seq_db.source_version;
        let version_for = |source_name: &str| {
            let version = versions
                .iter()
                .filter(|&v| v.source_name == source_name)
                .map(|v| v.source_version.clone())
                .collect::<Vec<_>>()
                .join(",");
            (!version.is_empty()).then_some(version)
        };
        let version_refseq = version_for("refseq");
        let version_ensembl = version_for("ensembl");
        let version_annotation = versions
            .iter()
            .map(|v| v.annotation_version.clone())
            .join(",");
        let annotation_name = versions.iter().map(|v| v.annotation_name.clone()).join(",");

        Self {
            assembly,
            version_refseq,
            version_ensembl,
            version_annotation,
            annotation_name,
        }
    }
}

/// Response of the `/api/v1/version` endpoint.
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize, utoipa::ToSchema)]
pub struct VersionsInfoResponse {
    /// Software versions specification.
    pub software: SoftwareVersions,
    /// Data versions specification.
    pub data: Vec<DataVersionEntry>,
}

impl VersionsInfoResponse {
    /// Create a new `VersionsInfoResponse` instance from the given `WebServerData``.
    pub fn from_web_server_data(data: &super::WebServerData) -> Result<Self, anyhow::Error> {
        let software = SoftwareVersions::new()?;
        let data = data
            .provider
            .values()
            .map(|provider| DataVersionEntry::from_provider(provider.as_ref()))
            .collect();

        Ok(Self { software, data })
    }
}

/// Query for consequence of a variant.
#[allow(clippy::unused_async)]
#[utoipa::path(
    get,
    operation_id = "versionsInfo",
    responses(
        (status = 200, description = "Version information.", body = VersionsInfoResponse),
        (status = 500, description = "Internal server error.", body = CustomError)
    )
)]
#[get("/api/v1/versionsInfo")]
async fn handle(
    data: Data<super::WebServerData>,
    _path: Path<()>,
    _query: web::Query<()>,
) -> actix_web::Result<Json<VersionsInfoResponse>, CustomError> {
    Ok(Json(
        VersionsInfoResponse::from_web_server_data(data.into_inner().as_ref())
            .map_err(|e| CustomError::new(anyhow::anyhow!("Problem determining version: {}", e)))?,
    ))
}