use super::ReaderError;
use super::formats::{self, Format, FormatDetectionError};
use crate::{
bam::{
BamHeader, IndexedBamReader,
record_store::{CustomizeRecordStore, RecordStore},
},
cram::reader::IndexedCramReader,
sam::reader::IndexedSamReader,
};
use seqair_types::Pos0;
use std::{
io::{Read, Seek},
path::Path,
};
use tracing::instrument;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct FetchCounts {
pub fetched: usize,
pub kept: usize,
}
#[non_exhaustive]
pub enum IndexedReader<R: Read + Seek = std::fs::File> {
Bam(IndexedBamReader<R>),
Sam(IndexedSamReader<R>),
Cram(Box<IndexedCramReader<R>>),
}
#[cfg(feature = "fuzz")]
pub type CursorReader = IndexedReader<std::io::Cursor<Vec<u8>>>;
impl<R: Read + Seek> std::fmt::Debug for IndexedReader<R> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Bam(r) => r.fmt(f),
Self::Sam(r) => r.fmt(f),
Self::Cram(r) => r.fmt(f),
}
}
}
impl<R: Read + Seek> IndexedReader<R> {
pub fn header(&self) -> &BamHeader {
match self {
Self::Bam(r) => r.header(),
Self::Sam(r) => r.header(),
Self::Cram(r) => r.header(),
}
}
pub fn fetch_into(
&mut self,
tid: u32,
start: Pos0,
end: Pos0,
store: &mut RecordStore,
) -> Result<usize, ReaderError> {
self.fetch_into_customized(tid, start, end, store, &mut ()).map(|c| c.kept)
}
pub fn fetch_into_customized<E: CustomizeRecordStore>(
&mut self,
tid: u32,
start: Pos0,
end: Pos0,
store: &mut RecordStore<E::Extra>,
customize: &mut E,
) -> Result<FetchCounts, ReaderError> {
match self {
Self::Bam(r) => r
.fetch_into_customized(tid, start, end, store, customize)
.map_err(ReaderError::from),
Self::Sam(r) => r
.fetch_into_customized(tid, start, end, store, customize)
.map_err(ReaderError::from),
Self::Cram(r) => r
.fetch_into_customized(tid, start, end, store, customize)
.map_err(ReaderError::from),
}
}
}
impl IndexedReader<std::fs::File> {
#[instrument(level = "debug", fields(path = %path.display()))]
pub fn open(path: &Path) -> Result<Self, ReaderError> {
match formats::detect(path)? {
Format::Bam => {
let reader = IndexedBamReader::open(path)?;
Ok(IndexedReader::Bam(reader))
}
Format::Sam => {
let reader = IndexedSamReader::open(path)?;
Ok(IndexedReader::Sam(reader))
}
Format::Cram => Err(FormatDetectionError::CramRequiresFasta.into()),
}
}
pub(crate) fn open_with_fasta(path: &Path, fasta_path: &Path) -> Result<Self, ReaderError> {
match formats::detect(path)? {
Format::Bam => {
let reader = IndexedBamReader::open(path)?;
Ok(IndexedReader::Bam(reader))
}
Format::Sam => {
let reader = IndexedSamReader::open(path)?;
Ok(IndexedReader::Sam(reader))
}
Format::Cram => {
let reader = IndexedCramReader::open(path, fasta_path)?;
Ok(IndexedReader::Cram(Box::new(reader)))
}
}
}
pub fn fork(&self) -> Result<Self, ReaderError> {
match self {
Self::Bam(r) => Ok(Self::Bam(r.fork()?)),
Self::Sam(r) => Ok(Self::Sam(r.fork()?)),
Self::Cram(r) => Ok(Self::Cram(Box::new(r.fork()?))),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write as _;
use tempfile::NamedTempFile;
#[test]
fn cram_requires_fasta() {
let mut f = NamedTempFile::new().expect("tempfile");
f.write_all(b"CRAM\x03\x00").expect("write");
f.flush().expect("flush");
let err = IndexedReader::open(f.path()).unwrap_err();
assert!(
matches!(err, ReaderError::Format { source: FormatDetectionError::CramRequiresFasta }),
"unexpected error: {err}"
);
}
}