noodles_bam/record/
cigar.rs

1use std::{fmt, io, mem};
2
3use noodles_sam::{self as sam, alignment::record::cigar::Op};
4
5const CHUNK_SIZE: usize = mem::size_of::<u32>();
6
7/// BAM record CIGAR operations.
8#[derive(Eq, PartialEq)]
9pub struct Cigar<'a>(&'a [u8]);
10
11impl<'a> Cigar<'a> {
12    pub(super) fn new(src: &'a [u8]) -> Self {
13        Self(src)
14    }
15
16    /// Returns whether there are any CIGAR operations.
17    pub fn is_empty(&self) -> bool {
18        self.0.is_empty()
19    }
20
21    /// Returns the number of CIGAR operations.
22    ///
23    /// This is _not_ the length of the buffer.
24    pub fn len(&self) -> usize {
25        self.0.len() / CHUNK_SIZE
26    }
27
28    /// Returns an iterator over CIGAR operations.
29    pub fn iter(&self) -> impl Iterator<Item = io::Result<Op>> + '_ {
30        use crate::record::codec::decoder::cigar::decode_op;
31
32        self.0.chunks(CHUNK_SIZE).map(|chunk| {
33            let buf = chunk
34                .try_into()
35                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
36            let n = u32::from_le_bytes(buf);
37            decode_op(n).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
38        })
39    }
40}
41
42impl sam::alignment::record::Cigar for Cigar<'_> {
43    fn is_empty(&self) -> bool {
44        self.is_empty()
45    }
46
47    fn len(&self) -> usize {
48        self.len()
49    }
50
51    fn iter(&self) -> Box<dyn Iterator<Item = io::Result<Op>> + '_> {
52        Box::new(self.iter())
53    }
54}
55
56impl AsRef<[u8]> for Cigar<'_> {
57    fn as_ref(&self) -> &[u8] {
58        self.0
59    }
60}
61
62impl fmt::Debug for Cigar<'_> {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        f.debug_list().entries(self.iter()).finish()
65    }
66}
67
68impl<'a> TryFrom<Cigar<'a>> for sam::alignment::record_buf::Cigar {
69    type Error = io::Error;
70
71    fn try_from(bam_cigar: Cigar<'a>) -> Result<Self, Self::Error> {
72        bam_cigar.iter().collect()
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn test_iter() -> io::Result<()> {
82        use sam::alignment::record::cigar::op::Kind;
83
84        let src = &[][..];
85        let cigar = Cigar::new(src);
86        assert!(cigar.iter().next().is_none());
87
88        let src = &[0x40, 0x00, 0x00, 0x00][..];
89        let cigar = Cigar::new(src);
90        let actual: Vec<_> = cigar.iter().collect::<io::Result<_>>()?;
91        let expected = [Op::new(Kind::Match, 4)];
92        assert_eq!(actual, expected);
93
94        let src = &[0x40, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00][..];
95        let cigar = Cigar::new(src);
96        let actual: Vec<_> = cigar.iter().collect::<io::Result<_>>()?;
97        let expected = [Op::new(Kind::Match, 4), Op::new(Kind::HardClip, 2)];
98        assert_eq!(actual, expected);
99
100        Ok(())
101    }
102}