imgt 0.2.0

Access the IMGT database from Rust
Documentation
//! Code to handle the PSI-MOD ontology
use std::{
    borrow::Cow,
    collections::HashMap,
    sync::{Arc, LazyLock},
};

use context_error::{BoxedError, Context, CreateError, combine_error};
use mzcv::{CVData, CVError, CVFile, CVIndex, CVSource, CVStructure, CVVersion, HashBufReader};

use crate::{Gene, Germline, Germlines, Species, parse::parse_dat};

/// A single shared static access to the static data in the ontologies for cases where no runtime
/// resolution is needed (like tests).
pub static STATIC_IMGT: LazyLock<CVIndex<IMGT>> = LazyLock::new(CVIndex::init_static);

/// IMGT antibody germlines
#[allow(
    missing_copy_implementations,
    missing_debug_implementations,
    clippy::upper_case_acronyms
)]
pub struct IMGT {}

impl CVData for Germline {
    type Index = (Species, Gene);

    fn index(&self) -> Option<Self::Index> {
        Some((self.species, self.name.clone()))
    }

    fn name(&self) -> Option<Cow<'_, str>> {
        Some(Cow::Owned(self.name.to_string()))
    }

    fn synonyms(&self) -> impl Iterator<Item = &str> {
        std::iter::empty()
    }
}

impl CVSource for IMGT {
    type Data = Germline;
    type Structure = HashMap<Species, Germlines>;

    fn cv_name() -> &'static str {
        "IMGT"
    }

    fn files() -> &'static [CVFile] {
        &[CVFile {
            name: "IMGT",
            extension: "dat",
            url: Some("https://imgt.org/download/LIGM-DB/imgt.dat.Z"),
            compression: mzcv::CVCompression::Lzw,
        }]
    }

    fn static_data() -> Option<(CVVersion, Self::Structure)> {
        #[cfg(not(feature = "internal-no-data"))]
        {
            use bincode::config::Configuration;
            let cache = bincode::decode_from_slice::<(CVVersion, Self::Structure), Configuration>(
                include_bytes!("IMGT.dat"),
                Configuration::default(),
            )
            .unwrap()
            .0;
            Some(cache)
        }
        #[cfg(feature = "internal-no-data")]
        None
    }

    fn parse(
        mut reader: impl Iterator<Item = HashBufReader<Box<dyn std::io::Read>, impl sha2::Digest>>,
    ) -> Result<
        (
            CVVersion,
            Self::Structure,
            Vec<BoxedError<'static, CVError>>,
        ),
        Vec<BoxedError<'static, CVError>>,
    > {
        let mut reader = reader.next().unwrap();
        let data = parse_dat(&mut reader);

        let (grouped, errors, release) = crate::combine::combine(data);

        let version = CVVersion {
            hash: reader.hash(),
            version: release.clone().map(|(_, _, _, r)| r),
            last_updated: release.as_ref().map(|(y, m, d, _)| (*y, *m, *d, 0, 0)),
        };

        let mut cv_errors = Vec::new();
        for (species, id, note) in errors {
            combine_error(
                &mut cv_errors,
                BoxedError::new(
                    CVError::ItemError,
                    "Error while parsing gene",
                    note,
                    Context::default().lines(0, format!("{species} {id}")),
                ),
            );
        }

        Ok((version, grouped, cv_errors))
    }
}

#[expect(
    clippy::implicit_hasher,
    reason = "Gave some issues with default and lifetimes, likely easily fixed but just could not be bothered"
)]
impl CVStructure<Germline> for HashMap<Species, Germlines> {
    type Index = (Species, Gene);
    type IterData<'a> = Box<dyn Iterator<Item = Arc<Germline>> + 'a>;
    type IterIndexed<'a> = Box<dyn Iterator<Item = (Self::Index, Arc<Germline>)> + 'a>;

    fn is_empty(&self) -> bool {
        self.is_empty()
    }

    fn len(&self) -> usize {
        self.values().fold(0, |acc, s| {
            acc + s.iter().fold(0, |acc, (_, g)| {
                acc + g.iter().fold(0, |acc, (_, g)| acc + g.len())
            })
        })
    }

    fn clear(&mut self) {
        self.clear();
    }

    fn add(&mut self, data: Arc<Germline>) {
        self.entry(data.species)
            .or_insert_with(|| Germlines::new(data.species))
            .insert(Arc::unwrap_or_clone(data));
    }

    fn iter_indexed(&self) -> Self::IterIndexed<'_> {
        Box::new(self.iter().flat_map(|(species, germlines)| {
            germlines.iter().flat_map(move |(_, germlines)| {
                germlines.iter().flat_map(move |(_, germlines)| {
                    germlines
                        .iter()
                        .map(move |germline| ((*species, germline.name.clone()), germline.clone()))
                })
            })
        }))
    }

    fn iter_data(&self) -> Self::IterData<'_> {
        Box::new(self.values().flat_map(|germlines| {
            germlines.iter().flat_map(|(_, germlines)| {
                germlines
                    .iter()
                    .flat_map(|(_, germlines)| germlines.iter().map(Clone::clone))
            })
        }))
    }

    fn index(&self, index: Self::Index) -> Option<Arc<Germline>> {
        self.get(&index.0).and_then(|germlines| germlines.find_germline(index.1))
    }

    fn remove(&mut self, index: Self::Index) {
        if let Some(germlines) = self.get_mut(&index.0) {
            germlines.remove_germline(index.1);
        }
    }
}