1use std::borrow::Cow;
29
30use crate::error::Result;
31use crate::format::Format;
32use crate::qual::{self, PHRED33};
33use crate::record::Sequence;
34use crate::seq::{self, BaseCounts};
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
46pub struct SequenceRef<'a> {
47 id: &'a [u8],
48 description: Option<&'a [u8]>,
49 seq: &'a [u8],
50 quality: Option<&'a [u8]>,
51}
52
53impl<'a> SequenceRef<'a> {
54 pub(crate) fn new(
56 id: &'a [u8],
57 description: Option<&'a [u8]>,
58 seq: &'a [u8],
59 quality: Option<&'a [u8]>,
60 ) -> SequenceRef<'a> {
61 SequenceRef {
62 id,
63 description,
64 seq,
65 quality,
66 }
67 }
68
69 pub fn id(&self) -> &'a [u8] {
71 self.id
72 }
73
74 pub fn id_str(&self) -> Cow<'a, str> {
78 String::from_utf8_lossy(self.id)
79 }
80
81 pub fn description(&self) -> Option<&'a [u8]> {
83 self.description
84 }
85
86 pub fn description_str(&self) -> Option<Cow<'a, str>> {
88 self.description.map(String::from_utf8_lossy)
89 }
90
91 pub fn seq(&self) -> &'a [u8] {
93 self.seq
94 }
95
96 pub fn quality(&self) -> Option<&'a [u8]> {
98 self.quality
99 }
100
101 pub fn len(&self) -> usize {
103 self.seq.len()
104 }
105
106 pub fn is_empty(&self) -> bool {
108 self.seq.is_empty()
109 }
110
111 pub fn has_quality(&self) -> bool {
113 self.quality.is_some()
114 }
115
116 pub fn format(&self) -> Format {
118 if self.has_quality() {
119 Format::Fastq
120 } else {
121 Format::Fasta
122 }
123 }
124
125 pub fn base_counts(&self) -> BaseCounts {
127 BaseCounts::of(self.seq)
128 }
129
130 pub fn gc_content(&self) -> Option<f64> {
132 seq::gc_content(self.seq)
133 }
134
135 pub fn mean_quality(&self) -> Option<f64> {
137 qual::mean_quality(self.quality?, PHRED33)
138 }
139
140 pub fn expected_errors(&self) -> Option<f64> {
142 Some(qual::expected_errors(self.quality?, PHRED33))
143 }
144
145 pub fn kmers(&self, k: usize) -> impl Iterator<Item = &'a [u8]> {
147 seq::kmers(self.seq, k)
148 }
149
150 pub fn to_owned(&self) -> Sequence {
152 Sequence {
153 id: self.id_str().into_owned(),
154 description: self.description_str().map(Cow::into_owned),
155 seq: self.seq.to_vec(),
156 quality: self.quality.map(<[u8]>::to_vec),
157 }
158 }
159
160 pub fn write<W: std::io::Write>(
164 &self,
165 out: &mut W,
166 format: Format,
167 line_width: Option<usize>,
168 ) -> Result<()> {
169 match format {
170 Format::Fasta => {
171 out.write_all(b">")?;
172 self.write_header(out)?;
173 match line_width.filter(|w| *w > 0) {
174 None => {
175 out.write_all(self.seq)?;
176 out.write_all(b"\n")?;
177 }
178 Some(width) => {
179 if self.seq.is_empty() {
180 out.write_all(b"\n")?;
181 }
182 for chunk in self.seq.chunks(width) {
183 out.write_all(chunk)?;
184 out.write_all(b"\n")?;
185 }
186 }
187 }
188 }
189 Format::Fastq => {
190 let quality = self.quality.ok_or_else(|| crate::Error::MissingQuality {
191 id: self.id_str().into_owned(),
192 })?;
193 out.write_all(b"@")?;
194 self.write_header(out)?;
195 out.write_all(self.seq)?;
196 out.write_all(b"\n+\n")?;
197 out.write_all(quality)?;
198 out.write_all(b"\n")?;
199 }
200 }
201 Ok(())
202 }
203
204 fn write_header<W: std::io::Write>(&self, out: &mut W) -> Result<()> {
205 out.write_all(self.id)?;
206 if let Some(description) = self.description {
207 out.write_all(b" ")?;
208 out.write_all(description)?;
209 }
210 out.write_all(b"\n")?;
211 Ok(())
212 }
213}
214
215impl PartialEq<Sequence> for SequenceRef<'_> {
216 fn eq(&self, other: &Sequence) -> bool {
219 self.id == other.id.as_bytes()
220 && self.description == other.description.as_deref().map(str::as_bytes)
221 && self.seq == other.seq.as_slice()
222 && self.quality == other.quality.as_deref()
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 #[test]
231 fn accessors_and_conversion() {
232 let record = SequenceRef::new(b"r1", Some(b"a sample"), b"ACGTN", Some(b"IIII!"));
233 assert_eq!(record.id(), b"r1");
234 assert_eq!(record.id_str(), "r1");
235 assert_eq!(record.description_str().unwrap(), "a sample");
236 assert_eq!(record.len(), 5);
237 assert!(record.has_quality());
238 assert_eq!(record.format(), Format::Fastq);
239 assert_eq!(record.gc_content(), Some(0.5));
240 assert_eq!(record.kmers(4).count(), 2);
241
242 let owned = record.to_owned();
243 assert_eq!(owned.id, "r1");
244 assert_eq!(owned.description.as_deref(), Some("a sample"));
245 assert_eq!(owned.seq, b"ACGTN");
246 assert_eq!(owned.quality.as_deref(), Some(&b"IIII!"[..]));
247 assert!(record == owned);
248 }
249
250 #[test]
251 fn non_utf8_ids_survive_as_bytes() {
252 let record = SequenceRef::new(&[b'i', 0xff], None, b"AC", None);
255 assert_eq!(record.id(), &[b'i', 0xff]);
256 assert_eq!(record.id_str(), "i\u{fffd}");
257 }
258
259 #[test]
260 fn writes_both_formats() {
261 let record = SequenceRef::new(b"r", Some(b"d"), b"ACGTAC", Some(b"IIIIII"));
262 let mut out = Vec::new();
263 record.write(&mut out, Format::Fastq, None).unwrap();
264 assert_eq!(out, b"@r d\nACGTAC\n+\nIIIIII\n");
265
266 let mut out = Vec::new();
267 record.write(&mut out, Format::Fasta, Some(4)).unwrap();
268 assert_eq!(out, b">r d\nACGT\nAC\n");
269
270 let fasta = SequenceRef::new(b"r", None, b"AC", None);
272 let mut out = Vec::new();
273 assert!(fasta.write(&mut out, Format::Fastq, None).is_err());
274 }
275}