Skip to main content

dino_seq/fastq/
record.rs

1use std::ops::Range;
2
3use crate::fastq_frame;
4
5/// Byte ranges for a four-line FASTQ record within a batch slab.
6///
7/// Ranges are local to [`FastqBatch::bytes`](super::FastqBatch::bytes). They
8/// are exposed for callers that want to build their own zero-copy views without
9/// using [`FastqRecord`].
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct RecordRef {
12    /// Header line, including the leading `@` and excluding the newline.
13    pub name: Range<u32>,
14    /// Sequence line, excluding the newline.
15    pub seq: Range<u32>,
16    /// Plus line, excluding the newline.
17    pub plus: Range<u32>,
18    /// Quality line, excluding the newline.
19    pub qual: Range<u32>,
20}
21
22/// Borrowed view of one FASTQ record inside a [`FastqBatch`](super::FastqBatch).
23///
24/// The record borrows from the batch's slab buffer and cannot outlive the
25/// batch. Accessors return byte slices rather than UTF-8 strings because FASTQ
26/// names and qualities are byte-oriented.
27#[derive(Debug, Clone, Copy)]
28pub struct FastqRecord<'a> {
29    pub(crate) bytes: &'a [u8],
30    pub(crate) record: &'a RecordRef,
31}
32
33impl<'a> FastqRecord<'a> {
34    /// Return the header line including the leading `@`.
35    #[inline]
36    pub fn name(self) -> &'a [u8] {
37        &self.bytes[to_usize(self.record.name.clone())]
38    }
39
40    /// Return the header line without a leading `@`.
41    #[inline]
42    pub fn name_without_at(self) -> &'a [u8] {
43        let name = self.name();
44        name.strip_prefix(b"@").unwrap_or(name)
45    }
46
47    /// Return the first whitespace-delimited identifier token.
48    #[inline]
49    pub fn id_token(self) -> &'a [u8] {
50        let name = self.name_without_at();
51        let end = name
52            .iter()
53            .position(u8::is_ascii_whitespace)
54            .unwrap_or(name.len());
55        &name[..end]
56    }
57
58    /// Return the identifier token with a trailing `/1` or `/2` removed.
59    #[inline]
60    pub fn pair_normalized_id(self) -> &'a [u8] {
61        strip_pair_suffix(self.id_token())
62    }
63
64    /// Return the sequence line.
65    #[inline]
66    pub fn seq(self) -> &'a [u8] {
67        &self.bytes[to_usize(self.record.seq.clone())]
68    }
69
70    /// Return the plus line.
71    #[inline]
72    pub fn plus(self) -> &'a [u8] {
73        &self.bytes[to_usize(self.record.plus.clone())]
74    }
75
76    /// Return the quality line.
77    #[inline]
78    pub fn qual(self) -> &'a [u8] {
79        &self.bytes[to_usize(self.record.qual.clone())]
80    }
81}
82
83/// Borrowed FASTQ record passed to [`FastqReader::visit_records`](super::FastqReader::visit_records).
84///
85/// This view is optimized for single-pass consumers that do not need the batch
86/// side table. The slices point directly into the reader slab and are valid
87/// only for the duration of the visitor callback.
88#[derive(Debug, Clone, Copy)]
89pub struct FastqVisitRecord<'a> {
90    pub(crate) name: &'a [u8],
91    pub(crate) seq: &'a [u8],
92    pub(crate) plus: &'a [u8],
93    pub(crate) qual: &'a [u8],
94}
95
96impl<'a> FastqVisitRecord<'a> {
97    /// Return the header line including the leading `@`.
98    #[inline]
99    pub fn name(self) -> &'a [u8] {
100        self.name
101    }
102
103    /// Return the sequence line.
104    #[inline]
105    pub fn seq(self) -> &'a [u8] {
106        self.seq
107    }
108
109    /// Return the plus line.
110    #[inline]
111    pub fn plus(self) -> &'a [u8] {
112        self.plus
113    }
114
115    /// Return the quality line.
116    #[inline]
117    pub fn qual(self) -> &'a [u8] {
118        self.qual
119    }
120}
121
122/// Strip a terminal `/1` or `/2` pair suffix from an identifier token.
123#[inline]
124pub fn strip_pair_suffix(id: &[u8]) -> &[u8] {
125    fastq_frame::strip_pair_suffix(id)
126}
127
128#[inline]
129pub(crate) fn to_u32_range(range: Range<usize>) -> Range<u32> {
130    debug_assert!(u32::try_from(range.end).is_ok());
131    range.start as u32..range.end as u32
132}
133
134#[inline]
135fn to_usize(range: Range<u32>) -> Range<usize> {
136    range.start as usize..range.end as usize
137}