Skip to main content

extended_htslib/bam/
record.rs

1// Copyright 2014 Christopher Schröder, Johannes Köster.
2// Licensed under the MIT license (http://opensource.org/licenses/MIT)
3// This file may not be copied, modified, or distributed
4// except according to those terms.
5
6use std::borrow::Cow;
7use std::convert::TryFrom;
8use std::convert::TryInto;
9use std::ffi;
10use std::fmt;
11use std::fmt::Display;
12use std::marker::PhantomData;
13use std::mem::{MaybeUninit, size_of};
14use std::ops;
15use std::ops::RangeBounds;
16use std::os::raw::c_char;
17use std::slice;
18use std::slice::SliceIndex;
19use std::str;
20use std::str::FromStr;
21use std::sync::Arc;
22
23use byteorder::{LittleEndian, ReadBytesExt};
24
25use crate::bam::Error;
26use crate::bam::HeaderView;
27use crate::errors::Result;
28use crate::htslib;
29use crate::utils;
30#[cfg(feature = "serde_feature")]
31use serde::{self, Deserialize, Serialize};
32
33use bio_types::alignment::{Alignment, AlignmentMode, AlignmentOperation};
34use bio_types::genome;
35use bio_types::sequence::SequenceRead;
36use bio_types::sequence::SequenceReadPairOrientation;
37use bio_types::strand::ReqStrand;
38
39/// A macro creating methods for flag access.
40macro_rules! flag {
41    ($get:ident, $set:ident, $unset:ident, $bit:expr) => {
42        pub fn $get(&self) -> bool {
43            self.inner().core.flag & $bit != 0
44        }
45
46        pub fn $set(&mut self) {
47            self.inner_mut().core.flag |= $bit;
48        }
49
50        pub fn $unset(&mut self) {
51            self.inner_mut().core.flag &= !$bit;
52        }
53    };
54}
55
56/// A BAM record.
57pub struct Record {
58    pub inner: htslib::bam1_t,
59    own: bool,
60    cigar: Option<CigarStringView>,
61    header: Option<Arc<HeaderView>>,
62}
63
64unsafe impl Send for Record {}
65unsafe impl Sync for Record {}
66
67impl Clone for Record {
68    fn clone(&self) -> Self {
69        let mut copy = Record::new();
70        unsafe { htslib::bam_copy1(copy.inner_ptr_mut(), self.inner_ptr()) };
71        copy
72    }
73}
74
75impl PartialEq for Record {
76    fn eq(&self, other: &Record) -> bool {
77        self.tid() == other.tid()
78            && self.pos() == other.pos()
79            && self.bin() == other.bin()
80            && self.mapq() == other.mapq()
81            && self.flags() == other.flags()
82            && self.mtid() == other.mtid()
83            && self.mpos() == other.mpos()
84            && self.insert_size() == other.insert_size()
85            && self.data() == other.data()
86            && self.inner().core.l_extranul == other.inner().core.l_extranul
87    }
88}
89
90impl Eq for Record {}
91
92impl fmt::Debug for Record {
93    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
94        fmt.write_fmt(format_args!(
95            "Record(tid: {}, pos: {})",
96            self.tid(),
97            self.pos()
98        ))
99    }
100}
101
102impl Default for Record {
103    fn default() -> Self {
104        Self::new()
105    }
106}
107
108#[inline]
109fn extranul_from_qname(qname: &[u8]) -> usize {
110    let qlen = qname.len() + 1;
111    if !qlen.is_multiple_of(4) {
112        4 - qlen % 4
113    } else {
114        0
115    }
116}
117
118impl Record {
119    /// Create an empty BAM record.
120    pub fn new() -> Self {
121        let mut record = Record {
122            inner: unsafe { MaybeUninit::zeroed().assume_init() },
123            own: true,
124            cigar: None,
125            header: None,
126        };
127        // The read/query name needs to be set as empty to properly initialize
128        // the record
129        record.set_qname(b"");
130        // Developer note: these are needed so the returned record is properly
131        // initialized as unmapped.
132        record.set_unmapped();
133        record.set_tid(-1);
134        record.set_pos(-1);
135        record.set_mpos(-1);
136        record.set_mtid(-1);
137        record
138    }
139    /// Check if cigar format has =/X or is classic M (Match) format. Could be transformed using (`crate::bam::record::getseqfromcs`).
140    pub fn cigarhasequal(&self) -> bool {
141        self.cigar()
142            .take()
143            .0
144            .iter()
145            .any(|f| matches!(f, Cigar::Equal(_)) || matches!(f, Cigar::Diff(_)))
146    }
147    pub fn from_inner(from: *mut htslib::bam1_t) -> Self {
148        Record {
149            inner: {
150                #[allow(clippy::uninit_assumed_init, invalid_value)]
151                let mut inner = unsafe { MaybeUninit::uninit().assume_init() };
152                unsafe {
153                    ::libc::memcpy(
154                        &mut inner as *mut htslib::bam1_t as *mut ::libc::c_void,
155                        from as *const ::libc::c_void,
156                        size_of::<htslib::bam1_t>(),
157                    );
158                }
159                inner
160            },
161            own: false,
162            cigar: None,
163            header: None,
164        }
165    }
166
167    // Create a BAM record from a line SAM text. SAM slice need not be 0-terminated.
168    pub fn from_sam(header_view: &HeaderView, sam: &[u8]) -> Result<Record> {
169        let mut record = Self::new();
170
171        let mut sam_copy = Vec::with_capacity(sam.len() + 1);
172        sam_copy.extend(sam);
173        sam_copy.push(0);
174
175        let mut sam_string = htslib::kstring_t {
176            s: sam_copy.as_ptr() as *mut c_char,
177            l: sam_copy.len(),
178            m: sam_copy.len(),
179        };
180
181        let succ = unsafe {
182            htslib::sam_parse1(
183                &mut sam_string,
184                header_view.inner_ptr() as *mut htslib::bam_hdr_t,
185                record.inner_ptr_mut(),
186            )
187        };
188
189        if succ == 0 {
190            Ok(record)
191        } else {
192            Err(Error::BamParseSAM {
193                rec: str::from_utf8(&sam_copy)
194                    .map_err(|e| Error::BamParseSAM { rec: e.to_string() })?
195                    .to_owned(),
196            })
197        }
198    }
199
200    pub fn set_header(&mut self, header: Arc<HeaderView>) {
201        self.header = Some(header);
202    }
203
204    pub(super) fn data(&self) -> &[u8] {
205        unsafe { slice::from_raw_parts(self.inner().data, self.inner().l_data as usize) }
206    }
207
208    #[inline]
209    pub fn inner_mut(&mut self) -> &mut htslib::bam1_t {
210        &mut self.inner
211    }
212
213    #[inline]
214    pub(super) fn inner_ptr_mut(&mut self) -> *mut htslib::bam1_t {
215        &mut self.inner as *mut htslib::bam1_t
216    }
217
218    #[inline]
219    pub fn inner(&self) -> &htslib::bam1_t {
220        &self.inner
221    }
222
223    #[inline]
224    pub(super) fn inner_ptr(&self) -> *const htslib::bam1_t {
225        &self.inner as *const htslib::bam1_t
226    }
227
228    /// Get target id.
229    pub fn tid(&self) -> i32 {
230        self.inner().core.tid
231    }
232
233    /// Set target id.
234    pub fn set_tid(&mut self, tid: i32) {
235        self.inner_mut().core.tid = tid;
236    }
237
238    /// Get position (0-based).
239    pub fn pos(&self) -> i64 {
240        self.inner().core.pos
241    }
242
243    /// Set position (0-based).
244    pub fn set_pos(&mut self, pos: i64) {
245        self.inner_mut().core.pos = pos;
246    }
247
248    pub fn bin(&self) -> u16 {
249        self.inner().core.bin
250    }
251
252    pub fn set_bin(&mut self, bin: u16) {
253        self.inner_mut().core.bin = bin;
254    }
255
256    /// Get MAPQ.
257    pub fn mapq(&self) -> u8 {
258        self.inner().core.qual
259    }
260
261    /// Set MAPQ.
262    pub fn set_mapq(&mut self, mapq: u8) {
263        self.inner_mut().core.qual = mapq;
264    }
265
266    /// Get strand information from record flags.
267    pub fn strand(&self) -> ReqStrand {
268        let reverse = self.flags() & 0x10 != 0;
269        if reverse {
270            ReqStrand::Reverse
271        } else {
272            ReqStrand::Forward
273        }
274    }
275
276    /// Get raw flags.
277    pub fn flags(&self) -> u16 {
278        self.inner().core.flag
279    }
280
281    /// Set raw flags.
282    pub fn set_flags(&mut self, flags: u16) {
283        self.inner_mut().core.flag = flags;
284    }
285
286    /// Unset all flags.
287    pub fn unset_flags(&mut self) {
288        self.inner_mut().core.flag = 0;
289    }
290
291    /// Get target id of mate.
292    pub fn mtid(&self) -> i32 {
293        self.inner().core.mtid
294    }
295
296    /// Set target id of mate.
297    pub fn set_mtid(&mut self, mtid: i32) {
298        self.inner_mut().core.mtid = mtid;
299    }
300
301    /// Get mate position.
302    pub fn mpos(&self) -> i64 {
303        self.inner().core.mpos
304    }
305
306    /// Set mate position.
307    pub fn set_mpos(&mut self, mpos: i64) {
308        self.inner_mut().core.mpos = mpos;
309    }
310
311    /// Get insert size.
312    pub fn insert_size(&self) -> i64 {
313        self.inner().core.isize_
314    }
315
316    /// Set insert size.
317    pub fn set_insert_size(&mut self, insert_size: i64) {
318        self.inner_mut().core.isize_ = insert_size;
319    }
320
321    fn qname_capacity(&self) -> usize {
322        self.inner().core.l_qname as usize
323    }
324
325    fn qname_len(&self) -> usize {
326        // discount all trailing zeros (the default one and extra nulls)
327        self.qname_capacity() - 1 - self.inner().core.l_extranul as usize
328    }
329
330    /// Get qname (read name). Complexity: O(1).
331    pub fn qname(&self) -> &[u8] {
332        &self.data()[..self.qname_len()]
333    }
334
335    /// Set the variable length data buffer
336    pub fn set_data(&mut self, new_data: &[u8]) {
337        self.cigar = None;
338
339        self.inner_mut().l_data = new_data.len() as i32;
340        if (self.inner().m_data as i32) < self.inner().l_data {
341            // Verbosity due to lexical borrowing
342            let l_data = self.inner().l_data;
343            self.realloc_var_data(l_data as usize);
344        }
345
346        // Copy new data into buffer
347        let data =
348            unsafe { slice::from_raw_parts_mut(self.inner.data, self.inner().l_data as usize) };
349        utils::copy_memory(new_data, data);
350    }
351
352    /// Set variable length data (qname, cigar, seq, qual).
353    /// The aux data is left unchanged.
354    /// `qual` is Phred-scaled quality values, without any offset.
355    /// NOTE: seq.len() must equal qual.len() or this method
356    /// will panic. If you don't have quality values use
357    /// `let quals = vec![ 255 as u8; seq.len()];` as a placeholder that will
358    /// be recognized as missing QVs by `samtools`.
359    pub fn set(&mut self, qname: &[u8], cigar: Option<&CigarString>, seq: &[u8], qual: &[u8]) {
360        assert!(qname.len() < 255);
361        assert_eq!(seq.len(), qual.len(), "seq.len() must equal qual.len()");
362
363        self.cigar = None;
364
365        let cigar_width = if let Some(cigar_string) = cigar {
366            cigar_string.len()
367        } else {
368            0
369        } * 4;
370        let q_len = qname.len() + 1;
371        let extranul = extranul_from_qname(qname);
372
373        let orig_aux_offset = self.qname_capacity()
374            + 4 * self.cigar_len()
375            + self.seq_len().div_ceil(2)
376            + self.seq_len();
377        let new_aux_offset = q_len + extranul + cigar_width + seq.len().div_ceil(2) + qual.len();
378        assert!(orig_aux_offset <= self.inner.l_data as usize);
379        let aux_len = self.inner.l_data as usize - orig_aux_offset;
380        self.inner_mut().l_data = (new_aux_offset + aux_len) as i32;
381        if (self.inner().m_data as i32) < self.inner().l_data {
382            // Verbosity due to lexical borrowing
383            let l_data = self.inner().l_data;
384            self.realloc_var_data(l_data as usize);
385        }
386
387        // Copy the aux data.
388        if aux_len > 0 && orig_aux_offset != new_aux_offset {
389            let data =
390                unsafe { slice::from_raw_parts_mut(self.inner.data, self.inner().m_data as usize) };
391            data.copy_within(orig_aux_offset..orig_aux_offset + aux_len, new_aux_offset);
392        }
393
394        let data =
395            unsafe { slice::from_raw_parts_mut(self.inner.data, self.inner().l_data as usize) };
396
397        // qname
398        utils::copy_memory(qname, data);
399        for i in 0..=extranul {
400            data[qname.len() + i] = b'\0';
401        }
402        let mut i = q_len + extranul;
403        self.inner_mut().core.l_qname = i as u16;
404        self.inner_mut().core.l_extranul = extranul as u8;
405
406        // cigar
407        if let Some(cigar_string) = cigar {
408            let cigar_data = unsafe {
409                //cigar is always aligned to 4 bytes (see extranul above) - so this is safe
410                #[allow(clippy::cast_ptr_alignment)]
411                slice::from_raw_parts_mut(data[i..].as_ptr() as *mut u32, cigar_string.len())
412            };
413            for (i, c) in cigar_string.iter().enumerate() {
414                cigar_data[i] = c.encode();
415            }
416            self.inner_mut().core.n_cigar = cigar_string.len() as u32;
417            i += cigar_string.len() * 4;
418        } else {
419            self.inner_mut().core.n_cigar = 0;
420        };
421
422        // seq
423        {
424            for j in (0..seq.len()).step_by(2) {
425                data[i + j / 2] = (ENCODE_BASE[seq[j] as usize] << 4)
426                    | (if j + 1 < seq.len() {
427                        ENCODE_BASE[seq[j + 1] as usize]
428                    } else {
429                        0
430                    });
431            }
432            self.inner_mut().core.l_qseq = seq.len() as i32;
433            i += seq.len().div_ceil(2);
434        }
435
436        // qual
437        utils::copy_memory(qual, &mut data[i..]);
438    }
439
440    /// Replace current qname with a new one.
441    pub fn set_qname(&mut self, new_qname: &[u8]) {
442        // 251 + 1NUL is the max 32-bit aligned value that fits in u8
443        assert!(new_qname.len() < 252);
444
445        let old_q_len = self.qname_capacity();
446        // We're going to add a terminal NUL
447        let extranul = extranul_from_qname(new_qname);
448        let new_q_len = new_qname.len() + 1 + extranul;
449
450        // Length of data after qname
451        let other_len = self.inner_mut().l_data - old_q_len as i32;
452
453        if new_q_len < old_q_len && self.inner().l_data > (old_q_len as i32) {
454            self.inner_mut().l_data -= (old_q_len - new_q_len) as i32;
455        } else if new_q_len > old_q_len {
456            self.inner_mut().l_data += (new_q_len - old_q_len) as i32;
457
458            // Reallocate if necessary
459            if (self.inner().m_data as i32) < self.inner().l_data {
460                // Verbosity due to lexical borrowing
461                let l_data = self.inner().l_data;
462                self.realloc_var_data(l_data as usize);
463            }
464        }
465
466        if new_q_len != old_q_len {
467            // Move other data to new location
468            unsafe {
469                let data = slice::from_raw_parts_mut(self.inner.data, self.inner().l_data as usize);
470
471                ::libc::memmove(
472                    data.as_mut_ptr().add(new_q_len) as *mut ::libc::c_void,
473                    data.as_mut_ptr().add(old_q_len) as *mut ::libc::c_void,
474                    other_len as usize,
475                );
476            }
477        }
478
479        // Copy qname data
480        let data =
481            unsafe { slice::from_raw_parts_mut(self.inner.data, self.inner().l_data as usize) };
482        utils::copy_memory(new_qname, data);
483        for i in 0..=extranul {
484            data[new_q_len - i - 1] = b'\0';
485        }
486        self.inner_mut().core.l_qname = new_q_len as u16;
487        self.inner_mut().core.l_extranul = extranul as u8;
488    }
489
490    /// Replace current cigar with a new one.
491    pub fn set_cigar(&mut self, new_cigar: Option<&CigarString>) {
492        self.cigar = None;
493
494        let qname_data_len = self.qname_capacity();
495        let old_cigar_data_len = self.cigar_len() * 4;
496
497        // Length of data after cigar
498        let other_data_len = self.inner_mut().l_data - (qname_data_len + old_cigar_data_len) as i32;
499
500        let new_cigar_len = match new_cigar {
501            Some(x) => x.len(),
502            None => 0,
503        };
504        let new_cigar_data_len = new_cigar_len * 4;
505
506        if new_cigar_data_len < old_cigar_data_len {
507            self.inner_mut().l_data -= (old_cigar_data_len - new_cigar_data_len) as i32;
508        } else if new_cigar_data_len > old_cigar_data_len {
509            self.inner_mut().l_data += (new_cigar_data_len - old_cigar_data_len) as i32;
510
511            // Reallocate if necessary
512            if (self.inner().m_data as i32) < self.inner().l_data {
513                // Verbosity due to lexical borrowing
514                let l_data = self.inner().l_data;
515                self.realloc_var_data(l_data as usize);
516            }
517        }
518
519        if new_cigar_data_len != old_cigar_data_len {
520            // Move other data to new location
521            unsafe {
522                ::libc::memmove(
523                    self.inner.data.add(qname_data_len + new_cigar_data_len) as *mut ::libc::c_void,
524                    self.inner.data.add(qname_data_len + old_cigar_data_len) as *mut ::libc::c_void,
525                    other_data_len as usize,
526                );
527            }
528        }
529
530        // Copy cigar data
531        if let Some(cigar_string) = new_cigar {
532            let cigar_data = unsafe {
533                #[allow(clippy::cast_ptr_alignment)]
534                slice::from_raw_parts_mut(
535                    self.inner.data.add(qname_data_len) as *mut u32,
536                    cigar_string.len(),
537                )
538            };
539            for (i, c) in cigar_string.iter().enumerate() {
540                cigar_data[i] = c.encode();
541            }
542        }
543        self.inner_mut().core.n_cigar = new_cigar_len as u32;
544    }
545
546    fn realloc_var_data(&mut self, new_len: usize) {
547        // pad request
548        let new_len = new_len as u32;
549        let new_request = new_len + 32 - (new_len % 32);
550
551        let ptr = unsafe {
552            ::libc::realloc(
553                self.inner().data as *mut ::libc::c_void,
554                new_request as usize,
555            ) as *mut u8
556        };
557
558        if ptr.is_null() {
559            panic!("ran out of memory in rust_htslib trying to realloc");
560        }
561
562        // don't update m_data until we know we have
563        // a successful allocation.
564        self.inner_mut().m_data = new_request;
565        self.inner_mut().data = ptr;
566
567        // we now own inner.data
568        self.own = true;
569    }
570
571    pub fn cigar_len(&self) -> usize {
572        self.inner().core.n_cigar as usize
573    }
574
575    /// Get reference to raw cigar string representation (as stored in BAM file).
576    /// Usually, the method `Record::cigar` should be used instead.
577    pub fn raw_cigar(&self) -> &[u32] {
578        //cigar is always aligned to 4 bytes - so this is safe
579        #[allow(clippy::cast_ptr_alignment)]
580        unsafe {
581            slice::from_raw_parts(
582                self.data()[self.qname_capacity()..].as_ptr() as *const u32,
583                self.cigar_len(),
584            )
585        }
586    }
587
588    /// Return unpacked cigar string. This will create a fresh copy the Cigar data.
589    pub fn cigar(&self) -> CigarStringView {
590        match self.cigar {
591            Some(ref c) => c.clone(),
592            None => self.unpack_cigar(),
593        }
594    }
595
596    // Return unpacked cigar string. This returns None unless you have first called `bam::Record::cache_cigar`.
597    pub fn cigar_cached(&self) -> Option<&CigarStringView> {
598        self.cigar.as_ref()
599    }
600
601    /// Decode the cigar string and cache it inside the `Record`
602    pub fn cache_cigar(&mut self) {
603        self.cigar = Some(self.unpack_cigar())
604    }
605
606    /// Unpack cigar string. Complexity: O(k) with k being the length of the cigar string.
607    fn unpack_cigar(&self) -> CigarStringView {
608        CigarString(
609            self.raw_cigar()
610                .iter()
611                .map(|&c| {
612                    let len = c >> 4;
613                    match c & 0b1111 {
614                        0 => Cigar::Match(len),
615                        1 => Cigar::Ins(len),
616                        2 => Cigar::Del(len),
617                        3 => Cigar::RefSkip(len),
618                        4 => Cigar::SoftClip(len),
619                        5 => Cigar::HardClip(len),
620                        6 => Cigar::Pad(len),
621                        7 => Cigar::Equal(len),
622                        8 => Cigar::Diff(len),
623                        _ => panic!("Unexpected cigar operation"),
624                    }
625                })
626                .collect(),
627        )
628        .into_view(self.pos())
629    }
630
631    pub fn seq_len(&self) -> usize {
632        self.inner().core.l_qseq as usize
633    }
634
635    fn seq_data(&self) -> &[u8] {
636        let offset = self.qname_capacity() + self.cigar_len() * 4;
637        &self.data()[offset..][..self.seq_len().div_ceil(2)]
638    }
639
640    /// Get read sequence. Complexity: O(1).
641    pub fn seq(&self) -> Seq<'_> {
642        Seq {
643            encoded: self.seq_data(),
644            len: self.seq_len(),
645        }
646    }
647
648    /// Get base qualities (PHRED-scaled probability that base is wrong).
649    /// This does not entail any offsets, hence the qualities can be used directly without
650    /// e.g. subtracting 33. Complexity: O(1).
651    pub fn qual(&self) -> &[u8] {
652        &self.data()[self.qname_capacity() + self.cigar_len() * 4 + self.seq_len().div_ceil(2)..]
653            [..self.seq_len()]
654    }
655
656    /// Look up an auxiliary field by its tag.
657    ///
658    /// Only the first two bytes of a given tag are used for the look-up of a field.
659    /// See [`Aux`] for more details.
660    pub fn aux(&self, tag: &[u8]) -> Result<Aux<'_>> {
661        if tag.len() < 2 {
662            return Err(Error::BamAuxStringError);
663        }
664        let aux = unsafe {
665            htslib::bam_aux_get(
666                &self.inner as *const htslib::bam1_t,
667                tag.as_ptr() as *const c_char,
668            )
669        };
670        unsafe { Self::read_aux_field(aux).map(|(aux_field, _length)| aux_field) }
671    }
672
673    unsafe fn read_aux_field<'a>(aux: *const u8) -> Result<(Aux<'a>, usize)> {
674        const TAG_LEN: isize = 2;
675        // Used for skipping type identifier
676        const TYPE_ID_LEN: isize = 1;
677
678        if aux.is_null() {
679            return Err(Error::BamAuxTagNotFound);
680        }
681
682        let (data, type_size) = match *aux {
683            b'A' => {
684                let type_size = size_of::<u8>();
685                (Aux::Char(*aux.offset(TYPE_ID_LEN)), type_size)
686            }
687            b'c' => {
688                let type_size = size_of::<i8>();
689                (Aux::I8(*aux.offset(TYPE_ID_LEN).cast::<i8>()), type_size)
690            }
691            b'C' => {
692                let type_size = size_of::<u8>();
693                (Aux::U8(*aux.offset(TYPE_ID_LEN)), type_size)
694            }
695            b's' => {
696                let type_size = size_of::<i16>();
697                (
698                    Aux::I16(
699                        slice::from_raw_parts(aux.offset(TYPE_ID_LEN), type_size)
700                            .read_i16::<LittleEndian>()
701                            .map_err(|_| Error::BamAuxParsingError)?,
702                    ),
703                    type_size,
704                )
705            }
706            b'S' => {
707                let type_size = size_of::<u16>();
708                (
709                    Aux::U16(
710                        slice::from_raw_parts(aux.offset(TYPE_ID_LEN), type_size)
711                            .read_u16::<LittleEndian>()
712                            .map_err(|_| Error::BamAuxParsingError)?,
713                    ),
714                    type_size,
715                )
716            }
717            b'i' => {
718                let type_size = size_of::<i32>();
719                (
720                    Aux::I32(
721                        slice::from_raw_parts(aux.offset(TYPE_ID_LEN), type_size)
722                            .read_i32::<LittleEndian>()
723                            .map_err(|_| Error::BamAuxParsingError)?,
724                    ),
725                    type_size,
726                )
727            }
728            b'I' => {
729                let type_size = size_of::<u32>();
730                (
731                    Aux::U32(
732                        slice::from_raw_parts(aux.offset(TYPE_ID_LEN), type_size)
733                            .read_u32::<LittleEndian>()
734                            .map_err(|_| Error::BamAuxParsingError)?,
735                    ),
736                    type_size,
737                )
738            }
739            b'f' => {
740                let type_size = size_of::<f32>();
741                (
742                    Aux::Float(
743                        slice::from_raw_parts(aux.offset(TYPE_ID_LEN), type_size)
744                            .read_f32::<LittleEndian>()
745                            .map_err(|_| Error::BamAuxParsingError)?,
746                    ),
747                    type_size,
748                )
749            }
750            b'd' => {
751                let type_size = size_of::<f64>();
752                (
753                    Aux::Double(
754                        slice::from_raw_parts(aux.offset(TYPE_ID_LEN), type_size)
755                            .read_f64::<LittleEndian>()
756                            .map_err(|_| Error::BamAuxParsingError)?,
757                    ),
758                    type_size,
759                )
760            }
761            b'Z' | b'H' => {
762                let c_str = ffi::CStr::from_ptr(aux.offset(TYPE_ID_LEN).cast::<c_char>());
763                let rust_str = c_str.to_str().map_err(|_| Error::BamAuxParsingError)?;
764                (Aux::String(rust_str), c_str.to_bytes_with_nul().len())
765            }
766            b'B' => {
767                const ARRAY_INNER_TYPE_LEN: isize = 1;
768                const ARRAY_COUNT_LEN: isize = 4;
769
770                // Used for skipping metadata
771                let array_data_offset = TYPE_ID_LEN + ARRAY_INNER_TYPE_LEN + ARRAY_COUNT_LEN;
772
773                let length =
774                    slice::from_raw_parts(aux.offset(TYPE_ID_LEN + ARRAY_INNER_TYPE_LEN), 4)
775                        .read_u32::<LittleEndian>()
776                        .map_err(|_| Error::BamAuxParsingError)? as usize;
777
778                // Return tuples of an `Aux` enum and the length of data + metadata in bytes
779                let (array_data, array_size) = match *aux.offset(TYPE_ID_LEN) {
780                    b'c' => (
781                        Aux::ArrayI8(AuxArray::<'a, i8>::from_bytes(slice::from_raw_parts(
782                            aux.offset(array_data_offset),
783                            length,
784                        ))),
785                        length,
786                    ),
787                    b'C' => (
788                        Aux::ArrayU8(AuxArray::<'a, u8>::from_bytes(slice::from_raw_parts(
789                            aux.offset(array_data_offset),
790                            length,
791                        ))),
792                        length,
793                    ),
794                    b's' => (
795                        Aux::ArrayI16(AuxArray::<'a, i16>::from_bytes(slice::from_raw_parts(
796                            aux.offset(array_data_offset),
797                            length * size_of::<i16>(),
798                        ))),
799                        length * std::mem::size_of::<i16>(),
800                    ),
801                    b'S' => (
802                        Aux::ArrayU16(AuxArray::<'a, u16>::from_bytes(slice::from_raw_parts(
803                            aux.offset(array_data_offset),
804                            length * size_of::<u16>(),
805                        ))),
806                        length * std::mem::size_of::<u16>(),
807                    ),
808                    b'i' => (
809                        Aux::ArrayI32(AuxArray::<'a, i32>::from_bytes(slice::from_raw_parts(
810                            aux.offset(array_data_offset),
811                            length * size_of::<i32>(),
812                        ))),
813                        length * std::mem::size_of::<i32>(),
814                    ),
815                    b'I' => (
816                        Aux::ArrayU32(AuxArray::<'a, u32>::from_bytes(slice::from_raw_parts(
817                            aux.offset(array_data_offset),
818                            length * size_of::<u32>(),
819                        ))),
820                        length * std::mem::size_of::<u32>(),
821                    ),
822                    b'f' => (
823                        Aux::ArrayFloat(AuxArray::<f32>::from_bytes(slice::from_raw_parts(
824                            aux.offset(array_data_offset),
825                            length * size_of::<f32>(),
826                        ))),
827                        length * std::mem::size_of::<f32>(),
828                    ),
829                    _ => {
830                        return Err(Error::BamAuxUnknownType);
831                    }
832                };
833                (
834                    array_data,
835                    // Offset: array-specific metadata + array size
836                    ARRAY_INNER_TYPE_LEN as usize + ARRAY_COUNT_LEN as usize + array_size,
837                )
838            }
839            _ => {
840                return Err(Error::BamAuxUnknownType);
841            }
842        };
843
844        // Offset: metadata + type size
845        Ok((data, TAG_LEN as usize + TYPE_ID_LEN as usize + type_size))
846    }
847
848    /// Returns an iterator over the auxiliary fields of the record.
849    ///
850    /// When an error occurs, the `Err` variant will be returned
851    /// and the iterator will not be able to advance anymore.
852    pub fn aux_iter(&'_ self) -> AuxIter<'_> {
853        AuxIter {
854            // In order to get to the aux data section of a `bam::Record`
855            // we need to skip fields in front of it
856            aux: &self.data()[
857                // NUL terminated read name:
858                self.qname_capacity()
859                // CIGAR (uint32_t):
860                + self.cigar_len() * std::mem::size_of::<u32>()
861                // Read sequence (4-bit encoded):
862                + self.seq_len().div_ceil(2)
863                // Base qualities (char):
864                + self.seq_len()..],
865        }
866    }
867
868    /// Add auxiliary data.
869    pub fn push_aux(&mut self, tag: &[u8], value: Aux<'_>) -> Result<()> {
870        // Don't allow pushing aux data when the given tag is already present in the record.
871        // `htslib` seems to allow this (for non-array values), which can lead to problems
872        // since retrieving aux fields consumes &[u8; 2] and yields one field only.
873        if self.aux(tag).is_ok() {
874            return Err(Error::BamAuxTagAlreadyPresent);
875        }
876        self.push_aux_unchecked(tag, value)
877    }
878
879    /// Add auxiliary data, without checking if the tag is present.
880    ///
881    /// The caller should ensure that the same tag is not pushed more than once.
882    /// This is provided as a performance optimization.
883    pub fn push_aux_unchecked(&mut self, tag: &[u8], value: Aux<'_>) -> Result<()> {
884        let ctag = tag.as_ptr() as *mut c_char;
885        let ret = unsafe {
886            match value {
887                Aux::Char(v) => htslib::bam_aux_append(
888                    self.inner_ptr_mut(),
889                    ctag,
890                    b'A' as c_char,
891                    size_of::<u8>() as i32,
892                    [v].as_mut_ptr(),
893                ),
894                Aux::I8(v) => htslib::bam_aux_append(
895                    self.inner_ptr_mut(),
896                    ctag,
897                    b'c' as c_char,
898                    size_of::<i8>() as i32,
899                    [v].as_mut_ptr() as *mut u8,
900                ),
901                Aux::U8(v) => htslib::bam_aux_append(
902                    self.inner_ptr_mut(),
903                    ctag,
904                    b'C' as c_char,
905                    size_of::<u8>() as i32,
906                    [v].as_mut_ptr(),
907                ),
908                Aux::I16(v) => htslib::bam_aux_append(
909                    self.inner_ptr_mut(),
910                    ctag,
911                    b's' as c_char,
912                    size_of::<i16>() as i32,
913                    [v].as_mut_ptr() as *mut u8,
914                ),
915                Aux::U16(v) => htslib::bam_aux_append(
916                    self.inner_ptr_mut(),
917                    ctag,
918                    b'S' as c_char,
919                    size_of::<u16>() as i32,
920                    [v].as_mut_ptr() as *mut u8,
921                ),
922                Aux::I32(v) => htslib::bam_aux_append(
923                    self.inner_ptr_mut(),
924                    ctag,
925                    b'i' as c_char,
926                    size_of::<i32>() as i32,
927                    [v].as_mut_ptr() as *mut u8,
928                ),
929                Aux::U32(v) => htslib::bam_aux_append(
930                    self.inner_ptr_mut(),
931                    ctag,
932                    b'I' as c_char,
933                    size_of::<u32>() as i32,
934                    [v].as_mut_ptr() as *mut u8,
935                ),
936                Aux::Float(v) => htslib::bam_aux_append(
937                    self.inner_ptr_mut(),
938                    ctag,
939                    b'f' as c_char,
940                    size_of::<f32>() as i32,
941                    [v].as_mut_ptr() as *mut u8,
942                ),
943                // Not part of specs but implemented in `htslib`:
944                Aux::Double(v) => htslib::bam_aux_append(
945                    self.inner_ptr_mut(),
946                    ctag,
947                    b'd' as c_char,
948                    size_of::<f64>() as i32,
949                    [v].as_mut_ptr() as *mut u8,
950                ),
951                Aux::String(v) => {
952                    let c_str = ffi::CString::new(v).map_err(|_| Error::BamAuxStringError)?;
953                    htslib::bam_aux_append(
954                        self.inner_ptr_mut(),
955                        ctag,
956                        b'Z' as c_char,
957                        (v.len() + 1) as i32,
958                        c_str.as_ptr() as *mut u8,
959                    )
960                }
961                Aux::HexByteArray(v) => {
962                    let c_str = ffi::CString::new(v).map_err(|_| Error::BamAuxStringError)?;
963                    htslib::bam_aux_append(
964                        self.inner_ptr_mut(),
965                        ctag,
966                        b'H' as c_char,
967                        (v.len() + 1) as i32,
968                        c_str.as_ptr() as *mut u8,
969                    )
970                }
971                // Not sure it's safe to cast an immutable slice to a mutable pointer in the following branches
972                Aux::ArrayI8(aux_array) => match aux_array {
973                    AuxArray::TargetType(inner) => htslib::bam_aux_update_array(
974                        self.inner_ptr_mut(),
975                        ctag,
976                        b'c',
977                        inner.len() as u32,
978                        inner.slice.as_ptr() as *mut ::libc::c_void,
979                    ),
980                    AuxArray::RawLeBytes(inner) => htslib::bam_aux_update_array(
981                        self.inner_ptr_mut(),
982                        ctag,
983                        b'c',
984                        inner.len() as u32,
985                        inner.slice.as_ptr() as *mut ::libc::c_void,
986                    ),
987                },
988                Aux::ArrayU8(aux_array) => match aux_array {
989                    AuxArray::TargetType(inner) => htslib::bam_aux_update_array(
990                        self.inner_ptr_mut(),
991                        ctag,
992                        b'C',
993                        inner.len() as u32,
994                        inner.slice.as_ptr() as *mut ::libc::c_void,
995                    ),
996                    AuxArray::RawLeBytes(inner) => htslib::bam_aux_update_array(
997                        self.inner_ptr_mut(),
998                        ctag,
999                        b'C',
1000                        inner.len() as u32,
1001                        inner.slice.as_ptr() as *mut ::libc::c_void,
1002                    ),
1003                },
1004                Aux::ArrayI16(aux_array) => match aux_array {
1005                    AuxArray::TargetType(inner) => htslib::bam_aux_update_array(
1006                        self.inner_ptr_mut(),
1007                        ctag,
1008                        b's',
1009                        inner.len() as u32,
1010                        inner.slice.as_ptr() as *mut ::libc::c_void,
1011                    ),
1012                    AuxArray::RawLeBytes(inner) => htslib::bam_aux_update_array(
1013                        self.inner_ptr_mut(),
1014                        ctag,
1015                        b's',
1016                        inner.len() as u32,
1017                        inner.slice.as_ptr() as *mut ::libc::c_void,
1018                    ),
1019                },
1020                Aux::ArrayU16(aux_array) => match aux_array {
1021                    AuxArray::TargetType(inner) => htslib::bam_aux_update_array(
1022                        self.inner_ptr_mut(),
1023                        ctag,
1024                        b'S',
1025                        inner.len() as u32,
1026                        inner.slice.as_ptr() as *mut ::libc::c_void,
1027                    ),
1028                    AuxArray::RawLeBytes(inner) => htslib::bam_aux_update_array(
1029                        self.inner_ptr_mut(),
1030                        ctag,
1031                        b'S',
1032                        inner.len() as u32,
1033                        inner.slice.as_ptr() as *mut ::libc::c_void,
1034                    ),
1035                },
1036                Aux::ArrayI32(aux_array) => match aux_array {
1037                    AuxArray::TargetType(inner) => htslib::bam_aux_update_array(
1038                        self.inner_ptr_mut(),
1039                        ctag,
1040                        b'i',
1041                        inner.len() as u32,
1042                        inner.slice.as_ptr() as *mut ::libc::c_void,
1043                    ),
1044                    AuxArray::RawLeBytes(inner) => htslib::bam_aux_update_array(
1045                        self.inner_ptr_mut(),
1046                        ctag,
1047                        b'i',
1048                        inner.len() as u32,
1049                        inner.slice.as_ptr() as *mut ::libc::c_void,
1050                    ),
1051                },
1052                Aux::ArrayU32(aux_array) => match aux_array {
1053                    AuxArray::TargetType(inner) => htslib::bam_aux_update_array(
1054                        self.inner_ptr_mut(),
1055                        ctag,
1056                        b'I',
1057                        inner.len() as u32,
1058                        inner.slice.as_ptr() as *mut ::libc::c_void,
1059                    ),
1060                    AuxArray::RawLeBytes(inner) => htslib::bam_aux_update_array(
1061                        self.inner_ptr_mut(),
1062                        ctag,
1063                        b'I',
1064                        inner.len() as u32,
1065                        inner.slice.as_ptr() as *mut ::libc::c_void,
1066                    ),
1067                },
1068                Aux::ArrayFloat(aux_array) => match aux_array {
1069                    AuxArray::TargetType(inner) => htslib::bam_aux_update_array(
1070                        self.inner_ptr_mut(),
1071                        ctag,
1072                        b'f',
1073                        inner.len() as u32,
1074                        inner.slice.as_ptr() as *mut ::libc::c_void,
1075                    ),
1076                    AuxArray::RawLeBytes(inner) => htslib::bam_aux_update_array(
1077                        self.inner_ptr_mut(),
1078                        ctag,
1079                        b'f',
1080                        inner.len() as u32,
1081                        inner.slice.as_ptr() as *mut ::libc::c_void,
1082                    ),
1083                },
1084            }
1085        };
1086
1087        if ret < 0 { Err(Error::BamAux) } else { Ok(()) }
1088    }
1089
1090    /// Update or add auxiliary data.
1091    pub fn update_aux(&mut self, tag: &[u8], value: Aux<'_>) -> Result<()> {
1092        // Update existing aux data for the given tag if already present in the record
1093        // without changing the ordering of tags in the record or append aux data at
1094        // the end of the existing aux records if it is a new tag.
1095
1096        let ctag = tag.as_ptr() as *mut c_char;
1097        let ret = unsafe {
1098            match value {
1099                Aux::Char(_v) => return Err(Error::BamAuxTagUpdatingNotSupported),
1100                Aux::I8(v) => htslib::bam_aux_update_int(self.inner_ptr_mut(), ctag, v as i64),
1101                Aux::U8(v) => htslib::bam_aux_update_int(self.inner_ptr_mut(), ctag, v as i64),
1102                Aux::I16(v) => htslib::bam_aux_update_int(self.inner_ptr_mut(), ctag, v as i64),
1103                Aux::U16(v) => htslib::bam_aux_update_int(self.inner_ptr_mut(), ctag, v as i64),
1104                Aux::I32(v) => htslib::bam_aux_update_int(self.inner_ptr_mut(), ctag, v as i64),
1105                Aux::U32(v) => htslib::bam_aux_update_int(self.inner_ptr_mut(), ctag, v as i64),
1106                Aux::Float(v) => htslib::bam_aux_update_float(self.inner_ptr_mut(), ctag, v),
1107                // Not part of specs but implemented in `htslib`:
1108                Aux::Double(v) => {
1109                    htslib::bam_aux_update_float(self.inner_ptr_mut(), ctag, v as f32)
1110                }
1111                Aux::String(v) => {
1112                    let c_str = ffi::CString::new(v).map_err(|_| Error::BamAuxStringError)?;
1113                    htslib::bam_aux_update_str(
1114                        self.inner_ptr_mut(),
1115                        ctag,
1116                        (v.len() + 1) as i32,
1117                        c_str.as_ptr() as *const c_char,
1118                    )
1119                }
1120                Aux::HexByteArray(_v) => return Err(Error::BamAuxTagUpdatingNotSupported),
1121                // Not sure it's safe to cast an immutable slice to a mutable pointer in the following branches
1122                Aux::ArrayI8(aux_array) => match aux_array {
1123                    AuxArray::TargetType(inner) => htslib::bam_aux_update_array(
1124                        self.inner_ptr_mut(),
1125                        ctag,
1126                        b'c',
1127                        inner.len() as u32,
1128                        inner.slice.as_ptr() as *mut ::libc::c_void,
1129                    ),
1130                    AuxArray::RawLeBytes(inner) => htslib::bam_aux_update_array(
1131                        self.inner_ptr_mut(),
1132                        ctag,
1133                        b'c',
1134                        inner.len() as u32,
1135                        inner.slice.as_ptr() as *mut ::libc::c_void,
1136                    ),
1137                },
1138                Aux::ArrayU8(aux_array) => match aux_array {
1139                    AuxArray::TargetType(inner) => htslib::bam_aux_update_array(
1140                        self.inner_ptr_mut(),
1141                        ctag,
1142                        b'C',
1143                        inner.len() as u32,
1144                        inner.slice.as_ptr() as *mut ::libc::c_void,
1145                    ),
1146                    AuxArray::RawLeBytes(inner) => htslib::bam_aux_update_array(
1147                        self.inner_ptr_mut(),
1148                        ctag,
1149                        b'C',
1150                        inner.len() as u32,
1151                        inner.slice.as_ptr() as *mut ::libc::c_void,
1152                    ),
1153                },
1154                Aux::ArrayI16(aux_array) => match aux_array {
1155                    AuxArray::TargetType(inner) => htslib::bam_aux_update_array(
1156                        self.inner_ptr_mut(),
1157                        ctag,
1158                        b's',
1159                        inner.len() as u32,
1160                        inner.slice.as_ptr() as *mut ::libc::c_void,
1161                    ),
1162                    AuxArray::RawLeBytes(inner) => htslib::bam_aux_update_array(
1163                        self.inner_ptr_mut(),
1164                        ctag,
1165                        b's',
1166                        inner.len() as u32,
1167                        inner.slice.as_ptr() as *mut ::libc::c_void,
1168                    ),
1169                },
1170                Aux::ArrayU16(aux_array) => match aux_array {
1171                    AuxArray::TargetType(inner) => htslib::bam_aux_update_array(
1172                        self.inner_ptr_mut(),
1173                        ctag,
1174                        b'S',
1175                        inner.len() as u32,
1176                        inner.slice.as_ptr() as *mut ::libc::c_void,
1177                    ),
1178                    AuxArray::RawLeBytes(inner) => htslib::bam_aux_update_array(
1179                        self.inner_ptr_mut(),
1180                        ctag,
1181                        b'S',
1182                        inner.len() as u32,
1183                        inner.slice.as_ptr() as *mut ::libc::c_void,
1184                    ),
1185                },
1186                Aux::ArrayI32(aux_array) => match aux_array {
1187                    AuxArray::TargetType(inner) => htslib::bam_aux_update_array(
1188                        self.inner_ptr_mut(),
1189                        ctag,
1190                        b'i',
1191                        inner.len() as u32,
1192                        inner.slice.as_ptr() as *mut ::libc::c_void,
1193                    ),
1194                    AuxArray::RawLeBytes(inner) => htslib::bam_aux_update_array(
1195                        self.inner_ptr_mut(),
1196                        ctag,
1197                        b'i',
1198                        inner.len() as u32,
1199                        inner.slice.as_ptr() as *mut ::libc::c_void,
1200                    ),
1201                },
1202                Aux::ArrayU32(aux_array) => match aux_array {
1203                    AuxArray::TargetType(inner) => htslib::bam_aux_update_array(
1204                        self.inner_ptr_mut(),
1205                        ctag,
1206                        b'I',
1207                        inner.len() as u32,
1208                        inner.slice.as_ptr() as *mut ::libc::c_void,
1209                    ),
1210                    AuxArray::RawLeBytes(inner) => htslib::bam_aux_update_array(
1211                        self.inner_ptr_mut(),
1212                        ctag,
1213                        b'I',
1214                        inner.len() as u32,
1215                        inner.slice.as_ptr() as *mut ::libc::c_void,
1216                    ),
1217                },
1218                Aux::ArrayFloat(aux_array) => match aux_array {
1219                    AuxArray::TargetType(inner) => htslib::bam_aux_update_array(
1220                        self.inner_ptr_mut(),
1221                        ctag,
1222                        b'f',
1223                        inner.len() as u32,
1224                        inner.slice.as_ptr() as *mut ::libc::c_void,
1225                    ),
1226                    AuxArray::RawLeBytes(inner) => htslib::bam_aux_update_array(
1227                        self.inner_ptr_mut(),
1228                        ctag,
1229                        b'f',
1230                        inner.len() as u32,
1231                        inner.slice.as_ptr() as *mut ::libc::c_void,
1232                    ),
1233                },
1234            }
1235        };
1236
1237        if ret < 0 { Err(Error::BamAux) } else { Ok(()) }
1238    }
1239
1240    // Delete auxiliary tag.
1241    pub fn remove_aux(&mut self, tag: &[u8]) -> Result<()> {
1242        if tag.len() < 2 {
1243            return Err(Error::BamAuxStringError);
1244        }
1245        let aux = unsafe {
1246            htslib::bam_aux_get(
1247                &self.inner as *const htslib::bam1_t,
1248                tag.as_ptr() as *const c_char,
1249            )
1250        };
1251        unsafe {
1252            if aux.is_null() {
1253                Err(Error::BamAuxTagNotFound)
1254            } else {
1255                htslib::bam_aux_del(self.inner_ptr_mut(), aux);
1256                Ok(())
1257            }
1258        }
1259    }
1260
1261    /// Access the base modifications associated with this Record through the MM tag.
1262    /// Example:
1263    /// ```
1264    ///    use extended_htslib::bam::{Read, Reader, Record};
1265    ///    let mut bam = Reader::from_path("test/base_mods/MM-orient.sam").unwrap();
1266    ///    let mut mod_count = 0;
1267    ///    for r in bam.records() {
1268    ///        let record = r.unwrap();
1269    ///        if let Ok(mods) = record.basemods_iter() {
1270    ///            // print metadata for the modifications present in this record
1271    ///            for mod_code in mods.recorded() {
1272    ///                if let Ok(mod_metadata) = mods.query_type(*mod_code) {
1273    ///                    println!("mod found with code {}/{} flags: [{} {} {}]",
1274    ///                              mod_code, *mod_code as u8 as char,
1275    ///                              mod_metadata.strand, mod_metadata.implicit, mod_metadata.canonical as u8 as char);
1276    ///                }
1277    ///            }
1278    ///
1279    ///            // iterate over the modifications in this record
1280    ///            // the modifications are returned as a tuple with the
1281    ///            // position within SEQ and an hts_base_mod struct
1282    ///            for res in mods {
1283    ///                if let Ok( (position, m) ) = res {
1284    ///                    println!("{} {},{}", position, m.modified_base as u8 as char, m.qual);
1285    ///                    mod_count += 1;
1286    ///                }
1287    ///            }
1288    ///        };
1289    ///    }
1290    ///    assert_eq!(mod_count, 14);
1291    /// ```
1292    pub fn basemods_iter(&'_ self) -> Result<BaseModificationsIter<'_>> {
1293        BaseModificationsIter::new(self)
1294    }
1295    /// Check if the record is primary (not secondary or supplementary).
1296    /// ```
1297    /// use extended_htslib::bam::Record;
1298    /// let mut a = Record::new();
1299    /// assert!(a.is_primary());
1300    /// a.set_secondary();
1301    /// assert!(!a.is_primary());
1302    /// a.unset_secondary();
1303    /// a.set_supplementary();
1304    /// assert!(!a.is_primary());
1305    /// a.unset_supplementary();
1306    /// a.set_reverse();
1307    /// assert!(a.is_primary());
1308    /// a.unset_reverse();
1309    /// a.set_paired();
1310    /// assert!(a.is_primary());
1311    /// a.unset_paired();
1312    /// ```
1313    pub fn is_primary(&self) -> bool {
1314        self.flags() & 0x900 == 0
1315    }
1316    /// An iterator that returns all of the modifications for each position as a vector.
1317    /// This is useful for the case where multiple possible modifications can be annotated
1318    /// at a single position (for example a C could be 5-mC or 5-hmC)
1319    pub fn basemods_position_iter(&'_ self) -> Result<BaseModificationsPositionIter<'_>> {
1320        BaseModificationsPositionIter::new(self)
1321    }
1322
1323    /// Infer read pair orientation from record. Returns `SequenceReadPairOrientation::None` if record
1324    /// is not paired, mates are not mapping to the same contig, or mates start at the
1325    /// same position.
1326    pub fn read_pair_orientation(&self) -> SequenceReadPairOrientation {
1327        if self.is_paired()
1328            && !self.is_unmapped()
1329            && !self.is_mate_unmapped()
1330            && self.tid() == self.mtid()
1331        {
1332            if self.pos() == self.mpos() {
1333                // both reads start at the same position, we cannot decide on the orientation.
1334                return SequenceReadPairOrientation::None;
1335            }
1336
1337            let (pos_1, pos_2, fwd_1, fwd_2) = if self.is_first_in_template() {
1338                (
1339                    self.pos(),
1340                    self.mpos(),
1341                    !self.is_reverse(),
1342                    !self.is_mate_reverse(),
1343                )
1344            } else {
1345                (
1346                    self.mpos(),
1347                    self.pos(),
1348                    !self.is_mate_reverse(),
1349                    !self.is_reverse(),
1350                )
1351            };
1352
1353            if pos_1 < pos_2 {
1354                match (fwd_1, fwd_2) {
1355                    (true, true) => SequenceReadPairOrientation::F1F2,
1356                    (true, false) => SequenceReadPairOrientation::F1R2,
1357                    (false, true) => SequenceReadPairOrientation::R1F2,
1358                    (false, false) => SequenceReadPairOrientation::R1R2,
1359                }
1360            } else {
1361                match (fwd_2, fwd_1) {
1362                    (true, true) => SequenceReadPairOrientation::F2F1,
1363                    (true, false) => SequenceReadPairOrientation::F2R1,
1364                    (false, true) => SequenceReadPairOrientation::R2F1,
1365                    (false, false) => SequenceReadPairOrientation::R2R1,
1366                }
1367            }
1368        } else {
1369            SequenceReadPairOrientation::None
1370        }
1371    }
1372
1373    flag!(is_paired, set_paired, unset_paired, 1u16);
1374    flag!(is_proper_pair, set_proper_pair, unset_proper_pair, 2u16);
1375    flag!(is_unmapped, set_unmapped, unset_unmapped, 4u16);
1376    flag!(
1377        is_mate_unmapped,
1378        set_mate_unmapped,
1379        unset_mate_unmapped,
1380        8u16
1381    );
1382    flag!(is_reverse, set_reverse, unset_reverse, 16u16);
1383    flag!(is_mate_reverse, set_mate_reverse, unset_mate_reverse, 32u16);
1384    flag!(
1385        is_first_in_template,
1386        set_first_in_template,
1387        unset_first_in_template,
1388        64u16
1389    );
1390    flag!(
1391        is_last_in_template,
1392        set_last_in_template,
1393        unset_last_in_template,
1394        128u16
1395    );
1396    flag!(is_secondary, set_secondary, unset_secondary, 256u16);
1397    flag!(
1398        is_quality_check_failed,
1399        set_quality_check_failed,
1400        unset_quality_check_failed,
1401        512u16
1402    );
1403    flag!(is_duplicate, set_duplicate, unset_duplicate, 1024u16);
1404    flag!(
1405        is_supplementary,
1406        set_supplementary,
1407        unset_supplementary,
1408        2048u16
1409    );
1410}
1411
1412impl Drop for Record {
1413    fn drop(&mut self) {
1414        if self.own {
1415            unsafe { ::libc::free(self.inner.data as *mut ::libc::c_void) }
1416        }
1417    }
1418}
1419
1420impl SequenceRead for Record {
1421    fn name(&self) -> &[u8] {
1422        self.qname()
1423    }
1424
1425    fn base(&self, i: usize) -> u8 {
1426        *decode_base_unchecked(encoded_base(self.seq_data(), i))
1427    }
1428
1429    fn base_qual(&self, i: usize) -> u8 {
1430        self.qual()[i]
1431    }
1432
1433    fn len(&self) -> usize {
1434        self.seq_len()
1435    }
1436
1437    fn is_empty(&self) -> bool {
1438        self.len() == 0
1439    }
1440}
1441
1442impl genome::AbstractInterval for Record {
1443    /// Return contig name. Panics if record does not know its header (which happens if it has not been read from a file).
1444    fn contig(&self) -> &str {
1445        let tid = self.tid();
1446        if tid < 0 {
1447            panic!("invalid tid, must be at least zero");
1448        }
1449        str::from_utf8(
1450            self.header
1451                .as_ref()
1452                .expect(
1453                    "header must be set (this is the case if the record has been read from a file)",
1454                )
1455                .tid2name(tid as u32),
1456        )
1457        .expect("unable to interpret contig name as UTF-8")
1458    }
1459
1460    /// Return genomic range covered by alignment. Panics if `Record::cache_cigar()` has not been called first or `Record::pos()` is less than zero.
1461    fn range(&self) -> ops::Range<genome::Position> {
1462        let end_pos = self
1463            .cigar_cached()
1464            .expect("cigar has not been cached yet, call cache_cigar() first")
1465            .end_pos() as u64;
1466
1467        if self.pos() < 0 {
1468            panic!("invalid position, must be positive")
1469        }
1470
1471        self.pos() as u64..end_pos
1472    }
1473}
1474
1475/// Auxiliary record data
1476///
1477/// The specification allows a wide range of types to be stored as an auxiliary data field of a BAM record.
1478///
1479/// Please note that the [`Aux::Double`] variant is _not_ part of the specification, but it is supported by `htslib`.
1480///
1481/// # Examples
1482///
1483/// ```
1484/// use extended_htslib::{
1485///     bam,
1486///     bam::record::{Aux, AuxArray},
1487///     errors::Error,
1488/// };
1489///
1490/// //Set up BAM record
1491/// let bam_header = bam::Header::new();
1492/// let mut record = bam::Record::from_sam(
1493///     &mut bam::HeaderView::from_header(&bam_header),
1494///     "ali1\t4\t*\t0\t0\t*\t*\t0\t0\tACGT\tFFFF".as_bytes(),
1495/// )
1496/// .unwrap();
1497///
1498/// // Add an integer field
1499/// let aux_integer_field = Aux::I32(1234);
1500/// record.push_aux(b"XI", aux_integer_field).unwrap();
1501///
1502/// match record.aux(b"XI") {
1503///     Ok(value) => {
1504///         // Typically, callers expect an aux field to be of a certain type.
1505///         // If that's not the case, the value can be `match`ed exhaustively.
1506///         if let Aux::I32(v) = value {
1507///             assert_eq!(v, 1234);
1508///         }
1509///     }
1510///     Err(e) => {
1511///         panic!("Error reading aux field: {}", e);
1512///     }
1513/// }
1514///
1515/// // Add an array field
1516/// let array_like_data = vec![0.4, 0.3, 0.2, 0.1];
1517/// let slice_of_data = &array_like_data;
1518/// let aux_array: AuxArray<f32> = slice_of_data.into();
1519/// let aux_array_field = Aux::ArrayFloat(aux_array);
1520/// record.push_aux(b"XA", aux_array_field).unwrap();
1521///
1522/// if let Ok(Aux::ArrayFloat(array)) = record.aux(b"XA") {
1523///     let read_array = array.iter().collect::<Vec<_>>();
1524///     assert_eq!(read_array, array_like_data);
1525/// } else {
1526///     panic!("Could not read array data");
1527/// }
1528/// ```
1529#[derive(Debug, PartialEq)]
1530pub enum Aux<'a> {
1531    Char(u8),
1532    I8(i8),
1533    U8(u8),
1534    I16(i16),
1535    U16(u16),
1536    I32(i32),
1537    U32(u32),
1538    Float(f32),
1539    Double(f64), // Not part of specs but implemented in `htslib`
1540    String(&'a str),
1541    HexByteArray(&'a str),
1542    ArrayI8(AuxArray<'a, i8>),
1543    ArrayU8(AuxArray<'a, u8>),
1544    ArrayI16(AuxArray<'a, i16>),
1545    ArrayU16(AuxArray<'a, u16>),
1546    ArrayI32(AuxArray<'a, i32>),
1547    ArrayU32(AuxArray<'a, u32>),
1548    ArrayFloat(AuxArray<'a, f32>),
1549}
1550
1551unsafe impl Send for Aux<'_> {}
1552unsafe impl Sync for Aux<'_> {}
1553
1554/// Types that can be used in aux arrays.
1555pub trait AuxArrayElement: Copy {
1556    fn from_le_bytes(bytes: &[u8]) -> Option<Self>;
1557}
1558
1559impl AuxArrayElement for i8 {
1560    fn from_le_bytes(bytes: &[u8]) -> Option<Self> {
1561        std::io::Cursor::new(bytes).read_i8().ok()
1562    }
1563}
1564impl AuxArrayElement for u8 {
1565    fn from_le_bytes(bytes: &[u8]) -> Option<Self> {
1566        std::io::Cursor::new(bytes).read_u8().ok()
1567    }
1568}
1569impl AuxArrayElement for i16 {
1570    fn from_le_bytes(bytes: &[u8]) -> Option<Self> {
1571        std::io::Cursor::new(bytes).read_i16::<LittleEndian>().ok()
1572    }
1573}
1574impl AuxArrayElement for u16 {
1575    fn from_le_bytes(bytes: &[u8]) -> Option<Self> {
1576        std::io::Cursor::new(bytes).read_u16::<LittleEndian>().ok()
1577    }
1578}
1579impl AuxArrayElement for i32 {
1580    fn from_le_bytes(bytes: &[u8]) -> Option<Self> {
1581        std::io::Cursor::new(bytes).read_i32::<LittleEndian>().ok()
1582    }
1583}
1584impl AuxArrayElement for u32 {
1585    fn from_le_bytes(bytes: &[u8]) -> Option<Self> {
1586        std::io::Cursor::new(bytes).read_u32::<LittleEndian>().ok()
1587    }
1588}
1589impl AuxArrayElement for f32 {
1590    fn from_le_bytes(bytes: &[u8]) -> Option<Self> {
1591        std::io::Cursor::new(bytes).read_f32::<LittleEndian>().ok()
1592    }
1593}
1594
1595/// Provides access to aux arrays.
1596///
1597/// Provides methods to either retrieve single elements or an iterator over the
1598/// array.
1599///
1600/// This type is used for wrapping both, array data that was read from a
1601/// BAM record and slices of data that are going to be stored in one.
1602///
1603/// In order to be able to add an `AuxArray` field to a BAM record, `AuxArray`s
1604/// can be constructed via the `From` trait which is implemented for all
1605/// supported types (see [`AuxArrayElement`] for a list).
1606///
1607/// # Examples
1608///
1609/// ```
1610/// use extended_htslib::{
1611///     bam,
1612///     bam::record::{Aux, AuxArray},
1613/// };
1614///
1615/// //Set up BAM record
1616/// let bam_header = bam::Header::new();
1617/// let mut record = bam::Record::from_sam(
1618///     &mut bam::HeaderView::from_header(&bam_header),
1619///     "ali1\t4\t*\t0\t0\t*\t*\t0\t0\tACGT\tFFFF".as_bytes(),
1620/// ).unwrap();
1621///
1622/// let data = vec![0.4, 0.3, 0.2, 0.1];
1623/// let slice_of_data = &data;
1624/// let aux_array: AuxArray<f32> = slice_of_data.into();
1625/// let aux_field = Aux::ArrayFloat(aux_array);
1626/// record.push_aux(b"XA", aux_field);
1627///
1628/// if let Ok(Aux::ArrayFloat(array)) = record.aux(b"XA") {
1629///     // Retrieve the second element from the array
1630///     assert_eq!(array.get(1).unwrap(), 0.3);
1631///     // Iterate over the array and collect it into a `Vec`
1632///     let read_array = array.iter().collect::<Vec<_>>();
1633///     assert_eq!(read_array, data);
1634/// } else {
1635///     panic!("Could not read array data");
1636/// }
1637/// ```
1638#[derive(Debug)]
1639pub enum AuxArray<'a, T> {
1640    TargetType(AuxArrayTargetType<'a, T>),
1641    RawLeBytes(AuxArrayRawLeBytes<'a, T>),
1642}
1643
1644impl<T> PartialEq<AuxArray<'_, T>> for AuxArray<'_, T>
1645where
1646    T: AuxArrayElement + PartialEq,
1647{
1648    fn eq(&self, other: &AuxArray<'_, T>) -> bool {
1649        use AuxArray::*;
1650        match (self, other) {
1651            (TargetType(v), TargetType(v_other)) => v == v_other,
1652            (RawLeBytes(v), RawLeBytes(v_other)) => v == v_other,
1653            (TargetType(_), RawLeBytes(_)) => self.iter().eq(other.iter()),
1654            (RawLeBytes(_), TargetType(_)) => self.iter().eq(other.iter()),
1655        }
1656    }
1657}
1658
1659/// Create AuxArrays from slices of allowed target types.
1660impl<'a, I, T> From<&'a T> for AuxArray<'a, I>
1661where
1662    I: AuxArrayElement,
1663    T: AsRef<[I]> + ?Sized,
1664{
1665    fn from(src: &'a T) -> Self {
1666        AuxArray::TargetType(AuxArrayTargetType {
1667            slice: src.as_ref(),
1668        })
1669    }
1670}
1671
1672impl<'a, T> AuxArray<'a, T>
1673where
1674    T: AuxArrayElement,
1675{
1676    /// Returns the element at a position or None if out of bounds.
1677    pub fn get(&self, index: usize) -> Option<T> {
1678        match self {
1679            AuxArray::TargetType(v) => v.get(index),
1680            AuxArray::RawLeBytes(v) => v.get(index),
1681        }
1682    }
1683
1684    /// Returns the number of elements in the array.
1685    pub fn len(&self) -> usize {
1686        match self {
1687            AuxArray::TargetType(a) => a.len(),
1688            AuxArray::RawLeBytes(a) => a.len(),
1689        }
1690    }
1691
1692    /// Returns true if the array contains no elements.
1693    pub fn is_empty(&self) -> bool {
1694        self.len() == 0
1695    }
1696
1697    /// Returns an iterator over the array.
1698    pub fn iter(&'_ self) -> AuxArrayIter<'_, T> {
1699        AuxArrayIter {
1700            index: 0,
1701            array: self,
1702        }
1703    }
1704
1705    /// Create AuxArrays from raw byte slices borrowed from `bam::Record`.
1706    fn from_bytes(bytes: &'a [u8]) -> Self {
1707        Self::RawLeBytes(AuxArrayRawLeBytes {
1708            slice: bytes,
1709            phantom_data: PhantomData,
1710        })
1711    }
1712}
1713
1714/// Encapsulates slice of target type.
1715#[doc(hidden)]
1716#[derive(Debug, PartialEq)]
1717pub struct AuxArrayTargetType<'a, T> {
1718    slice: &'a [T],
1719}
1720
1721impl<T> AuxArrayTargetType<'_, T>
1722where
1723    T: AuxArrayElement,
1724{
1725    fn get(&self, index: usize) -> Option<T> {
1726        self.slice.get(index).copied()
1727    }
1728
1729    fn len(&self) -> usize {
1730        self.slice.len()
1731    }
1732}
1733
1734/// Encapsulates slice of raw bytes to prevent it from being accidentally accessed.
1735#[doc(hidden)]
1736#[derive(Debug, PartialEq)]
1737pub struct AuxArrayRawLeBytes<'a, T> {
1738    slice: &'a [u8],
1739    phantom_data: PhantomData<T>,
1740}
1741
1742impl<T> AuxArrayRawLeBytes<'_, T>
1743where
1744    T: AuxArrayElement,
1745{
1746    fn get(&self, index: usize) -> Option<T> {
1747        let type_size = std::mem::size_of::<T>();
1748        if index * type_size + type_size > self.slice.len() {
1749            return None;
1750        }
1751        T::from_le_bytes(&self.slice[index * type_size..][..type_size])
1752    }
1753
1754    fn len(&self) -> usize {
1755        self.slice.len() / std::mem::size_of::<T>()
1756    }
1757}
1758
1759/// Aux array iterator
1760///
1761/// This struct is created by the [`AuxArray::iter`] method.
1762pub struct AuxArrayIter<'a, T> {
1763    index: usize,
1764    array: &'a AuxArray<'a, T>,
1765}
1766
1767impl<T> Iterator for AuxArrayIter<'_, T>
1768where
1769    T: AuxArrayElement,
1770{
1771    type Item = T;
1772    fn next(&mut self) -> Option<Self::Item> {
1773        let value = self.array.get(self.index);
1774        self.index += 1;
1775        value
1776    }
1777
1778    fn size_hint(&self) -> (usize, Option<usize>) {
1779        let array_length = self.array.len() - self.index;
1780        (array_length, Some(array_length))
1781    }
1782}
1783
1784/// Auxiliary data iterator
1785///
1786/// This struct is created by the [`Record::aux_iter`] method.
1787///
1788/// This iterator returns `Result`s that wrap tuples containing
1789/// a slice which represents the two-byte tag (`&[u8; 2]`) as
1790/// well as an `Aux` enum that wraps the associated value.
1791///
1792/// When an error occurs, the `Err` variant will be returned
1793/// and the iterator will not be able to advance anymore.
1794pub struct AuxIter<'a> {
1795    aux: &'a [u8],
1796}
1797
1798impl<'a> Iterator for AuxIter<'a> {
1799    type Item = Result<(&'a [u8], Aux<'a>)>;
1800
1801    fn next(&mut self) -> Option<Self::Item> {
1802        // We're finished
1803        if self.aux.is_empty() {
1804            return None;
1805        }
1806        // Incomplete aux data
1807        if (1..=3).contains(&self.aux.len()) {
1808            // In the case of an error, we can not safely advance in the aux data, so we terminate the Iteration
1809            self.aux = &[];
1810            return Some(Err(Error::BamAuxParsingError));
1811        }
1812        let tag = &self.aux[..2];
1813        Some(unsafe {
1814            let data_ptr = self.aux[2..].as_ptr();
1815            Record::read_aux_field(data_ptr)
1816                .map(|(aux, offset)| {
1817                    self.aux = &self.aux[offset..];
1818                    (tag, aux)
1819                })
1820                .inspect_err(|_e| {
1821                    // In the case of an error, we can not safely advance in the aux data, so we terminate the Iteration
1822                    self.aux = &[];
1823                })
1824        })
1825    }
1826}
1827
1828static DECODE_BASE: &[u8] = b"=ACMGRSVTWYHKDBN";
1829static ENCODE_BASE: [u8; 256] = [
1830    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
1831    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
1832    1, 2, 4, 8, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 1, 14, 2, 13, 15, 15, 4, 11, 15,
1833    15, 12, 15, 3, 15, 15, 15, 15, 5, 6, 8, 15, 7, 9, 15, 10, 15, 15, 15, 15, 15, 15, 15, 1, 14, 2,
1834    13, 15, 15, 4, 11, 15, 15, 12, 15, 3, 15, 15, 15, 15, 5, 6, 8, 15, 7, 9, 15, 10, 15, 15, 15,
1835    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
1836    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
1837    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
1838    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
1839    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
1840    15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
1841];
1842
1843#[inline]
1844fn encoded_base(encoded_seq: &[u8], i: usize) -> u8 {
1845    (encoded_seq[i / 2] >> ((!i & 1) << 2)) & 0b1111
1846}
1847
1848#[inline]
1849unsafe fn encoded_base_unchecked(encoded_seq: &[u8], i: usize) -> u8 {
1850    (encoded_seq.get_unchecked(i / 2) >> ((!i & 1) << 2)) & 0b1111
1851}
1852
1853#[inline]
1854fn decode_base_unchecked(base: u8) -> &'static u8 {
1855    unsafe { DECODE_BASE.get_unchecked(base as usize) }
1856}
1857
1858/// The sequence of a record.
1859#[derive(Debug, Copy, Clone)]
1860pub struct Seq<'a> {
1861    pub encoded: &'a [u8],
1862    len: usize,
1863}
1864
1865impl Seq<'_> {
1866    /// Return encoded base. Complexity: O(1).
1867    #[inline]
1868    pub fn encoded_base(&self, i: usize) -> u8 {
1869        encoded_base(self.encoded, i)
1870    }
1871
1872    /// Return encoded base. Complexity: O(1).
1873    ///
1874    /// # Safety
1875    ///
1876    /// TODO
1877    #[inline]
1878    pub unsafe fn encoded_base_unchecked(&self, i: usize) -> u8 {
1879        encoded_base_unchecked(self.encoded, i)
1880    }
1881
1882    /// Obtain decoded base without performing bounds checking.
1883    /// Use index based access seq()[i], for checked, safe access.
1884    /// Complexity: O(1).
1885    ///
1886    /// # Safety
1887    ///
1888    /// TODO
1889    #[inline]
1890    pub unsafe fn decoded_base_unchecked(&self, i: usize) -> u8 {
1891        *decode_base_unchecked(self.encoded_base_unchecked(i))
1892    }
1893
1894    /// Return decoded sequence. Complexity: O(m) with m being the read length.
1895    pub fn as_bytes(&self) -> Vec<u8> {
1896        (0..self.len()).map(|i| self[i]).collect()
1897    }
1898    /// Return a specific range of nucleotides (sequence-based 0) as Cow<str>, return Err if out of bounds
1899    pub fn rangeextract<'a, T>(&'a self, range: T) -> std::io::Result<Cow<'a, str>>
1900    where
1901        T: RangeBounds<usize> + SliceIndex<str>,
1902        <T as SliceIndex<str>>::Output: std::string::ToString,
1903    {
1904        if self.is_empty() {
1905            return Err(std::io::Error::from(std::io::ErrorKind::InvalidInput));
1906        }
1907        let seq = match String::from_utf8(self.as_bytes().to_vec()) {
1908            Ok(d) if d.is_ascii() => d,
1909            _ => return Err(std::io::Error::from(std::io::ErrorKind::InvalidData)),
1910        };
1911        let substring = match seq.get(range) {
1912            Some(d) => d,
1913            None => return Err(std::io::Error::from(std::io::ErrorKind::InvalidInput)),
1914        };
1915
1916        Ok(Cow::Owned(substring.to_string()))
1917    }
1918    /// Return length (in bases) of the sequence.
1919    pub fn len(&self) -> usize {
1920        self.len
1921    }
1922
1923    pub fn is_empty(&self) -> bool {
1924        self.len() == 0
1925    }
1926}
1927
1928impl ops::Index<usize> for Seq<'_> {
1929    type Output = u8;
1930
1931    /// Return decoded base at given position within read. Complexity: O(1).
1932    fn index(&self, index: usize) -> &u8 {
1933        decode_base_unchecked(self.encoded_base(index))
1934    }
1935}
1936
1937unsafe impl Send for Seq<'_> {}
1938unsafe impl Sync for Seq<'_> {}
1939
1940#[cfg_attr(feature = "serde_feature", derive(Serialize, Deserialize))]
1941#[derive(PartialEq, PartialOrd, Eq, Debug, Clone, Copy, Hash)]
1942/// Cigar format
1943pub enum Cigar {
1944    Match(u32),    // M
1945    Ins(u32),      // I
1946    Del(u32),      // D
1947    RefSkip(u32),  // N
1948    SoftClip(u32), // S
1949    HardClip(u32), // H
1950    Pad(u32),      // P
1951    Equal(u32),    // =
1952    Diff(u32),     // X
1953}
1954
1955impl Cigar {
1956    fn encode(self) -> u32 {
1957        match self {
1958            Cigar::Match(len) => len << 4, // | 0,
1959            Cigar::Ins(len) => (len << 4) | 1,
1960            Cigar::Del(len) => (len << 4) | 2,
1961            Cigar::RefSkip(len) => (len << 4) | 3,
1962            Cigar::SoftClip(len) => (len << 4) | 4,
1963            Cigar::HardClip(len) => (len << 4) | 5,
1964            Cigar::Pad(len) => (len << 4) | 6,
1965            Cigar::Equal(len) => (len << 4) | 7,
1966            Cigar::Diff(len) => (len << 4) | 8,
1967        }
1968    }
1969
1970    /// Return the length of the CIGAR.
1971    pub fn len(self) -> u32 {
1972        match self {
1973            Cigar::Match(len) => len,
1974            Cigar::Ins(len) => len,
1975            Cigar::Del(len) => len,
1976            Cigar::RefSkip(len) => len,
1977            Cigar::SoftClip(len) => len,
1978            Cigar::HardClip(len) => len,
1979            Cigar::Pad(len) => len,
1980            Cigar::Equal(len) => len,
1981            Cigar::Diff(len) => len,
1982        }
1983    }
1984
1985    pub fn is_empty(self) -> bool {
1986        self.len() == 0
1987    }
1988
1989    /// Return the character representing the CIGAR.
1990    pub fn char(self) -> char {
1991        match self {
1992            Cigar::Match(_) => 'M',
1993            Cigar::Ins(_) => 'I',
1994            Cigar::Del(_) => 'D',
1995            Cigar::RefSkip(_) => 'N',
1996            Cigar::SoftClip(_) => 'S',
1997            Cigar::HardClip(_) => 'H',
1998            Cigar::Pad(_) => 'P',
1999            Cigar::Equal(_) => '=',
2000            Cigar::Diff(_) => 'X',
2001        }
2002    }
2003}
2004
2005impl fmt::Display for Cigar {
2006    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
2007        fmt.write_fmt(format_args!("{}{}", self.len(), self.char()))
2008    }
2009}
2010
2011unsafe impl Send for Cigar {}
2012unsafe impl Sync for Cigar {}
2013
2014custom_derive! {
2015    /// A CIGAR string. This type wraps around a `Vec<Cigar>`.
2016    ///
2017    /// # Example
2018    ///
2019    /// ```
2020    /// use extended_htslib::bam::record::{Cigar, CigarString};
2021    ///
2022    /// let cigar = CigarString(vec![Cigar::Match(100), Cigar::SoftClip(10)]);
2023    ///
2024    /// // access by index
2025    /// assert_eq!(cigar[0], Cigar::Match(100));
2026    /// // format into classical string representation
2027    /// assert_eq!(format!("{}", cigar), "100M10S");
2028    /// // iterate
2029    /// for op in &cigar {
2030    ///    println!("{}", op);
2031    /// }
2032    /// ```
2033    #[cfg_attr(feature = "serde_feature", derive(Serialize, Deserialize))]
2034    #[derive(NewtypeDeref,
2035            NewtypeDerefMut,
2036             NewtypeIndex(usize),
2037             NewtypeIndexMut(usize),
2038             NewtypeFrom,
2039             PartialEq,
2040             PartialOrd,
2041             Eq,
2042             NewtypeDebug,
2043             Clone,
2044             Hash
2045    )]
2046    pub struct CigarString(pub Vec<Cigar>);
2047}
2048
2049impl CigarString {
2050    /// Create a `CigarStringView` from this CigarString at position `pos`
2051    pub fn into_view(self, pos: i64) -> CigarStringView {
2052        CigarStringView::new(self, pos)
2053    }
2054    /// Contains =/X instead of classic M (match)
2055    pub fn containsequal(&self) -> bool {
2056        self.iter()
2057            .any(|f| matches!(f, &Cigar::Equal(_)) || matches!(f, &Cigar::Diff(_)))
2058            || self.iter().all(|f| !matches!(f, &Cigar::Match(_)))
2059    }
2060    /// Calculate the bam cigar from the alignment struct. x is the target string
2061    /// and y is the reference. `hard_clip` controls how unaligned read bases are encoded in the
2062    /// cigar string. Set to true to use the hard clip (`H`) code, or false to use soft clip
2063    /// (`S`) code. See the [SAM spec](https://samtools.github.io/hts-specs/SAMv1.pdf) for more details.
2064    pub fn from_alignment(alignment: &Alignment, hard_clip: bool) -> Self {
2065        match alignment.mode {
2066            AlignmentMode::Global => {
2067                panic!(" Bam cigar fn not supported for Global Alignment mode")
2068            }
2069            AlignmentMode::Local => panic!(" Bam cigar fn not supported for Local Alignment mode"),
2070            _ => {}
2071        }
2072
2073        let mut cigar = Vec::new();
2074        if alignment.operations.is_empty() {
2075            return CigarString(cigar);
2076        }
2077
2078        let add_op = |op: AlignmentOperation, length: u32, cigar: &mut Vec<Cigar>| match op {
2079            AlignmentOperation::Del => cigar.push(Cigar::Del(length)),
2080            AlignmentOperation::Ins => cigar.push(Cigar::Ins(length)),
2081            AlignmentOperation::Subst => cigar.push(Cigar::Diff(length)),
2082            AlignmentOperation::Match => cigar.push(Cigar::Equal(length)),
2083            _ => {}
2084        };
2085
2086        if alignment.xstart > 0 {
2087            cigar.push(if hard_clip {
2088                Cigar::HardClip(alignment.xstart as u32)
2089            } else {
2090                Cigar::SoftClip(alignment.xstart as u32)
2091            });
2092        }
2093
2094        let mut last = alignment.operations[0];
2095        let mut k = 1u32;
2096        for &op in alignment.operations[1..].iter() {
2097            if op == last {
2098                k += 1;
2099            } else {
2100                add_op(last, k, &mut cigar);
2101                k = 1;
2102            }
2103            last = op;
2104        }
2105        add_op(last, k, &mut cigar);
2106        if alignment.xlen > alignment.xend {
2107            cigar.push(if hard_clip {
2108                Cigar::HardClip((alignment.xlen - alignment.xend) as u32)
2109            } else {
2110                Cigar::SoftClip((alignment.xlen - alignment.xend) as u32)
2111            });
2112        }
2113
2114        CigarString(cigar)
2115    }
2116}
2117
2118impl TryFrom<&[u8]> for CigarString {
2119    type Error = Error;
2120
2121    /// Create a CigarString from given &[u8].
2122    /// # Example
2123    /// ```
2124    /// use extended_htslib::bam::record::*;
2125    /// use extended_htslib::bam::record::CigarString;
2126    /// use extended_htslib::bam::record::Cigar::*;
2127    /// use std::convert::TryFrom;
2128    ///
2129    /// let cigar_str = "2H10M5X3=2H".as_bytes();
2130    /// let cigar = CigarString::try_from(cigar_str)
2131    ///     .expect("Unable to parse cigar string.");
2132    /// let expected_cigar = CigarString(vec![
2133    ///     HardClip(2),
2134    ///     Match(10),
2135    ///     Diff(5),
2136    ///     Equal(3),
2137    ///     HardClip(2),
2138    /// ]);
2139    /// assert_eq!(cigar, expected_cigar);
2140    /// ```
2141    fn try_from(bytes: &[u8]) -> Result<Self> {
2142        let mut inner = Vec::new();
2143        let mut i = 0;
2144        let text_len = bytes.len();
2145        while i < text_len {
2146            let mut j = i;
2147            while j < text_len && bytes[j].is_ascii_digit() {
2148                j += 1;
2149            }
2150            // check that length is provided
2151            if i == j {
2152                return Err(Error::BamParseCigar {
2153                    msg: "Expected length before cigar operation [0-9]+[MIDNSHP=X]".to_owned(),
2154                });
2155            }
2156            // get the length of the operation
2157            let s = str::from_utf8(&bytes[i..j]).map_err(|_| Error::BamParseCigar {
2158                msg: format!("Invalid utf-8 bytes '{:?}'.", &bytes[i..j]),
2159            })?;
2160            let n = s.parse().map_err(|_| Error::BamParseCigar {
2161                msg: format!("Unable to parse &str '{:?}' to u32.", s),
2162            })?;
2163            // get the operation
2164            let op = &bytes[j];
2165            inner.push(match op {
2166                b'M' => Cigar::Match(n),
2167                b'I' => Cigar::Ins(n),
2168                b'D' => Cigar::Del(n),
2169                b'N' => Cigar::RefSkip(n),
2170                b'H' => {
2171                    if i == 0 || j + 1 == text_len {
2172                        Cigar::HardClip(n)
2173                    } else {
2174                        return Err(Error::BamParseCigar {
2175                            msg: "Hard clipping ('H') is only valid at the start or end of a cigar."
2176                                .to_owned(),
2177                        });
2178                    }
2179                }
2180                b'S' => {
2181                    if i == 0
2182                        || j + 1 == text_len
2183                        || bytes[i-1] == b'H'
2184                        || bytes[j+1..].iter().all(|c| c.is_ascii_digit() || *c == b'H') {
2185                        Cigar::SoftClip(n)
2186                    } else {
2187                        return Err(Error::BamParseCigar {
2188                        msg: "Soft clips ('S') can only have hard clips ('H') between them and the end of the CIGAR string."
2189                            .to_owned(),
2190                        });
2191                    }
2192                },
2193                b'P' => Cigar::Pad(n),
2194                b'=' => Cigar::Equal(n),
2195                b'X' => Cigar::Diff(n),
2196                op => {
2197                    return Err(Error::BamParseCigar {
2198                        msg: format!("Expected cigar operation [MIDNSHP=X] but got [{}]", op),
2199                    })
2200                }
2201            });
2202            i = j + 1;
2203        }
2204        Ok(CigarString(inner))
2205    }
2206}
2207
2208impl TryFrom<&str> for CigarString {
2209    type Error = Error;
2210
2211    /// Create a CigarString from given &str.
2212    /// # Example
2213    /// ```
2214    /// use extended_htslib::bam::record::*;
2215    /// use extended_htslib::bam::record::CigarString;
2216    /// use extended_htslib::bam::record::Cigar::*;
2217    /// use std::convert::TryFrom;
2218    ///
2219    /// let cigar_str = "2H10M5X3=2H";
2220    /// let cigar = CigarString::try_from(cigar_str)
2221    ///     .expect("Unable to parse cigar string.");
2222    /// let expected_cigar = CigarString(vec![
2223    ///     HardClip(2),
2224    ///     Match(10),
2225    ///     Diff(5),
2226    ///     Equal(3),
2227    ///     HardClip(2),
2228    /// ]);
2229    /// assert_eq!(cigar, expected_cigar);
2230    /// ```
2231    fn try_from(text: &str) -> Result<Self> {
2232        let bytes = text.as_bytes();
2233        if text.chars().count() != bytes.len() {
2234            return Err(Error::BamParseCigar {
2235                msg: "CIGAR string contained non-ASCII characters, which are not valid. Valid are [0-9MIDNSHP=X].".to_owned(),
2236            });
2237        }
2238        CigarString::try_from(bytes)
2239    }
2240}
2241
2242impl<'a> CigarString {
2243    pub fn iter(&'a self) -> ::std::slice::Iter<'a, Cigar> {
2244        self.into_iter()
2245    }
2246}
2247
2248impl<'a> IntoIterator for &'a CigarString {
2249    type Item = &'a Cigar;
2250    type IntoIter = ::std::slice::Iter<'a, Cigar>;
2251
2252    fn into_iter(self) -> Self::IntoIter {
2253        self.0.iter()
2254    }
2255}
2256
2257impl fmt::Display for CigarString {
2258    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
2259        for op in self {
2260            fmt.write_fmt(format_args!("{}{}", op.len(), op.char()))?;
2261        }
2262        Ok(())
2263    }
2264}
2265
2266// Get number of leading/trailing softclips if a CigarString taking hardclips into account
2267fn calc_softclips<'a>(mut cigar: impl DoubleEndedIterator<Item = &'a Cigar>) -> i64 {
2268    match (cigar.next(), cigar.next()) {
2269        (Some(Cigar::HardClip(_)), Some(Cigar::SoftClip(s))) | (Some(Cigar::SoftClip(s)), _) => {
2270            *s as i64
2271        }
2272        _ => 0,
2273    }
2274}
2275
2276#[derive(Eq, PartialEq, Clone, Debug)]
2277pub struct CigarStringView {
2278    inner: CigarString,
2279    pos: i64,
2280}
2281
2282impl CigarStringView {
2283    /// Construct a new CigarStringView from a CigarString at a position
2284    pub fn new(c: CigarString, pos: i64) -> CigarStringView {
2285        CigarStringView { inner: c, pos }
2286    }
2287    /// Contains =/X instead of classic M (match)
2288    pub fn containsequal(&self) -> bool {
2289        self.inner.containsequal()
2290    }
2291    /// Get (exclusive) end position of alignment.
2292    pub fn end_pos(&self) -> i64 {
2293        let mut pos = self.pos;
2294        for c in self {
2295            match c {
2296                Cigar::Match(l)
2297                | Cigar::RefSkip(l)
2298                | Cigar::Del(l)
2299                | Cigar::Equal(l)
2300                | Cigar::Diff(l) => pos += *l as i64,
2301                // these don't add to end_pos on reference
2302                Cigar::Ins(_) | Cigar::SoftClip(_) | Cigar::HardClip(_) | Cigar::Pad(_) => (),
2303            }
2304        }
2305        pos
2306    }
2307
2308    /// Get the start position of the alignment (0-based).
2309    pub fn pos(&self) -> i64 {
2310        self.pos
2311    }
2312
2313    /// Get number of bases softclipped at the beginning of the alignment.
2314    pub fn leading_softclips(&self) -> i64 {
2315        calc_softclips(self.iter())
2316    }
2317
2318    /// Get number of bases softclipped at the end of the alignment.
2319    pub fn trailing_softclips(&self) -> i64 {
2320        calc_softclips(self.iter().rev())
2321    }
2322
2323    /// Get number of bases hardclipped at the beginning of the alignment.
2324    pub fn leading_hardclips(&self) -> i64 {
2325        self.first().map_or(0, |cigar| {
2326            if let Cigar::HardClip(s) = cigar {
2327                *s as i64
2328            } else {
2329                0
2330            }
2331        })
2332    }
2333
2334    /// Get number of bases hardclipped at the end of the alignment.
2335    pub fn trailing_hardclips(&self) -> i64 {
2336        self.last().map_or(0, |cigar| {
2337            if let Cigar::HardClip(s) = cigar {
2338                *s as i64
2339            } else {
2340                0
2341            }
2342        })
2343    }
2344
2345    /// For a given position in the reference, get corresponding position within read.
2346    /// If reference position is outside of the read alignment, return None.
2347    ///
2348    /// # Arguments
2349    ///
2350    /// * `ref_pos` - the reference position
2351    /// * `include_softclips` - if true, softclips will be considered as matches or mismatches
2352    /// * `include_dels` - if true, positions within deletions will be considered (first reference matching read position after deletion will be returned)
2353    ///
2354    pub fn read_pos(
2355        &self,
2356        ref_pos: u32,
2357        include_softclips: bool,
2358        include_dels: bool,
2359    ) -> Result<Option<u32>> {
2360        let mut rpos = self.pos as u32; // reference position
2361        let mut qpos = 0u32; // position within read
2362        let mut j = 0; // index into cigar operation vector
2363
2364        // find first cigar operation referring to qpos = 0 (and thus bases in record.seq()),
2365        // because all augmentations of qpos and rpos before that are invalid
2366        for (i, c) in self.iter().enumerate() {
2367            match c {
2368                Cigar::Match(_) |
2369                Cigar::Diff(_)  |
2370                Cigar::Equal(_) |
2371                // this is unexpected, but bwa + GATK indel realignment can produce insertions
2372                // before matching positions
2373                Cigar::Ins(_) => {
2374                    j = i;
2375                    break;
2376                },
2377                Cigar::SoftClip(l) => {
2378                    j = i;
2379                    if include_softclips {
2380                        // Alignment starts with softclip and we want to include it in the
2381                        // projection of the reference position. However, the POS field does not
2382                        // include the softclip. Hence we have to subtract its length.
2383                        rpos = rpos.saturating_sub(*l);
2384                    }
2385                    break;
2386                },
2387                Cigar::Del(l) => {
2388                    // METHOD: leading deletions can happen in case of trimmed reads where
2389                    // a primer has been removed AFTER read mapping.
2390                    // Example: 24M8I8D18M9S before trimming, 32H8D18M9S after trimming
2391                    // with fgbio. While leading deletions should be impossible with
2392                    // normal read mapping, they make perfect sense with primer trimming
2393                    // because the mapper still had the evidence to decide in favor of
2394                    // the deletion via the primer sequence.
2395                    rpos += l;
2396                },
2397                Cigar::RefSkip(_) => {
2398                    return Err(Error::BamUnexpectedCigarOperation {
2399                        msg: "'reference skip' (N) found before any operation describing read sequence".to_owned()
2400                    });
2401                },
2402                Cigar::HardClip(_) if i > 0 && i < self.len()-1 => {
2403                    return Err(Error::BamUnexpectedCigarOperation{
2404                        msg: "'hard clip' (H) found in between operations, contradicting SAMv1 spec that hard clips can only be at the ends of reads".to_owned()
2405                    });
2406                },
2407                // if we have reached the end of the CigarString with only pads and hard clips, we have no read position matching the variant
2408                Cigar::Pad(_) | Cigar::HardClip(_) if i == self.len()-1 => return Ok(None),
2409                // skip leading HardClips and Pads, as they consume neither read sequence nor reference sequence
2410                Cigar::Pad(_) | Cigar::HardClip(_) => ()
2411            }
2412        }
2413
2414        let contains_ref_pos = |cigar_op_start: u32, cigar_op_length: u32| {
2415            cigar_op_start <= ref_pos && cigar_op_start + cigar_op_length > ref_pos
2416        };
2417
2418        while rpos <= ref_pos && j < self.len() {
2419            match self[j] {
2420                // potential SNV evidence
2421                Cigar::Match(l) | Cigar::Diff(l) | Cigar::Equal(l) if contains_ref_pos(rpos, l) => {
2422                    // difference between desired position and first position of current cigar
2423                    // operation
2424                    qpos += ref_pos - rpos;
2425                    return Ok(Some(qpos));
2426                }
2427                Cigar::SoftClip(l) if include_softclips && contains_ref_pos(rpos, l) => {
2428                    qpos += ref_pos - rpos;
2429                    return Ok(Some(qpos));
2430                }
2431                Cigar::Del(l) if include_dels && contains_ref_pos(rpos, l) => {
2432                    // qpos shall resemble the start of the deletion
2433                    return Ok(Some(qpos));
2434                }
2435                // for others, just increase pos and qpos as needed
2436                Cigar::Match(l) | Cigar::Diff(l) | Cigar::Equal(l) => {
2437                    rpos += l;
2438                    qpos += l;
2439                    j += 1;
2440                }
2441                Cigar::SoftClip(l) => {
2442                    qpos += l;
2443                    j += 1;
2444                    if include_softclips {
2445                        rpos += l;
2446                    }
2447                }
2448                Cigar::Ins(l) => {
2449                    qpos += l;
2450                    j += 1;
2451                }
2452                Cigar::RefSkip(l) | Cigar::Del(l) => {
2453                    rpos += l;
2454                    j += 1;
2455                }
2456                Cigar::Pad(_) => {
2457                    j += 1;
2458                }
2459                Cigar::HardClip(_) if j < self.len() - 1 => {
2460                    return Err(Error::BamUnexpectedCigarOperation{
2461                        msg: "'hard clip' (H) found in between operations, contradicting SAMv1 spec that hard clips can only be at the ends of reads".to_owned()
2462                    });
2463                }
2464                Cigar::HardClip(_) => return Ok(None),
2465            }
2466        }
2467
2468        Ok(None)
2469    }
2470
2471    /// transfer ownership of the Cigar out of the CigarView
2472    pub fn take(self) -> CigarString {
2473        self.inner
2474    }
2475}
2476
2477impl ops::Deref for CigarStringView {
2478    type Target = CigarString;
2479
2480    fn deref(&self) -> &CigarString {
2481        &self.inner
2482    }
2483}
2484
2485impl ops::Index<usize> for CigarStringView {
2486    type Output = Cigar;
2487
2488    fn index(&self, index: usize) -> &Cigar {
2489        self.inner.index(index)
2490    }
2491}
2492
2493impl ops::IndexMut<usize> for CigarStringView {
2494    fn index_mut(&mut self, index: usize) -> &mut Cigar {
2495        self.inner.index_mut(index)
2496    }
2497}
2498
2499impl<'a> CigarStringView {
2500    pub fn iter(&'a self) -> ::std::slice::Iter<'a, Cigar> {
2501        self.inner.into_iter()
2502    }
2503}
2504
2505impl<'a> IntoIterator for &'a CigarStringView {
2506    type Item = &'a Cigar;
2507    type IntoIter = ::std::slice::Iter<'a, Cigar>;
2508
2509    fn into_iter(self) -> Self::IntoIter {
2510        self.inner.into_iter()
2511    }
2512}
2513
2514impl fmt::Display for CigarStringView {
2515    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
2516        self.inner.fmt(fmt)
2517    }
2518}
2519/// Enum of CS
2520#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2521pub enum CsSame {
2522    /// Long form of CS
2523    Full(String),
2524    /// Short form CS
2525    Small(usize),
2526}
2527impl CsSame {
2528    /// Get size of element
2529    pub fn getsize(&self) -> usize {
2530        match self {
2531            CsSame::Full(a) => a.len(),
2532            CsSame::Small(b) => *b,
2533        }
2534    }
2535    /// Is a long form?
2536    pub fn islongform(&self) -> bool {
2537        match self {
2538            CsSame::Full(_) => true,
2539            CsSame::Small(_) => false,
2540        }
2541    }
2542}
2543///Value of CS tag
2544#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2545pub enum CsValue {
2546    /// Equal base
2547    Same(CsSame),
2548    /// One substitution
2549    Substitution((char, char)),
2550    /// Insertion
2551    Insertion(String),
2552    /// Deletion
2553    Deletion(String),
2554    /// Intron (acceptor/length/donor)
2555    Intron((String, usize, String)),
2556}
2557impl std::fmt::Display for CsValue {
2558    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2559        match self {
2560            CsValue::Same(a) => write!(f, ":{}", a.getsize()),
2561            CsValue::Substitution((a, b)) => write!(f, "*{}{}", a, b),
2562            CsValue::Insertion(a) => write!(f, "+{}", a),
2563            CsValue::Deletion(a) => write!(f, "-{}", a),
2564            CsValue::Intron((a, b, c)) => write!(f, "~{}{}{}", a, b, c),
2565        }
2566    }
2567}
2568impl FromStr for CsValue {
2569    type Err = crate::bam::Error;
2570    fn from_str(s: &str) -> Result<Self, Self::Err> {
2571        let s = if s.trim().len() <= 1 || s.chars().any(|p| !p.is_ascii()) {
2572            return Err(crate::errors::Error::BamParseCS {
2573                rec: "Bad CS parsing".to_string(),
2574            });
2575        } else {
2576            s.trim()
2577        };
2578        match s.split_at_checked(1) {
2579            Some((a, b)) if let Ok(d) = char::from_str(a) => CsValue::new(d, b),
2580            _ => {
2581                return Err(crate::errors::Error::BamParseCS {
2582                    rec: format!("Bad CS parsing for {s}"),
2583                });
2584            }
2585        }
2586    }
2587}
2588impl CsValue {
2589    pub fn issame(&self) -> bool {
2590        matches!(self, CsValue::Same(_))
2591    }
2592    pub fn isubstitution(&self) -> bool {
2593        matches!(self, CsValue::Substitution(_))
2594    }
2595    /// # Errors
2596    /// Can return none if there is no sequence in the MD tag (short version of = & intron)
2597    pub fn getseq(&self) -> Option<String> {
2598        match self {
2599            CsValue::Same(CsSame::Full(a)) => Some(a.to_string()),
2600            CsValue::Same(CsSame::Small(_)) => None,
2601            CsValue::Substitution((a, b)) => Some(format!("{}{}", a, b)),
2602            CsValue::Insertion(a) => Some(a.to_string()),
2603            CsValue::Deletion(a) => Some(a.to_string()),
2604            CsValue::Intron(_) => None,
2605        }
2606    }
2607    pub fn isinsertion(&self) -> bool {
2608        matches!(self, CsValue::Insertion(_))
2609    }
2610    pub fn isdeletion(&self) -> bool {
2611        matches!(self, CsValue::Deletion(_))
2612    }
2613    pub fn getlength(&self) -> usize {
2614        match self {
2615            CsValue::Intron(d) => d.1 + 4,
2616            CsValue::Same(a) => a.getsize(),
2617            CsValue::Deletion(d) | CsValue::Insertion(d) => d.len(),
2618            CsValue::Substitution(_) => 1,
2619        }
2620    }
2621    pub fn isintron(&self) -> bool {
2622        matches!(self, CsValue::Intron(_))
2623    }
2624}
2625impl CsValue {
2626    //Value is the symbol and valtocheck is the value associated
2627    pub fn new<T>(value: char, valtocheck: T) -> Result<CsValue, crate::bam::Error>
2628    where
2629        T: AsRef<str>,
2630    {
2631        let valtocheck = valtocheck.as_ref().to_ascii_lowercase().trim().to_string();
2632        match value {
2633            '=' | ':' => {
2634                match valtocheck.parse::<usize>() {
2635                    Ok(d) => Ok(CsValue::Same(CsSame::Small(d))),
2636                    Err(_) => {
2637                        //Long form
2638                        Ok(CsValue::Same(CsSame::Full(String::from(valtocheck))))
2639                    }
2640                }
2641            }
2642            id @ ('-' | '+' | '*') => {
2643                let seq = String::from(valtocheck);
2644                let state = match id {
2645                    '-' => CsValue::Deletion(seq),
2646                    '+' => CsValue::Insertion(seq),
2647                    '*' => {
2648                        match seq
2649                            .split_at_checked(1)
2650                            .map(|(a, b)| (char::from_str(a), char::from_str(b)))
2651                        {
2652                            Some((Ok(a), Ok(b))) if a != b => CsValue::Substitution((a, b)),
2653                            _ => {
2654                                return Err(crate::errors::Error::BamParseCS {
2655                                    rec: "CS tag invalid".to_string(),
2656                                });
2657                            }
2658                        }
2659                    }
2660                    _ => todo!(),
2661                };
2662                Ok(state)
2663            }
2664            '~' => {
2665                if valtocheck.len() <= 5 || valtocheck.chars().any(|p| !p.is_ascii()) {
2666                    return Err(crate::errors::Error::BamParseCS {
2667                        rec: "CS tag invalid".to_string(),
2668                    });
2669                }
2670                let seq = valtocheck.chars().take(2).collect::<String>();
2671                let seq2 = valtocheck
2672                    .chars()
2673                    .skip(valtocheck.len() - 2)
2674                    .collect::<String>();
2675                let full = valtocheck
2676                    .chars()
2677                    .skip(2)
2678                    .take(valtocheck.len().saturating_sub(4))
2679                    .collect::<String>();
2680                let ilength = match full.parse::<usize>() {
2681                    Ok(d) => d,
2682                    Err(_) => {
2683                        return Err(crate::errors::Error::BamParseCS {
2684                            rec: "CS tag invalid".to_string(),
2685                        });
2686                    }
2687                };
2688                Ok(CsValue::Intron((
2689                    seq.to_string(),
2690                    ilength,
2691                    seq2.to_string(),
2692                )))
2693            }
2694            _ => Err(crate::bam::Error::BamAuxParsingError),
2695        }
2696    }
2697}
2698custom_derive! {
2699    /// A CS String. Implements a combination of CsValue as a string. This type wraps around a `Vec<CsValue>`.
2700    ///
2701    /// # Example
2702    ///
2703    /// ```
2704    /// use extended_htslib::bam::record::{CsValue, CsString,CsSame};
2705    ///
2706    /// let csval = CsString(vec![CsValue::Same(CsSame::Small(100)), CsValue::Deletion("aaattccggc".to_string())]);
2707    ///
2708    /// // access by index
2709    /// assert_eq!(csval[0], CsValue::Same(CsSame::Small(100)));
2710    /// // format into classical string representation
2711    /// assert_eq!(format!("{}", csval), ":100-aaattccggc");
2712    /// // iterate
2713    /// for op in &csval {
2714    ///    println!("{}", op);
2715    /// }
2716    /// ```
2717    #[cfg_attr(feature = "serde_feature", derive(Serialize, Deserialize))]
2718    #[derive(NewtypeDeref,
2719            NewtypeDerefMut,
2720             NewtypeIndex(usize),
2721             NewtypeIndexMut(usize),
2722             NewtypeFrom,
2723             PartialEq,
2724             Eq,
2725             NewtypeDebug,
2726             Clone,
2727             Hash
2728    )]
2729    pub struct CsString(pub Vec<CsValue>);
2730}
2731
2732/// Value must be a valid short or long Cs tag.
2733/// ```
2734/// use extended_htslib::bam::record::{CsSame,CsValue,CsString};
2735/// use std::str::FromStr;
2736/// let vec = vec![
2737///     CsValue::Same(CsSame::Small(20)),
2738///     CsValue::Insertion("ccc".to_string()),
2739///     CsValue::Same(CsSame::Small(40)),
2740///     CsValue::Substitution(('a', 't')),
2741///     CsValue::Substitution(('a', 'g')),
2742///     CsValue::Deletion("ct".to_string()),
2743///     CsValue::Intron(("at".to_string(), 12, "ag".to_string())),
2744/// ];
2745/// let c = CsString(vec);
2746/// assert_eq!(CsString::from_str(":20+ccc:40*at*ag-ct~at12ag").unwrap(),c);
2747/// ```
2748/// # Errors
2749/// Value is not a valid CsString
2750/// ```
2751/// use extended_htslib::bam::record::CsString;
2752/// use std::str::FromStr;
2753/// assert!(CsString::from_str(":20+ccc:40*aa*ag~at12ag").is_err());
2754/// ```
2755impl<'a> IntoIterator for &'a CsString {
2756    type Item = &'a CsValue;
2757    type IntoIter = ::std::slice::Iter<'a, CsValue>;
2758    fn into_iter(self) -> Self::IntoIter {
2759        self.0.iter()
2760    }
2761}
2762impl FromStr for CsString {
2763    type Err = crate::bam::Error;
2764
2765    fn from_str(s: &str) -> Result<Self, Self::Err> {
2766        let isnotalphanum = |p: char| !p.is_alphanumeric();
2767        if !s.starts_with(isnotalphanum) {
2768            return Err(crate::errors::Error::BamParseCS {
2769                rec: "CS tag starts with an alphanumeric char".to_string(),
2770            })?;
2771        }
2772        let s_reversed = s.chars().rev().collect::<String>();
2773        let blocks = s_reversed
2774            .split_inclusive(isnotalphanum)
2775            // reverse each block in the iterator...
2776            .map(|block| block.chars().rev().collect::<String>())
2777            // and then reverse the whole iterator
2778            .rev();
2779        let mut list = Vec::new();
2780        for block in blocks {
2781            let v = CsValue::from_str(&block)
2782                .map_err(|e| crate::errors::Error::BamParseCS { rec: e.to_string() })?;
2783            list.push(v)
2784        }
2785        Ok(CsString(list))
2786    }
2787}
2788impl Display for CsString {
2789    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2790        for elem in self.0.iter() {
2791            write!(f, "{}", elem)?;
2792        }
2793        Ok(())
2794    }
2795}
2796impl CsString {
2797    pub fn islongcs(&self) -> bool {
2798        self.iter().filter(|p| p.issame()).any(|a| match a {
2799            CsValue::Same(CsSame::Full(_)) => true,
2800            CsValue::Same(CsSame::Small(_)) => false,
2801            _ => unreachable!("Filter by issame"),
2802        })
2803    }
2804}
2805pub struct BaseModificationMetadata {
2806    pub strand: i32,
2807    pub implicit: i32,
2808    pub canonical: u8,
2809}
2810
2811/// struct containing the internal state required to access
2812/// the base modifications for a bam::Record
2813pub struct BaseModificationState<'a> {
2814    record: &'a Record,
2815    state: *mut htslib::hts_base_mod_state,
2816    buffer: Vec<htslib::hts_base_mod>,
2817    buffer_pos: i32,
2818}
2819
2820impl BaseModificationState<'_> {
2821    /// Initialize a new BaseModification struct from a bam::Record
2822    /// This function allocates memory for the state structure
2823    /// and initializes the iterator to the start of the modification
2824    /// records.
2825    fn new(r: &Record) -> Result<BaseModificationState<'_>> {
2826        let mut bm = unsafe {
2827            BaseModificationState {
2828                record: r,
2829                state: hts_sys::hts_base_mod_state_alloc(),
2830                buffer: Vec::new(),
2831                buffer_pos: -1,
2832            }
2833        };
2834
2835        if bm.state.is_null() {
2836            panic!("Unable to allocate memory for hts_base_mod_state");
2837        }
2838
2839        // parse the MM tag to initialize the state
2840        unsafe {
2841            let ret = hts_sys::bam_parse_basemod(bm.record.inner_ptr(), bm.state);
2842            if ret != 0 {
2843                return Err(Error::BamBaseModificationTagNotFound);
2844            }
2845        }
2846
2847        let types = bm.recorded();
2848        bm.buffer.reserve(types.len());
2849        Ok(bm)
2850    }
2851
2852    pub fn buffer_next_mods(&mut self) -> Result<usize> {
2853        unsafe {
2854            let ret = hts_sys::bam_next_basemod(
2855                self.record.inner_ptr(),
2856                self.state,
2857                self.buffer.as_mut_ptr(),
2858                self.buffer.capacity() as i32,
2859                &mut self.buffer_pos,
2860            );
2861
2862            if ret < 0 {
2863                return Err(Error::BamBaseModificationIterationFailed);
2864            }
2865
2866            // the htslib API won't write more than buffer.capacity() mods to the output array but it will
2867            // return the actual number of modifications found. We return an error to the caller
2868            // in the case where there was insufficient storage to return all mods.
2869            if ret as usize > self.buffer.capacity() {
2870                return Err(Error::BamBaseModificationTooManyMods);
2871            }
2872
2873            // we read the modifications directly into the vector, which does
2874            // not update the length so needs to be manually set
2875            self.buffer.set_len(ret as usize);
2876
2877            Ok(ret as usize)
2878        }
2879    }
2880
2881    /// Return an array containing the modification codes listed for this record.
2882    /// Positive values are ascii character codes (eg m), negative values are chEBI codes.
2883    pub fn recorded<'a>(&self) -> &'a [i32] {
2884        unsafe {
2885            let mut n: i32 = 0;
2886            let data_ptr: *const i32 = hts_sys::bam_mods_recorded(self.state, &mut n);
2887
2888            // htslib should not return a null pointer, even when there are no base mods
2889            if data_ptr.is_null() {
2890                panic!("Unable to obtain pointer to base modifications");
2891            }
2892            assert!(n >= 0);
2893            slice::from_raw_parts(data_ptr, n as usize)
2894        }
2895    }
2896
2897    /// Return metadata for the specified character code indicating the strand
2898    /// the base modification was called on, whether the tag uses implicit mode
2899    /// and the ascii code for the canonical base.
2900    /// If there are multiple modifications with the same code this will return the data
2901    /// for the first mod.  See https://github.com/samtools/htslib/issues/1635
2902    pub fn query_type(&self, code: i32) -> Result<BaseModificationMetadata> {
2903        unsafe {
2904            let mut strand: i32 = 0;
2905            let mut implicit: i32 = 0;
2906            // This may be i8 or u8 in hts_sys.
2907            let mut canonical: c_char = 0;
2908
2909            let ret = hts_sys::bam_mods_query_type(
2910                self.state,
2911                code,
2912                &mut strand,
2913                &mut implicit,
2914                &mut canonical,
2915            );
2916            if ret == -1 {
2917                Err(Error::BamBaseModificationTypeNotFound)
2918            } else {
2919                Ok(BaseModificationMetadata {
2920                    strand,
2921                    implicit,
2922                    canonical: canonical
2923                        .try_into()
2924                        .map_err(|_| Error::BamBaseModificationIterationFailed)?,
2925                })
2926            }
2927        }
2928    }
2929}
2930
2931impl Drop for BaseModificationState<'_> {
2932    fn drop<'a>(&mut self) {
2933        unsafe {
2934            hts_sys::hts_base_mod_state_free(self.state);
2935        }
2936    }
2937}
2938
2939/// Iterator over the base modifications that returns
2940/// a vector for all of the mods at each position
2941pub struct BaseModificationsPositionIter<'a> {
2942    mod_state: BaseModificationState<'a>,
2943}
2944
2945impl BaseModificationsPositionIter<'_> {
2946    fn new(r: &Record) -> Result<BaseModificationsPositionIter<'_>> {
2947        let state = BaseModificationState::new(r)?;
2948        Ok(BaseModificationsPositionIter { mod_state: state })
2949    }
2950
2951    pub fn recorded<'a>(&self) -> &'a [i32] {
2952        self.mod_state.recorded()
2953    }
2954
2955    pub fn query_type(&self, code: i32) -> Result<BaseModificationMetadata> {
2956        self.mod_state.query_type(code)
2957    }
2958}
2959
2960impl Iterator for BaseModificationsPositionIter<'_> {
2961    type Item = Result<(i32, Vec<hts_sys::hts_base_mod>)>;
2962
2963    fn next(&mut self) -> Option<Self::Item> {
2964        let ret = self.mod_state.buffer_next_mods();
2965
2966        // Three possible things happened in buffer_next_mods:
2967        // 1. the htslib API call was successful but there are no more mods
2968        // 2. ths htslib API call was successful and we read some mods
2969        // 3. the htslib API call failed, we propogate the error wrapped in an option
2970        match ret {
2971            Ok(num_mods) => {
2972                if num_mods == 0 {
2973                    None
2974                } else {
2975                    let data = (self.mod_state.buffer_pos, self.mod_state.buffer.clone());
2976                    Some(Ok(data))
2977                }
2978            }
2979            Err(e) => Some(Err(e)),
2980        }
2981    }
2982}
2983
2984/// Iterator over the base modifications that returns
2985/// the next modification found, one by one
2986pub struct BaseModificationsIter<'a> {
2987    mod_state: BaseModificationState<'a>,
2988    buffer_idx: usize,
2989}
2990
2991impl BaseModificationsIter<'_> {
2992    fn new(r: &Record) -> Result<BaseModificationsIter<'_>> {
2993        let state = BaseModificationState::new(r)?;
2994        Ok(BaseModificationsIter {
2995            mod_state: state,
2996            buffer_idx: 0,
2997        })
2998    }
2999
3000    pub fn recorded<'a>(&self) -> &'a [i32] {
3001        self.mod_state.recorded()
3002    }
3003
3004    pub fn query_type(&self, code: i32) -> Result<BaseModificationMetadata> {
3005        self.mod_state.query_type(code)
3006    }
3007}
3008
3009impl Iterator for BaseModificationsIter<'_> {
3010    type Item = Result<(i32, hts_sys::hts_base_mod)>;
3011
3012    fn next(&mut self) -> Option<Self::Item> {
3013        if self.buffer_idx == self.mod_state.buffer.len() {
3014            // need to use the internal state to read the next
3015            // set of modifications into the buffer
3016            let ret = self.mod_state.buffer_next_mods();
3017
3018            match ret {
3019                Ok(num_mods) => {
3020                    if num_mods == 0 {
3021                        // done iterating
3022                        return None;
3023                    } else {
3024                        // we read some mods, reset the position in the buffer then fall through
3025                        self.buffer_idx = 0;
3026                    }
3027                }
3028                Err(e) => return Some(Err(e)),
3029            }
3030        }
3031
3032        // if we got here when there are mods buffered that we haven't emitted yet
3033        assert!(self.buffer_idx < self.mod_state.buffer.len());
3034        let data = (
3035            self.mod_state.buffer_pos,
3036            self.mod_state.buffer[self.buffer_idx],
3037        );
3038        self.buffer_idx += 1;
3039        Some(Ok(data))
3040    }
3041}
3042
3043#[cfg(test)]
3044mod tests {
3045    use super::*;
3046
3047    #[test]
3048    fn test_cigar_string() {
3049        let cigar = CigarString(vec![Cigar::Match(100), Cigar::SoftClip(10)]);
3050
3051        assert_eq!(cigar[0], Cigar::Match(100));
3052        assert_eq!(format!("{}", cigar), "100M10S");
3053        for op in &cigar {
3054            println!("{}", op);
3055        }
3056    }
3057
3058    #[test]
3059    fn test_cigar_string_view_pos() {
3060        let cigar = CigarString(vec![Cigar::Match(100), Cigar::SoftClip(10)]).into_view(5);
3061        assert_eq!(cigar.pos(), 5);
3062    }
3063
3064    #[test]
3065    fn test_cigar_string_leading_softclips() {
3066        let cigar = CigarString(vec![Cigar::SoftClip(10), Cigar::Match(100)]).into_view(0);
3067        assert_eq!(cigar.leading_softclips(), 10);
3068        let cigar2 = CigarString(vec![
3069            Cigar::HardClip(5),
3070            Cigar::SoftClip(10),
3071            Cigar::Match(100),
3072        ])
3073        .into_view(0);
3074        assert_eq!(cigar2.leading_softclips(), 10);
3075    }
3076
3077    #[test]
3078    fn test_cigar_string_trailing_softclips() {
3079        let cigar = CigarString(vec![Cigar::Match(100), Cigar::SoftClip(10)]).into_view(0);
3080        assert_eq!(cigar.trailing_softclips(), 10);
3081        let cigar2 = CigarString(vec![
3082            Cigar::Match(100),
3083            Cigar::SoftClip(10),
3084            Cigar::HardClip(5),
3085        ])
3086        .into_view(0);
3087        assert_eq!(cigar2.trailing_softclips(), 10);
3088    }
3089
3090    #[test]
3091    fn test_cigar_read_pos() {
3092        let vpos = 5; // variant position
3093
3094        // Ignore leading HardClip
3095        // ref:       00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
3096        // var:                       V
3097        // c01: 7H                 M  M
3098        // qpos:                  00 01
3099        let c01 = CigarString(vec![Cigar::HardClip(7), Cigar::Match(2)]).into_view(4);
3100        assert_eq!(c01.read_pos(vpos, false, false).unwrap(), Some(1));
3101
3102        // Skip leading SoftClip or use as pre-POS matches
3103        // ref:       00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
3104        // var:                       V
3105        // c02: 5H2S         M  M  M  M  M  M
3106        // qpos:  00        02 03 04 05 06 07
3107        // c02: 5H     S  S  M  M  M  M  M  M
3108        // qpos:      00 01 02 03 04 05 06 07
3109        let c02 = CigarString(vec![Cigar::SoftClip(2), Cigar::Match(6)]).into_view(2);
3110        assert_eq!(c02.read_pos(vpos, false, false).unwrap(), Some(5));
3111        assert_eq!(c02.read_pos(vpos, true, false).unwrap(), Some(5));
3112
3113        // Skip leading SoftClip returning None for unmatched reference positiong or use as
3114        // pre-POS matches
3115        // ref:       00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
3116        // var:                       V
3117        // c03:  3S                      M  M
3118        // qpos: 00                     03 04
3119        // c03:                 S  S  S  M  M
3120        // qpos:               00 01 02 03 04
3121        let c03 = CigarString(vec![Cigar::SoftClip(3), Cigar::Match(6)]).into_view(6);
3122        assert_eq!(c03.read_pos(vpos, false, false).unwrap(), None);
3123        assert_eq!(c03.read_pos(vpos, true, false).unwrap(), Some(2));
3124
3125        // Skip leading Insertion before variant position
3126        // ref:       00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
3127        // var:                       V
3128        // c04:  3I                X  X  X
3129        // qpos: 00               03 04 05
3130        let c04 = CigarString(vec![Cigar::Ins(3), Cigar::Diff(3)]).into_view(4);
3131        assert_eq!(c04.read_pos(vpos, true, false).unwrap(), Some(4));
3132
3133        // Matches and deletion before variant position
3134        // ref:       00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
3135        // var:                       V
3136        // c05:        =  =  D  D  X  =  =
3137        // qpos:      00 01       02 03 04 05
3138        let c05 = CigarString(vec![
3139            Cigar::Equal(2),
3140            Cigar::Del(2),
3141            Cigar::Diff(1),
3142            Cigar::Equal(2),
3143        ])
3144        .into_view(0);
3145        assert_eq!(c05.read_pos(vpos, true, false).unwrap(), Some(3));
3146
3147        // single nucleotide Deletion covering variant position
3148        // ref:       00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
3149        // var:                       V
3150        // c06:                 =  =  D  X  X
3151        // qpos:               00 01    02 03
3152        let c06 = CigarString(vec![Cigar::Equal(2), Cigar::Del(1), Cigar::Diff(2)]).into_view(3);
3153        assert_eq!(c06.read_pos(vpos, false, true).unwrap(), Some(2));
3154        assert_eq!(c06.read_pos(vpos, false, false).unwrap(), None);
3155
3156        // three nucleotide Deletion covering variant position
3157        // ref:       00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
3158        // var:                       V
3159        // c07:              =  =  D  D  D  M  M
3160        // qpos:            00 01          02 03
3161        let c07 = CigarString(vec![Cigar::Equal(2), Cigar::Del(3), Cigar::Match(2)]).into_view(2);
3162        assert_eq!(c07.read_pos(vpos, false, true).unwrap(), Some(2));
3163        assert_eq!(c07.read_pos(vpos, false, false).unwrap(), None);
3164
3165        // three nucleotide RefSkip covering variant position
3166        // ref:       00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
3167        // var:                       V
3168        // c08:              =  X  N  N  N  M  M
3169        // qpos:            00 01          02 03
3170        let c08 = CigarString(vec![
3171            Cigar::Equal(1),
3172            Cigar::Diff(1),
3173            Cigar::RefSkip(3),
3174            Cigar::Match(2),
3175        ])
3176        .into_view(2);
3177        assert_eq!(c08.read_pos(vpos, false, true).unwrap(), None);
3178        assert_eq!(c08.read_pos(vpos, false, false).unwrap(), None);
3179
3180        // internal hard clip before variant pos
3181        // ref:       00 01 02 03    04 05 06 07 08 09 10 11 12 13 14 15
3182        // var:                          V
3183        // c09: 3H           =  = 3H  =  =
3184        // qpos:            00 01    02 03
3185        let c09 = CigarString(vec![
3186            Cigar::HardClip(3),
3187            Cigar::Equal(2),
3188            Cigar::HardClip(3),
3189            Cigar::Equal(2),
3190        ])
3191        .into_view(2);
3192        assert_eq!(c09.read_pos(vpos, false, true).is_err(), true);
3193
3194        // Deletion right before variant position
3195        // ref:       00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
3196        // var:                       V
3197        // c10:           M  M  D  D  M  M
3198        // qpos:         00 01       02 03
3199        let c10 = CigarString(vec![Cigar::Match(2), Cigar::Del(2), Cigar::Match(2)]).into_view(1);
3200        assert_eq!(c10.read_pos(vpos, false, false).unwrap(), Some(2));
3201
3202        // Insertion right before variant position
3203        // ref:       00 01 02 03 04    05 06 07 08 09 10 11 12 13 14 15
3204        // var:                          V
3205        // c11:                 M  M 3I  M
3206        // qpos:               00 01 02 05 06
3207        let c11 = CigarString(vec![Cigar::Match(2), Cigar::Ins(3), Cigar::Match(2)]).into_view(3);
3208        assert_eq!(c11.read_pos(vpos, false, false).unwrap(), Some(5));
3209
3210        // Insertion right after variant position
3211        // ref:       00 01 02 03 04 05    06 07 08 09 10 11 12 13 14 15
3212        // var:                       V
3213        // c12:                 M  M  M 2I  =
3214        // qpos:               00 01 02 03 05
3215        let c12 = CigarString(vec![Cigar::Match(3), Cigar::Ins(2), Cigar::Equal(1)]).into_view(3);
3216        assert_eq!(c12.read_pos(vpos, false, false).unwrap(), Some(2));
3217
3218        // Deletion right after variant position
3219        // ref:       00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
3220        // var:                       V
3221        // c13:                 M  M  M  D  =
3222        // qpos:               00 01 02    03
3223        let c13 = CigarString(vec![Cigar::Match(3), Cigar::Del(1), Cigar::Equal(1)]).into_view(3);
3224        assert_eq!(c13.read_pos(vpos, false, false).unwrap(), Some(2));
3225
3226        // A messy and complicated example, including a Pad operation
3227        let vpos2 = 15;
3228        // ref:       00    01 02    03 04 05    06 07 08 09 10 11 12 13 14 15
3229        // var:                                                           V
3230        // c14: 5H3S   = 2P  M  X 3I  M  M  D 2I  =  =  N  N  N  M  M  M  =  =  5S2H
3231        // qpos:  00  03    04 05 06 09 10    11 13 14          15 16 17 18 19
3232        let c14 = CigarString(vec![
3233            Cigar::HardClip(5),
3234            Cigar::SoftClip(3),
3235            Cigar::Equal(1),
3236            Cigar::Pad(2),
3237            Cigar::Match(1),
3238            Cigar::Diff(1),
3239            Cigar::Ins(3),
3240            Cigar::Match(2),
3241            Cigar::Del(1),
3242            Cigar::Ins(2),
3243            Cigar::Equal(2),
3244            Cigar::RefSkip(3),
3245            Cigar::Match(3),
3246            Cigar::Equal(2),
3247            Cigar::SoftClip(5),
3248            Cigar::HardClip(2),
3249        ])
3250        .into_view(0);
3251        assert_eq!(c14.read_pos(vpos2, false, false).unwrap(), Some(19));
3252
3253        // HardClip after Pad
3254        // ref:       00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
3255        // var:                       V
3256        // c15: 5P1H            =  =  =
3257        // qpos:               00 01 02
3258        let c15 =
3259            CigarString(vec![Cigar::Pad(5), Cigar::HardClip(1), Cigar::Equal(3)]).into_view(3);
3260        assert_eq!(c15.read_pos(vpos, false, false).is_err(), true);
3261
3262        // only HardClip and Pad operations
3263        // c16: 7H5P2H
3264        let c16 =
3265            CigarString(vec![Cigar::HardClip(7), Cigar::Pad(5), Cigar::HardClip(2)]).into_view(3);
3266        assert_eq!(c16.read_pos(vpos, false, false).unwrap(), None);
3267    }
3268
3269    #[test]
3270    fn test_clone() {
3271        let mut rec = Record::new();
3272        rec.set_pos(300);
3273        rec.set_qname(b"read1");
3274        let clone = rec.clone();
3275        assert_eq!(rec, clone);
3276    }
3277
3278    #[test]
3279    fn test_flags() {
3280        let mut rec = Record::new();
3281
3282        rec.set_paired();
3283        assert_eq!(rec.is_paired(), true);
3284
3285        rec.set_supplementary();
3286        assert_eq!(rec.is_supplementary(), true);
3287        assert_eq!(rec.is_supplementary(), true);
3288
3289        rec.unset_paired();
3290        assert_eq!(rec.is_paired(), false);
3291        assert_eq!(rec.is_supplementary(), true);
3292
3293        rec.unset_supplementary();
3294        assert_eq!(rec.is_paired(), false);
3295        assert_eq!(rec.is_supplementary(), false);
3296    }
3297
3298    #[test]
3299    fn test_cigar_parse() {
3300        let cigar = "1S20M1D2I3X1=2H";
3301        let parsed = CigarString::try_from(cigar).unwrap();
3302        assert_eq!(parsed.to_string(), cigar);
3303    }
3304}
3305
3306#[cfg(test)]
3307mod alignment_cigar_tests {
3308    use super::*;
3309    use crate::bam::{Read, Reader};
3310    use bio_types::alignment::AlignmentOperation::{Del, Ins, Match, Subst, Xclip, Yclip};
3311    use bio_types::alignment::{Alignment, AlignmentMode};
3312
3313    #[test]
3314    fn test_cigar() {
3315        let alignment = Alignment {
3316            score: 5,
3317            xstart: 3,
3318            ystart: 0,
3319            xend: 9,
3320            yend: 10,
3321            ylen: 10,
3322            xlen: 10,
3323            operations: vec![Match, Match, Match, Subst, Ins, Ins, Del, Del],
3324            mode: AlignmentMode::Semiglobal,
3325        };
3326        assert_eq!(alignment.cigar(false), "3S3=1X2I2D1S");
3327        assert_eq!(
3328            CigarString::from_alignment(&alignment, false).0,
3329            vec![
3330                Cigar::SoftClip(3),
3331                Cigar::Equal(3),
3332                Cigar::Diff(1),
3333                Cigar::Ins(2),
3334                Cigar::Del(2),
3335                Cigar::SoftClip(1),
3336            ]
3337        );
3338
3339        let alignment = Alignment {
3340            score: 5,
3341            xstart: 0,
3342            ystart: 5,
3343            xend: 4,
3344            yend: 10,
3345            ylen: 10,
3346            xlen: 5,
3347            operations: vec![Yclip(5), Match, Subst, Subst, Ins, Del, Del, Xclip(1)],
3348            mode: AlignmentMode::Custom,
3349        };
3350        assert_eq!(alignment.cigar(false), "1=2X1I2D1S");
3351        assert_eq!(alignment.cigar(true), "1=2X1I2D1H");
3352        assert_eq!(
3353            CigarString::from_alignment(&alignment, false).0,
3354            vec![
3355                Cigar::Equal(1),
3356                Cigar::Diff(2),
3357                Cigar::Ins(1),
3358                Cigar::Del(2),
3359                Cigar::SoftClip(1),
3360            ]
3361        );
3362        assert_eq!(
3363            CigarString::from_alignment(&alignment, true).0,
3364            vec![
3365                Cigar::Equal(1),
3366                Cigar::Diff(2),
3367                Cigar::Ins(1),
3368                Cigar::Del(2),
3369                Cigar::HardClip(1),
3370            ]
3371        );
3372
3373        let alignment = Alignment {
3374            score: 5,
3375            xstart: 0,
3376            ystart: 5,
3377            xend: 3,
3378            yend: 8,
3379            ylen: 10,
3380            xlen: 3,
3381            operations: vec![Yclip(5), Subst, Match, Subst, Yclip(2)],
3382            mode: AlignmentMode::Custom,
3383        };
3384        assert_eq!(alignment.cigar(false), "1X1=1X");
3385        assert_eq!(
3386            CigarString::from_alignment(&alignment, false).0,
3387            vec![Cigar::Diff(1), Cigar::Equal(1), Cigar::Diff(1)]
3388        );
3389
3390        let alignment = Alignment {
3391            score: 5,
3392            xstart: 0,
3393            ystart: 5,
3394            xend: 3,
3395            yend: 8,
3396            ylen: 10,
3397            xlen: 3,
3398            operations: vec![Subst, Match, Subst],
3399            mode: AlignmentMode::Semiglobal,
3400        };
3401        assert_eq!(alignment.cigar(false), "1X1=1X");
3402        assert_eq!(
3403            CigarString::from_alignment(&alignment, false).0,
3404            vec![Cigar::Diff(1), Cigar::Equal(1), Cigar::Diff(1)]
3405        );
3406    }
3407
3408    #[test]
3409    fn test_read_orientation_f1r2() {
3410        let mut bam = Reader::from_path("test/test_paired.sam").unwrap();
3411
3412        for res in bam.records() {
3413            let record = res.unwrap();
3414            assert_eq!(
3415                record.read_pair_orientation(),
3416                SequenceReadPairOrientation::F1R2
3417            );
3418        }
3419    }
3420
3421    #[test]
3422    fn test_read_orientation_f2r1() {
3423        let mut bam = Reader::from_path("test/test_nonstandard_orientation.sam").unwrap();
3424
3425        for res in bam.records() {
3426            let record = res.unwrap();
3427            assert_eq!(
3428                record.read_pair_orientation(),
3429                SequenceReadPairOrientation::F2R1
3430            );
3431        }
3432    }
3433
3434    #[test]
3435    fn test_read_orientation_supplementary() {
3436        let mut bam = Reader::from_path("test/test_orientation_supplementary.sam").unwrap();
3437
3438        for res in bam.records() {
3439            let record = res.unwrap();
3440            assert_eq!(
3441                record.read_pair_orientation(),
3442                SequenceReadPairOrientation::F2R1
3443            );
3444        }
3445    }
3446
3447    #[test]
3448    pub fn test_cigar_parsing_non_ascii_error() {
3449        let cigar_str = "43ጷ";
3450        let expected_error = Err(Error::BamParseCigar {
3451                msg: "CIGAR string contained non-ASCII characters, which are not valid. Valid are [0-9MIDNSHP=X].".to_owned(),
3452            });
3453
3454        let result = CigarString::try_from(cigar_str);
3455        assert_eq!(expected_error, result);
3456    }
3457
3458    #[test]
3459    pub fn test_cigar_parsing() {
3460        // parsing test cases
3461        let cigar_strs = [
3462            "1H10M4D100I300N1102=10P25X11S", // test every cigar opt
3463            "100M",                          // test a single op
3464            "",                              // test empty input
3465            "1H1=1H",                        // test simple hardclip
3466            "1S1=1S",                        // test simple softclip
3467            "11H11S11=11S11H",               // test complex softclip
3468            "10H",
3469            "10S",
3470        ];
3471        // expected results
3472        let cigars = [
3473            CigarString(vec![
3474                Cigar::HardClip(1),
3475                Cigar::Match(10),
3476                Cigar::Del(4),
3477                Cigar::Ins(100),
3478                Cigar::RefSkip(300),
3479                Cigar::Equal(1102),
3480                Cigar::Pad(10),
3481                Cigar::Diff(25),
3482                Cigar::SoftClip(11),
3483            ]),
3484            CigarString(vec![Cigar::Match(100)]),
3485            CigarString(vec![]),
3486            CigarString(vec![
3487                Cigar::HardClip(1),
3488                Cigar::Equal(1),
3489                Cigar::HardClip(1),
3490            ]),
3491            CigarString(vec![
3492                Cigar::SoftClip(1),
3493                Cigar::Equal(1),
3494                Cigar::SoftClip(1),
3495            ]),
3496            CigarString(vec![
3497                Cigar::HardClip(11),
3498                Cigar::SoftClip(11),
3499                Cigar::Equal(11),
3500                Cigar::SoftClip(11),
3501                Cigar::HardClip(11),
3502            ]),
3503            CigarString(vec![Cigar::HardClip(10)]),
3504            CigarString(vec![Cigar::SoftClip(10)]),
3505        ];
3506        // compare
3507        for (&cigar_str, truth) in cigar_strs.iter().zip(cigars.iter()) {
3508            let cigar_parse = CigarString::try_from(cigar_str)
3509                .unwrap_or_else(|_| panic!("Unable to parse cigar: {}", cigar_str));
3510            assert_eq!(&cigar_parse, truth);
3511        }
3512    }
3513}
3514
3515#[cfg(test)]
3516mod basemod_tests {
3517    use crate::bam::{Read, Reader};
3518
3519    #[test]
3520    pub fn test_count_recorded() {
3521        let mut bam = Reader::from_path("test/base_mods/MM-double.sam").unwrap();
3522
3523        for r in bam.records() {
3524            let record = r.unwrap();
3525            if let Ok(mods) = record.basemods_iter() {
3526                let n = mods.recorded().len();
3527                assert_eq!(n, 3);
3528            };
3529        }
3530    }
3531
3532    #[test]
3533    pub fn test_query_type() {
3534        let mut bam = Reader::from_path("test/base_mods/MM-orient.sam").unwrap();
3535
3536        let mut n_fwd = 0;
3537        let mut n_rev = 0;
3538
3539        for r in bam.records() {
3540            let record = r.unwrap();
3541            if let Ok(mods) = record.basemods_iter() {
3542                for mod_code in mods.recorded() {
3543                    if let Ok(mod_metadata) = mods.query_type(*mod_code) {
3544                        if mod_metadata.strand == 0 {
3545                            n_fwd += 1;
3546                        }
3547                        if mod_metadata.strand == 1 {
3548                            n_rev += 1;
3549                        }
3550                    }
3551                }
3552            };
3553        }
3554        assert_eq!(n_fwd, 2);
3555        assert_eq!(n_rev, 2);
3556    }
3557
3558    #[test]
3559    pub fn test_mod_iter() {
3560        let mut bam = Reader::from_path("test/base_mods/MM-double.sam").unwrap();
3561        let expected_positions = [1, 7, 12, 13, 13, 22, 30, 31];
3562        let mut i = 0;
3563
3564        for r in bam.records() {
3565            let record = r.unwrap();
3566            for res in record.basemods_iter().unwrap().flatten() {
3567                let (position, _m) = res;
3568                assert_eq!(position, expected_positions[i]);
3569                i += 1;
3570            }
3571        }
3572    }
3573
3574    #[test]
3575    pub fn test_position_iter() {
3576        let mut bam = Reader::from_path("test/base_mods/MM-double.sam").unwrap();
3577        let expected_positions = [1, 7, 12, 13, 22, 30, 31];
3578        let expected_counts = [1, 1, 1, 2, 1, 1, 1];
3579        let mut i = 0;
3580
3581        for r in bam.records() {
3582            let record = r.unwrap();
3583            for res in record.basemods_position_iter().unwrap().flatten() {
3584                let (position, elements) = res;
3585                assert_eq!(position, expected_positions[i]);
3586                assert_eq!(elements.len(), expected_counts[i]);
3587                i += 1;
3588            }
3589        }
3590    }
3591}