1use std::io::{Read, Write};
25use std::path::Path;
26
27use crate::error::{Error, Result};
28use crate::format::Format;
29use crate::reader::{self, FastxReader};
30use crate::record::Sequence;
31use crate::writer::FastxWriter;
32
33#[derive(Debug, Clone, Default, PartialEq, Eq)]
35pub struct Pair {
36 pub first: Sequence,
38 pub second: Sequence,
40}
41
42impl Pair {
43 pub fn new(first: Sequence, second: Sequence) -> Pair {
45 Pair { first, second }
46 }
47
48 pub fn len(&self) -> usize {
50 self.first.len() + self.second.len()
51 }
52
53 pub fn is_empty(&self) -> bool {
55 self.first.is_empty() && self.second.is_empty()
56 }
57
58 pub fn clear(&mut self) {
60 self.first.clear();
61 self.second.clear();
62 }
63
64 pub fn names_match(&self) -> bool {
66 mate_stem(&self.first.id) == mate_stem(&self.second.id)
67 }
68}
69
70pub fn mate_stem(id: &str) -> &str {
87 let bytes = id.as_bytes();
88 if bytes.len() >= 2 {
89 let last = bytes[bytes.len() - 1];
90 let separator = bytes[bytes.len() - 2];
91 if (last == b'1' || last == b'2') && matches!(separator, b'/' | b'.' | b'_') {
92 return &id[..id.len() - 2];
93 }
94 }
95 id
96}
97
98#[allow(clippy::large_enum_variant)]
104enum Source<R: Read> {
105 Split {
107 first: FastxReader<R>,
108 second: FastxReader<R>,
109 },
110 Interleaved(FastxReader<R>),
112}
113
114pub struct PairedReader<R: Read> {
120 source: Source<R>,
121 check_names: bool,
122 pairs_read: u64,
123}
124
125pub type BoxedPairedReader = PairedReader<Box<dyn Read + Send>>;
127
128impl PairedReader<Box<dyn Read + Send>> {
129 pub fn open<P: AsRef<Path>, Q: AsRef<Path>>(first: P, second: Q) -> Result<BoxedPairedReader> {
131 Ok(PairedReader {
132 source: Source::Split {
133 first: reader::open(first)?,
134 second: reader::open(second)?,
135 },
136 check_names: true,
137 pairs_read: 0,
138 })
139 }
140
141 pub fn open_interleaved<P: AsRef<Path>>(path: P) -> Result<BoxedPairedReader> {
143 Ok(PairedReader {
144 source: Source::Interleaved(reader::open(path)?),
145 check_names: true,
146 pairs_read: 0,
147 })
148 }
149}
150
151impl<R: Read> PairedReader<R> {
152 pub fn from_readers(first: R, second: R) -> PairedReader<R> {
154 PairedReader::from_split(FastxReader::new(first), FastxReader::new(second))
155 }
156
157 pub fn interleaved(inner: R) -> PairedReader<R> {
159 PairedReader::from_interleaved(FastxReader::new(inner))
160 }
161
162 pub fn from_interleaved(reader: FastxReader<R>) -> PairedReader<R> {
165 PairedReader {
166 source: Source::Interleaved(reader),
167 check_names: true,
168 pairs_read: 0,
169 }
170 }
171
172 pub fn from_split(first: FastxReader<R>, second: FastxReader<R>) -> PairedReader<R> {
174 PairedReader {
175 source: Source::Split { first, second },
176 check_names: true,
177 pairs_read: 0,
178 }
179 }
180
181 pub fn check_names(mut self, check: bool) -> Self {
183 self.check_names = check;
184 self
185 }
186
187 pub fn pairs_read(&self) -> u64 {
189 self.pairs_read
190 }
191
192 pub fn format(&self) -> Option<Format> {
194 match &self.source {
195 Source::Split { first, .. } => first.format(),
196 Source::Interleaved(reader) => reader.format(),
197 }
198 }
199
200 pub fn read_into(&mut self, pair: &mut Pair) -> Result<bool> {
206 let present = match &mut self.source {
207 Source::Split { first, second } => {
208 let got_first = first.read_into(&mut pair.first)?;
209 let got_second = second.read_into(&mut pair.second)?;
210 match (got_first, got_second) {
211 (false, false) => return Ok(false),
212 (true, true) => true,
213 (true, false) => {
214 return Err(Error::PairTruncated {
215 pairs_read: self.pairs_read,
216 missing: "R2",
217 })
218 }
219 (false, true) => {
220 return Err(Error::PairTruncated {
221 pairs_read: self.pairs_read,
222 missing: "R1",
223 })
224 }
225 }
226 }
227 Source::Interleaved(reader) => {
228 if !reader.read_into(&mut pair.first)? {
229 return Ok(false);
230 }
231 if !reader.read_into(&mut pair.second)? {
232 return Err(Error::PairTruncated {
233 pairs_read: self.pairs_read,
234 missing: "the second mate",
235 });
236 }
237 true
238 }
239 };
240
241 if self.check_names && !pair.names_match() {
242 return Err(Error::PairMismatch {
243 pairs_read: self.pairs_read,
244 first: pair.first.id.clone(),
245 second: pair.second.id.clone(),
246 });
247 }
248 self.pairs_read += 1;
249 Ok(present)
250 }
251
252 pub fn read_pair(&mut self) -> Result<Option<Pair>> {
254 let mut pair = Pair::default();
255 if self.read_into(&mut pair)? {
256 Ok(Some(pair))
257 } else {
258 Ok(None)
259 }
260 }
261
262 pub fn for_each_pair<F>(&mut self, mut f: F) -> Result<()>
264 where
265 F: FnMut(&Pair) -> Result<()>,
266 {
267 let mut pair = Pair::default();
268 while self.read_into(&mut pair)? {
269 f(&pair)?;
270 }
271 Ok(())
272 }
273
274 pub fn count_pairs(&mut self) -> Result<u64> {
276 let mut pairs = 0;
277 let mut pair = Pair::default();
278 while self.read_into(&mut pair)? {
279 pairs += 1;
280 }
281 Ok(pairs)
282 }
283}
284
285impl<R: Read> Iterator for PairedReader<R> {
286 type Item = Result<Pair>;
287
288 fn next(&mut self) -> Option<Self::Item> {
289 match self.read_pair() {
290 Ok(Some(pair)) => Some(Ok(pair)),
291 Ok(None) => None,
292 Err(e) => Some(Err(e)),
293 }
294 }
295}
296
297pub enum PairedWriter<W: Write> {
299 Split {
301 first: FastxWriter<W>,
303 second: FastxWriter<W>,
305 },
306 Interleaved(FastxWriter<W>),
308}
309
310impl<W: Write> PairedWriter<W> {
311 pub fn write_pair(&mut self, pair: &Pair) -> Result<()> {
313 match self {
314 PairedWriter::Split { first, second } => {
315 first.write_record(&pair.first)?;
316 second.write_record(&pair.second)?;
317 }
318 PairedWriter::Interleaved(writer) => {
319 writer.write_record(&pair.first)?;
320 writer.write_record(&pair.second)?;
321 }
322 }
323 Ok(())
324 }
325
326 pub fn flush(&mut self) -> Result<()> {
328 match self {
329 PairedWriter::Split { first, second } => {
330 first.flush()?;
331 second.flush()?;
332 }
333 PairedWriter::Interleaved(writer) => writer.flush()?,
334 }
335 Ok(())
336 }
337
338 pub fn finish(self) -> Result<()> {
340 match self {
341 PairedWriter::Split { first, second } => {
342 first.finish()?;
343 second.finish()?;
344 }
345 PairedWriter::Interleaved(writer) => {
346 writer.finish()?;
347 }
348 }
349 Ok(())
350 }
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356
357 const R1: &[u8] = b"@read1/1\nACGT\n+\nIIII\n@read2/1\nTTTT\n+\nJJJJ\n";
358 const R2: &[u8] = b"@read1/2\nCCCC\n+\nIIII\n@read2/2\nGGGG\n+\nJJJJ\n";
359
360 #[test]
361 fn reads_pairs_from_two_streams() {
362 let mut reader = PairedReader::from_readers(R1, R2);
363 let pairs: Vec<Pair> = reader.by_ref().collect::<Result<Vec<_>>>().unwrap();
364 assert_eq!(pairs.len(), 2);
365 assert_eq!(pairs[0].first.seq, b"ACGT");
366 assert_eq!(pairs[0].second.seq, b"CCCC");
367 assert_eq!(pairs[1].first.id, "read2/1");
368 assert_eq!(reader.pairs_read(), 2);
369 }
370
371 #[test]
372 fn reads_interleaved() {
373 let interleaved = b"@r/1\nAC\n+\nII\n@r/2\nGT\n+\nII\n@s/1\nAA\n+\nII\n@s/2\nTT\n+\nII\n";
374 let pairs: Vec<Pair> = PairedReader::interleaved(&interleaved[..])
375 .collect::<Result<Vec<_>>>()
376 .unwrap();
377 assert_eq!(pairs.len(), 2);
378 assert_eq!(pairs[0].second.seq, b"GT");
379 assert_eq!(pairs[1].first.id, "s/1");
380 }
381
382 #[test]
383 fn catches_mispaired_names() {
384 let shuffled = b"@read2/2\nGGGG\n+\nJJJJ\n@read1/2\nCCCC\n+\nIIII\n";
386 let error = PairedReader::from_readers(R1, &shuffled[..])
387 .read_pair()
388 .unwrap_err();
389 match error {
390 Error::PairMismatch { first, second, .. } => {
391 assert_eq!((first.as_str(), second.as_str()), ("read1/1", "read2/2"));
392 }
393 other => panic!("expected PairMismatch, got {other}"),
394 }
395
396 let pairs = PairedReader::from_readers(R1, &shuffled[..])
398 .check_names(false)
399 .count_pairs()
400 .unwrap();
401 assert_eq!(pairs, 2);
402 }
403
404 #[test]
405 fn catches_a_truncated_mate_file() {
406 let short = b"@read1/2\nCCCC\n+\nIIII\n";
407 let error = PairedReader::from_readers(R1, &short[..])
408 .count_pairs()
409 .unwrap_err();
410 assert!(
411 matches!(
412 error,
413 Error::PairTruncated {
414 missing: "R2",
415 pairs_read: 1
416 }
417 ),
418 "{error}"
419 );
420
421 let error = PairedReader::from_readers(&short[..], R2)
422 .count_pairs()
423 .unwrap_err();
424 assert!(
425 matches!(error, Error::PairTruncated { missing: "R1", .. }),
426 "{error}"
427 );
428
429 let odd = b"@r/1\nAC\n+\nII\n@r/2\nGT\n+\nII\n@s/1\nAA\n+\nII\n";
431 let error = PairedReader::interleaved(&odd[..])
432 .count_pairs()
433 .unwrap_err();
434 assert!(
435 matches!(error, Error::PairTruncated { pairs_read: 1, .. }),
436 "{error}"
437 );
438 }
439
440 #[test]
441 fn accepts_illumina_style_names() {
442 let r1 = b"@A00123:1:HXX:1:1101:1000:1000 1:N:0:ATCG\nAC\n+\nII\n";
445 let r2 = b"@A00123:1:HXX:1:1101:1000:1000 2:N:0:ATCG\nGT\n+\nII\n";
446 let pair = PairedReader::from_readers(&r1[..], &r2[..])
447 .read_pair()
448 .unwrap()
449 .unwrap();
450 assert!(pair.names_match());
451 assert_eq!(pair.first.description.as_deref(), Some("1:N:0:ATCG"));
452 }
453
454 #[test]
455 fn mate_stems() {
456 for (id, stem) in [
457 ("read/1", "read"),
458 ("read/2", "read"),
459 ("read.1", "read"),
460 ("read_2", "read"),
461 ("read", "read"),
462 ("read1", "read1"),
463 ("r", "r"),
464 ("", ""),
465 ("/1", ""),
466 ("read/3", "read/3"),
467 ] {
468 assert_eq!(mate_stem(id), stem, "{id}");
469 }
470 }
471
472 #[test]
473 fn empty_input_yields_no_pairs() {
474 assert_eq!(
475 PairedReader::from_readers(&b""[..], &b""[..])
476 .count_pairs()
477 .unwrap(),
478 0
479 );
480 assert_eq!(
481 PairedReader::interleaved(&b""[..]).count_pairs().unwrap(),
482 0
483 );
484 }
485
486 #[test]
487 fn round_trips_through_the_writer() {
488 let mut interleaved = Vec::new();
489 {
490 let mut writer =
491 PairedWriter::Interleaved(FastxWriter::new(&mut interleaved, Format::Fastq));
492 let mut reader = PairedReader::from_readers(R1, R2);
493 reader
494 .for_each_pair(|pair| writer.write_pair(pair))
495 .unwrap();
496 writer.finish().unwrap();
497 }
498
499 let pairs: Vec<Pair> = PairedReader::interleaved(&interleaved[..])
500 .collect::<Result<Vec<_>>>()
501 .unwrap();
502 let original: Vec<Pair> = PairedReader::from_readers(R1, R2)
503 .collect::<Result<Vec<_>>>()
504 .unwrap();
505 assert_eq!(pairs, original);
506 }
507
508 #[test]
509 fn split_writer_separates_the_mates() {
510 let mut first = Vec::new();
511 let mut second = Vec::new();
512 {
513 let mut writer = PairedWriter::Split {
514 first: FastxWriter::new(&mut first, Format::Fastq),
515 second: FastxWriter::new(&mut second, Format::Fastq),
516 };
517 PairedReader::from_readers(R1, R2)
518 .for_each_pair(|pair| writer.write_pair(pair))
519 .unwrap();
520 writer.finish().unwrap();
521 }
522 assert_eq!(first, R1);
523 assert_eq!(second, R2);
524 }
525}