noodles_sam/alignment/
record.rs

1//! Alignment record.
2
3pub mod cigar;
4pub mod data;
5mod flags;
6pub mod mapping_quality;
7mod quality_scores;
8mod sequence;
9
10use std::io;
11
12use bstr::BStr;
13use noodles_core::Position;
14
15pub use self::{
16    cigar::Cigar, data::Data, flags::Flags, mapping_quality::MappingQuality,
17    quality_scores::QualityScores, sequence::Sequence,
18};
19use crate::{
20    header::{
21        record::value::{map::ReferenceSequence, Map},
22        ReferenceSequences,
23    },
24    Header,
25};
26
27/// An alignment record.
28pub trait Record {
29    /// Returns the name.
30    fn name(&self) -> Option<&BStr>;
31
32    /// Returns the flags.
33    fn flags(&self) -> io::Result<Flags>;
34
35    /// Returns the reference sequence ID.
36    fn reference_sequence_id<'r, 'h: 'r>(&'r self, header: &'h Header)
37        -> Option<io::Result<usize>>;
38
39    /// Returns the alignment start.
40    ///
41    /// This position is 1-based, inclusive.
42    fn alignment_start(&self) -> Option<io::Result<Position>>;
43
44    /// Returns the mapping quality.
45    fn mapping_quality(&self) -> Option<io::Result<MappingQuality>>;
46
47    /// Returns the CIGAR operations.
48    fn cigar(&self) -> Box<dyn Cigar + '_>;
49
50    /// Returns the mate reference sequence ID.
51    fn mate_reference_sequence_id<'r, 'h: 'r>(
52        &'r self,
53        header: &'h Header,
54    ) -> Option<io::Result<usize>>;
55
56    /// Returns the mate alignment start.
57    ///
58    /// This position is 1-based, inclusive.
59    fn mate_alignment_start(&self) -> Option<io::Result<Position>>;
60
61    /// Returns the template length.
62    fn template_length(&self) -> io::Result<i32>;
63
64    /// Returns the sequence.
65    fn sequence(&self) -> Box<dyn Sequence + '_>;
66
67    /// Returns the quality scores.
68    fn quality_scores(&self) -> Box<dyn QualityScores + '_>;
69
70    /// Returns the data.
71    fn data(&self) -> Box<dyn Data + '_>;
72
73    /// Returns the associated reference sequence.
74    fn reference_sequence<'h>(
75        &self,
76        header: &'h Header,
77    ) -> Option<io::Result<(&'h BStr, &'h Map<ReferenceSequence>)>> {
78        let reference_sequence_id = match self.reference_sequence_id(header).transpose() {
79            Ok(reference_sequence_id) => reference_sequence_id,
80            Err(e) => return Some(Err(e)),
81        };
82
83        get_reference_sequence(header.reference_sequences(), reference_sequence_id)
84    }
85
86    /// Returns the associated mate reference sequence.
87    fn mate_reference_sequence<'h>(
88        &self,
89        header: &'h Header,
90    ) -> Option<io::Result<(&'h BStr, &'h Map<ReferenceSequence>)>> {
91        let mate_reference_sequence_id = match self.mate_reference_sequence_id(header).transpose() {
92            Ok(id) => id,
93            Err(e) => return Some(Err(e)),
94        };
95
96        get_reference_sequence(header.reference_sequences(), mate_reference_sequence_id)
97    }
98
99    /// Returns the alignment span.
100    fn alignment_span(&self) -> Option<io::Result<usize>> {
101        match self.cigar().alignment_span() {
102            Ok(0) => None,
103            Ok(span) => Some(Ok(span)),
104            Err(e) => Some(Err(e)),
105        }
106    }
107
108    /// Calculates the end position.
109    ///
110    /// This position is 1-based, inclusive.
111    fn alignment_end(&self) -> Option<io::Result<Position>> {
112        let start = match self.alignment_start().transpose() {
113            Ok(position) => position?,
114            Err(e) => return Some(Err(e)),
115        };
116
117        match self.alignment_span() {
118            Some(Ok(span)) => {
119                let end = usize::from(start) + span - 1;
120                Position::new(end).map(Ok)
121            }
122            Some(Err(e)) => Some(Err(e)),
123            None => Some(Ok(start)),
124        }
125    }
126}
127
128impl Record for Box<dyn Record> {
129    fn name(&self) -> Option<&BStr> {
130        (**self).name()
131    }
132
133    fn flags(&self) -> io::Result<Flags> {
134        (**self).flags()
135    }
136
137    fn reference_sequence_id<'r, 'h: 'r>(
138        &'r self,
139        header: &'h Header,
140    ) -> Option<io::Result<usize>> {
141        (**self).reference_sequence_id(header)
142    }
143
144    fn alignment_start(&self) -> Option<io::Result<Position>> {
145        (**self).alignment_start()
146    }
147
148    fn mapping_quality(&self) -> Option<io::Result<MappingQuality>> {
149        (**self).mapping_quality()
150    }
151
152    fn cigar(&self) -> Box<dyn Cigar + '_> {
153        (**self).cigar()
154    }
155
156    fn mate_reference_sequence_id<'r, 'h: 'r>(
157        &'r self,
158        header: &'h Header,
159    ) -> Option<io::Result<usize>> {
160        (**self).mate_reference_sequence_id(header)
161    }
162
163    fn mate_alignment_start(&self) -> Option<io::Result<Position>> {
164        (**self).mate_alignment_start()
165    }
166
167    fn template_length(&self) -> io::Result<i32> {
168        (**self).template_length()
169    }
170
171    fn sequence(&self) -> Box<dyn Sequence + '_> {
172        (**self).sequence()
173    }
174
175    fn quality_scores(&self) -> Box<dyn QualityScores + '_> {
176        (**self).quality_scores()
177    }
178
179    fn data(&self) -> Box<dyn Data + '_> {
180        (**self).data()
181    }
182}
183
184fn get_reference_sequence(
185    reference_sequences: &ReferenceSequences,
186    reference_sequence_id: Option<usize>,
187) -> Option<io::Result<(&BStr, &Map<ReferenceSequence>)>> {
188    let id = reference_sequence_id?;
189
190    let result = reference_sequences
191        .get_index(id)
192        .map(|(name, reference_sequence)| (name.as_ref(), reference_sequence))
193        .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "invalid reference sequence ID"));
194
195    Some(result)
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201
202    #[test]
203    fn test_alignment_end() -> Result<(), Box<dyn std::error::Error>> {
204        use crate::alignment::{
205            record::cigar::{op::Kind, Op},
206            RecordBuf,
207        };
208
209        let record = RecordBuf::builder()
210            .set_alignment_start(Position::try_from(8)?)
211            .set_cigar([Op::new(Kind::Match, 5)].into_iter().collect())
212            .build();
213
214        let actual = Record::alignment_end(&record).transpose()?;
215        let expected = Position::new(12);
216        assert_eq!(actual, expected);
217
218        Ok(())
219    }
220}