hts 0.1.17

Rust binding for htslib
Documentation
use crate::{
    alloc::{MemoryPoolObject, PooledObject},
    HtsResult,
};
use crate::{
    hts_ffi::{
        bcf_destroy, bcf_get_fmt, bcf_get_info, bcf_init, bcf_is_snp, bcf_unpack, htslib_obj,
        vcf_format, HtslibReturnValue, BCF_DT_CTG, BCF_DT_ID, BCF_UN_FLT, BCF_UN_FMT, BCF_UN_INFO,
        BCF_UN_STR,
    },
    Nucleotide,
};
use std::{
    ffi::CStr,
    fmt::{Display, Formatter, Result as FmtResult},
    ptr::NonNull,
};

use super::{info::VcfRecordInfo, PerSampleInfo, VcfFile};

impl MemoryPoolObject for htslib_obj::Bcf {
    fn alloc() -> Option<NonNull<Self>> {
        Some(unsafe { NonNull::new_unchecked(bcf_init().into_hts_result().ok()?) })
    }

    fn dealloc(mut obj: NonNull<Self>) {
        unsafe { bcf_destroy(obj.as_mut()) }
    }
}

pub trait VcfReader {
    fn read_next(&self) -> HtsResult<Option<VcfRecord>>;
}

pub struct VcfRecord<'a> {
    inner: PooledObject<'a, htslib_obj::Bcf>,
    file: &'a VcfFile,
}

impl<'a> Display for VcfRecord<'a> {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        let mut buf = htslib_obj::KString::new();
        if unsafe { vcf_format(self.file.header(), self.inner() as _, &mut buf as _) } < 0 {
            return Err(std::fmt::Error);
        }
        extern "C" {
            fn free(s: *mut std::ffi::c_void);
        }

        let s = unsafe { std::ffi::CStr::from_ptr(buf.s) }
            .to_str()
            .unwrap_or("<Enocding Error>");

        write!(f, "{}", s)?;

        unsafe {
            free(buf.s as _);
        }

        Ok(())
    }
}

impl<'a> VcfRecord<'a> {
    pub(crate) fn from_inner(
        inner: PooledObject<'a, htslib_obj::Bcf>,
        file: &'a VcfFile,
    ) -> HtsResult<Self> {
        Ok(Self { inner, file })
    }

    pub(crate) fn inner(&self) -> &htslib_obj::Bcf {
        &self.inner
    }

    /// This function is only used for HTSLIB's lazy propagation feature.
    /// As HTSLIB never move the propagated values, it doesn't voilate the
    /// borrowing rule in Rust code, as all the mutable borrow is dead after
    /// finishing the htslib call
    unsafe fn get_mut_inner(&self) -> *mut htslib_obj::Bcf {
        self.inner() as *const _ as *mut _
    }

    unsafe fn unpack_and_call<
        F: Fn(&htslib_obj::Bcf, &htslib_obj::BcfHeader) -> HtsResult<T>,
        T,
    >(
        &self,
        level: u32,
        f: F,
    ) -> HtsResult<T> {
        let bcf_obj = self.get_mut_inner();
        let hdr_obj = self.file.header() as *const _;
        bcf_unpack(bcf_obj, level as i32).into_hts_result()?;
        f(&*bcf_obj, &*hdr_obj)
    }

    pub fn chrom_id(&self) -> i32 {
        self.inner().rid
    }

    pub fn chrom_name(&self) -> Option<&str> {
        let chrom_id = self.chrom_id();
        let header = self.file.header();
        if chrom_id < 0 || chrom_id >= header.n[BCF_DT_CTG as usize] {
            return None;
        }

        let raw_name = unsafe { (*header.id[BCF_DT_CTG as usize]).key };
        unsafe { CStr::from_ptr(raw_name).to_str().ok() }
    }

    pub fn id(&self) -> HtsResult<Option<&str>> {
        unsafe {
            self.unpack_and_call(BCF_UN_STR, |hdr, _| {
                let raw_id = (*hdr).d.id;
                Ok(CStr::from_ptr(raw_id).to_str().ok())
            })
        }
    }

    pub fn pos(&self) -> i64 {
        self.inner().pos
    }

    pub fn ref_len(&self) -> i64 {
        self.inner().rlen
    }

    pub fn qual(&self) -> f32 {
        self.inner().qual
    }

    pub fn is_snp(&self) -> bool {
        unsafe { bcf_is_snp(self.get_mut_inner()) > 0 }
    }

    pub(crate) fn get_fmt_raw(&self, key: &[u8]) -> HtsResult<&htslib_obj::BcfFmt> {
        unsafe {
            let fmt_ptr = bcf_get_fmt(
                self.file.header() as _,
                self.get_mut_inner(),
                key.as_ptr() as _,
            )
            .into_hts_result()?;

            Ok(&*fmt_ptr)
        }
    }

    pub fn per_sample_info(&self, key: &[u8]) -> HtsResult<PerSampleInfo> {
        Ok(PerSampleInfo::from_inner(
            self.get_fmt_raw(key)?,
            self.file,
            self.inner().n_sample(),
        ))
    }

    pub(crate) fn get_info_raw(&self, key: &[u8]) -> Option<&htslib_obj::BcfInfo> {
        // At this point, HTSLIB calls the bcf_unpack, no need to use the unpack_and_call wrapper
        unsafe {
            bcf_get_info(
                self.file.header() as _,
                self.get_mut_inner(),
                key.as_ptr() as _,
            )
            .as_ref()
        }
    }

    pub fn get_info(&self, key: &[u8]) -> Option<VcfRecordInfo> {
        let raw = self.get_info_raw(key)?;
        Some(VcfRecordInfo::from_inner(raw, self.file))
    }

    pub fn format_iter(&self) -> HtsResult<impl Iterator<Item = PerSampleInfo<'_>>> {
        let all_formats = unsafe {
            self.unpack_and_call(BCF_UN_FMT, |rec, _| {
                let n_fmts = rec.n_fmt() as usize;
                let raw_fmts = rec.d.fmt;
                Ok(std::slice::from_raw_parts(raw_fmts, n_fmts))
            })?
        };
        Ok(all_formats
            .into_iter()
            .map(move |fmt| PerSampleInfo::from_inner(fmt, self.file, self.inner().n_sample())))
    }

    pub fn info_iter(&self) -> HtsResult<impl Iterator<Item = VcfRecordInfo<'_>>> {
        let all_info = unsafe {
            self.unpack_and_call(BCF_UN_INFO, |rec, _| {
                let n_info = rec.n_info() as usize;
                let raw_info = rec.d.info;
                Ok(std::slice::from_raw_parts(raw_info, n_info))
            })?
        };
        Ok(all_info
            .into_iter()
            .map(move |info| VcfRecordInfo::from_inner(info, self.file)))
    }

    pub fn allele(&self) -> HtsResult<impl Iterator<Item = Option<&str>>> {
        let allele = unsafe {
            self.unpack_and_call(BCF_UN_STR, |bcf, _| {
                Ok(std::slice::from_raw_parts(
                    bcf.d.allele,
                    bcf.n_allele() as usize,
                ))
            })?
        };
        Ok(allele.into_iter().map(move |&all_ptr| {
            let raw_name = unsafe { CStr::from_ptr(all_ptr) };
            raw_name.to_str().ok()
        }))
    }

    pub(crate) fn get_filter_raw(&self) -> HtsResult<&[i32]> {
        unsafe {
            self.unpack_and_call(BCF_UN_FLT, |bcf, _| {
                Ok(std::slice::from_raw_parts(bcf.d.flt, bcf.d.n_flt as usize))
            })
        }
    }

    pub fn filters(&self) -> HtsResult<impl Iterator<Item = Option<&str>>> {
        let raw_filter_list = self.get_filter_raw()?;

        Ok(raw_filter_list.into_iter().map(move |&idx| {
            let raw_name =
                unsafe { (*self.file.header().id[BCF_DT_ID as usize].offset(idx as isize)).key };
            let raw_name_cstr = unsafe { CStr::from_ptr(raw_name) };
            raw_name_cstr.to_str().ok()
        }))
    }
    pub(crate) fn get_ref_seq_raw(&self) -> HtsResult<&CStr> {
        unsafe {
            let ptr = self.unpack_and_call(BCF_UN_STR, |bcf, _| Ok(bcf.d.als))?;
            Ok(CStr::from_ptr(ptr))
        }
    }

    pub fn ref_seq_str(&self) -> HtsResult<Option<&str>> {
        self.get_ref_seq_raw().map(|s| s.to_str().ok())
    }

    pub fn ref_seq(&self) -> HtsResult<impl Iterator<Item = Nucleotide> + '_> {
        let raw_seq = self.get_ref_seq_raw()?;
        Ok(raw_seq
            .to_bytes()
            .into_iter()
            .filter_map(|&a| Nucleotide::from_ascii(a)))
    }

    pub(crate) fn get_alt_seq_raw(&self) -> HtsResult<&CStr> {
        let ref_seq = self.get_ref_seq_raw()?;
        let alt_seq = ref_seq.to_bytes_with_nul().last().unwrap() as *const u8;
        unsafe { Ok(CStr::from_ptr(alt_seq.offset(1) as _)) }
    }

    pub fn alt_seq_str(&self) -> HtsResult<Option<&str>> {
        self.get_alt_seq_raw().map(|s| s.to_str().ok())
    }

    pub fn alt_seq(&self) -> HtsResult<impl Iterator<Item = Nucleotide> + '_> {
        let raw_seq = self.get_alt_seq_raw()?;
        Ok(raw_seq
            .to_bytes()
            .into_iter()
            .filter_map(|&a| Nucleotide::from_ascii(a)))
    }
}