1use std::fmt;
4use std::fs::File;
5use std::io::{self, BufReader, Read};
6use std::path::Path;
7
8use crate::borrowed::SequenceRef;
9use crate::error::{Error, ParseError, Result};
10use crate::format::{Compression, Format};
11use crate::qual::{self, QualityEncoding};
12use crate::record::Sequence;
13
14pub const DEFAULT_BUFFER_SIZE: usize = 128 * 1024;
17
18const MIN_BUFFER_SIZE: usize = 4 * 1024;
20
21pub struct FastxReader<R: Read> {
61 inner: R,
62 buf: Vec<u8>,
63 pos: usize,
65 end: usize,
67 eof: bool,
68 format: Option<Format>,
69 line: u64,
70 quality_encoding: QualityEncoding,
71 max_line_length: Option<usize>,
72 max_record_length: Option<usize>,
73 spare_description: String,
76 joined_seq: Vec<u8>,
80 joined_quality: Vec<u8>,
82 scan_header: (usize, usize),
85 scan_seq: Vec<(usize, usize)>,
86 scan_quality: Vec<(usize, usize)>,
87 joined: bool,
89}
90
91struct Lines {
96 pos: usize,
97 end: usize,
98 eof: bool,
99 consumed: u64,
102}
103
104impl Lines {
105 fn new(pos: usize, end: usize, eof: bool) -> Lines {
106 Lines {
107 pos,
108 end,
109 eof,
110 consumed: 0,
111 }
112 }
113
114 fn exhausted(&self) -> bool {
117 self.pos == self.end && self.eof
118 }
119
120 fn peek_first_byte(&self, buf: &[u8]) -> Option<u8> {
122 buf.get(self.pos).copied().filter(|_| self.pos < self.end)
123 }
124
125 fn next(&mut self, buf: &[u8]) -> Option<(usize, usize)> {
130 match memchr::memchr(b'\n', &buf[self.pos..self.end]) {
131 Some(offset) => {
132 let newline = self.pos + offset;
133 let start = self.pos;
134 let mut stop = newline;
135 if stop > start && buf[stop - 1] == b'\r' {
136 stop -= 1;
137 }
138 self.pos = newline + 1;
139 self.consumed += 1;
140 Some((start, stop))
141 }
142 None if self.eof && self.pos < self.end => {
145 let start = self.pos;
146 let mut stop = self.end;
147 if stop > start && buf[stop - 1] == b'\r' {
148 stop -= 1;
149 }
150 self.pos = self.end;
151 self.consumed += 1;
152 Some((start, stop))
153 }
154 None => None,
155 }
156 }
157}
158
159fn trim_cr(region: &[u8], start: usize, stop: usize) -> usize {
161 if stop > start && region[stop - 1] == b'\r' {
162 stop - 1
163 } else {
164 stop
165 }
166}
167
168fn join_ranges(ranges: &[(usize, usize)], buf: &[u8], out: &mut Vec<u8>) {
170 out.clear();
171 for &(start, stop) in ranges {
172 out.extend_from_slice(&buf[start..stop]);
173 }
174}
175
176impl<R: Read> FastxReader<R> {
177 pub fn new(inner: R) -> FastxReader<R> {
179 FastxReader::with_capacity(inner, DEFAULT_BUFFER_SIZE)
180 }
181
182 pub fn with_format(inner: R, format: Format) -> FastxReader<R> {
184 let mut reader = FastxReader::new(inner);
185 reader.format = Some(format);
186 reader
187 }
188
189 pub fn with_capacity(inner: R, capacity: usize) -> FastxReader<R> {
191 FastxReader {
192 inner,
193 buf: vec![0; capacity.max(MIN_BUFFER_SIZE)],
194 pos: 0,
195 end: 0,
196 eof: false,
197 format: None,
198 line: 0,
199 quality_encoding: QualityEncoding::Phred33,
200 max_line_length: None,
201 max_record_length: None,
202 spare_description: String::new(),
203 joined_seq: Vec::new(),
204 joined_quality: Vec::new(),
205 scan_header: (0, 0),
206 scan_seq: Vec::new(),
207 scan_quality: Vec::new(),
208 joined: false,
209 }
210 }
211
212 pub fn format(&self) -> Option<Format> {
215 self.format
216 }
217
218 pub fn quality_encoding(&self) -> QualityEncoding {
223 self.quality_encoding
224 }
225
226 pub fn line_number(&self) -> u64 {
228 self.line
229 }
230
231 pub fn into_inner(self) -> R {
233 self.inner
234 }
235
236 pub fn read_into(&mut self, record: &mut Sequence) -> Result<bool> {
241 let format = match self.format {
242 Some(format) => {
243 if !self.skip_blank_lines()? {
244 return Ok(false);
245 }
246 format
247 }
248 None => match self.detect_format()? {
249 Some(format) => {
250 self.format = Some(format);
251 format
252 }
253 None => return Ok(false),
254 },
255 };
256 if let Some(mut previous) = record.description.take() {
259 if previous.capacity() > self.spare_description.capacity() {
260 previous.clear();
261 self.spare_description = previous;
262 }
263 }
264 record.clear();
265 match format {
266 Format::Fasta => self.read_fasta_into(record)?,
267 Format::Fastq => self.read_fastq_into(record)?,
268 }
269 Ok(true)
270 }
271
272 pub fn read_record(&mut self) -> Result<Option<Sequence>> {
274 let mut record = Sequence::default();
275 if self.read_into(&mut record)? {
276 Ok(Some(record))
277 } else {
278 Ok(None)
279 }
280 }
281
282 pub fn records(&mut self) -> Records<'_, R> {
286 Records { reader: self }
287 }
288
289 pub fn for_each_record<F>(&mut self, mut f: F) -> Result<()>
304 where
305 F: FnMut(&Sequence) -> Result<()>,
306 {
307 let mut record = Sequence::default();
308 while self.read_into(&mut record)? {
309 f(&record)?;
310 }
311 Ok(())
312 }
313
314 pub fn read_ref(&mut self) -> Result<Option<SequenceRef<'_>>> {
339 let format = match self.format {
340 Some(format) => {
341 if !self.skip_blank_lines()? {
342 return Ok(None);
343 }
344 format
345 }
346 None => match self.detect_format()? {
347 Some(format) => {
348 self.format = Some(format);
349 format
350 }
351 None => return Ok(None),
352 },
353 };
354 match format {
355 Format::Fasta => self.read_fasta_ref(),
356 Format::Fastq => self.read_fastq_ref(),
357 }
358 .map(Some)
359 }
360
361 pub fn for_each_ref<F>(&mut self, mut f: F) -> Result<()>
379 where
380 F: FnMut(SequenceRef<'_>) -> Result<()>,
381 {
382 while let Some(record) = self.read_ref()? {
383 f(record)?;
384 }
385 Ok(())
386 }
387
388 pub fn count_records(&mut self) -> Result<u64> {
390 let mut n = 0;
391 let mut record = Sequence::default();
392 while self.read_into(&mut record)? {
393 n += 1;
394 }
395 Ok(n)
396 }
397
398 fn read_fasta_into(&mut self, record: &mut Sequence) -> Result<()> {
401 let (start, end) = match self.read_line()? {
402 Some(range) => range,
403 None => {
404 return Err(Error::parse(
405 self.line,
406 ParseError::UnexpectedEof {
407 expected: "a FASTA header",
408 },
409 ))
410 }
411 };
412 if self.buf[start] != b'>' && self.buf[start] != b';' {
413 return Err(Error::parse(
414 self.line,
415 ParseError::ExpectedHeader {
416 found: self.buf[start],
417 },
418 ));
419 }
420 record.set_header_reusing(&self.buf[start + 1..end], &mut self.spare_description);
421 if record.id.is_empty() {
422 return Err(Error::parse(self.line, ParseError::EmptyId));
423 }
424 loop {
425 match self.peek_byte()? {
426 None | Some(b'>') => break,
427 _ => {
428 let (start, end) = self.read_line()?.expect("peeked byte is available");
429 record.seq.extend_from_slice(&self.buf[start..end]);
430 self.check_record_limit(record.seq.len(), "sequence")?;
431 }
432 }
433 }
434 Ok(())
435 }
436
437 fn read_fastq_into(&mut self, record: &mut Sequence) -> Result<()> {
438 let (start, end) = match self.read_line()? {
439 Some(range) => range,
440 None => {
441 return Err(Error::parse(
442 self.line,
443 ParseError::UnexpectedEof {
444 expected: "a FASTQ header",
445 },
446 ))
447 }
448 };
449 if self.buf[start] != b'@' {
450 return Err(Error::parse(
451 self.line,
452 ParseError::ExpectedHeader {
453 found: self.buf[start],
454 },
455 ));
456 }
457 record.set_header_reusing(&self.buf[start + 1..end], &mut self.spare_description);
458 if record.id.is_empty() {
459 return Err(Error::parse(self.line, ParseError::EmptyId));
460 }
461
462 loop {
465 match self.peek_byte()? {
466 None => {
467 return Err(Error::parse(
468 self.line,
469 ParseError::UnexpectedEof {
470 expected: "a FASTQ '+' separator",
471 },
472 ))
473 }
474 Some(b'+') => {
475 self.read_line()?;
476 break;
477 }
478 _ => {
479 let (start, end) = self.read_line()?.expect("peeked byte is available");
480 record.seq.extend_from_slice(&self.buf[start..end]);
481 self.check_record_limit(record.seq.len(), "sequence")?;
482 }
483 }
484 }
485
486 let quality = record.quality.get_or_insert_with(Vec::new);
489 while quality.len() < record.seq.len() {
490 match self.read_line()? {
491 Some((start, end)) => quality.extend_from_slice(&self.buf[start..end]),
492 None => {
493 return Err(Error::LengthMismatch {
494 id: record.id.clone(),
495 seq: record.seq.len(),
496 quality: quality.len(),
497 })
498 }
499 }
500 }
501 if quality.len() != record.seq.len() {
502 return Err(Error::LengthMismatch {
503 id: record.id.clone(),
504 seq: record.seq.len(),
505 quality: quality.len(),
506 });
507 }
508 if self.quality_encoding != QualityEncoding::Phred33 {
511 let from = self.quality_encoding.offset();
512 for c in quality.iter_mut() {
513 *c = qual::encode(qual::score(*c, from), qual::PHRED33);
514 }
515 }
516 Ok(())
517 }
518
519 fn check_line_limit(&self, length: usize) -> Result<()> {
521 match self.max_line_length {
522 Some(limit) if length > limit => Err(Error::TooLarge {
523 line: self.line + 1,
524 what: "line",
525 limit,
526 }),
527 _ => Ok(()),
528 }
529 }
530
531 fn check_record_limit(&self, length: usize, what: &'static str) -> Result<()> {
533 match self.max_record_length {
534 Some(limit) if length > limit => Err(Error::TooLarge {
535 line: self.line,
536 what,
537 limit,
538 }),
539 _ => Ok(()),
540 }
541 }
542
543 fn read_fastq_ref(&mut self) -> Result<SequenceRef<'_>> {
550 loop {
551 if self.scan_fastq_quick()? || self.scan_fastq()? {
552 break;
553 }
554 if self.eof {
555 return Err(Error::parse(
556 self.line,
557 ParseError::UnexpectedEof {
558 expected: "a complete FASTQ record",
559 },
560 ));
561 }
562 self.check_record_limit(self.end - self.pos, "record")?;
563 self.refill()?;
564 }
565
566 if self.joined {
567 join_ranges(&self.scan_seq, &self.buf, &mut self.joined_seq);
568 join_ranges(&self.scan_quality, &self.buf, &mut self.joined_quality);
569 }
570 let (seq, quality) = if self.joined {
571 (&self.joined_seq[..], &self.joined_quality[..])
572 } else {
573 let seq = self.scan_seq[0];
574 let quality = self.scan_quality[0];
575 (&self.buf[seq.0..seq.1], &self.buf[quality.0..quality.1])
576 };
577 let (id, description) =
578 crate::record::split_header(&self.buf[self.scan_header.0..self.scan_header.1]);
579 if id.is_empty() {
580 return Err(Error::parse(self.line, ParseError::EmptyId));
581 }
582 Ok(SequenceRef::new(id, description, seq, Some(quality)))
583 }
584
585 fn read_fasta_ref(&mut self) -> Result<SequenceRef<'_>> {
587 loop {
588 if self.scan_fasta()? {
589 break;
590 }
591 self.check_record_limit(self.end - self.pos, "record")?;
592 self.refill()?;
593 }
594
595 if self.joined {
596 join_ranges(&self.scan_seq, &self.buf, &mut self.joined_seq);
597 }
598 let seq = if self.joined {
599 &self.joined_seq[..]
600 } else {
601 match self.scan_seq.first() {
602 Some(&(start, stop)) => &self.buf[start..stop],
603 None => &[][..],
604 }
605 };
606 let (id, description) =
607 crate::record::split_header(&self.buf[self.scan_header.0..self.scan_header.1]);
608 if id.is_empty() {
609 return Err(Error::parse(self.line, ParseError::EmptyId));
610 }
611 Ok(SequenceRef::new(id, description, seq, None))
612 }
613
614 fn scan_fastq_quick(&mut self) -> Result<bool> {
625 let region = &self.buf[self.pos..self.end];
626 let mut newlines = memchr::memchr_iter(b'\n', region);
627 let (Some(a), Some(b), Some(c), Some(d)) = (
628 newlines.next(),
629 newlines.next(),
630 newlines.next(),
631 newlines.next(),
632 ) else {
633 return Ok(false);
634 };
635
636 let base = self.pos;
638 let header = (base, trim_cr(region, 0, a) + base);
639 let seq = (base + a + 1, trim_cr(region, a + 1, b) + base);
640 let plus = (base + b + 1, trim_cr(region, b + 1, c) + base);
641 let quality = (base + c + 1, trim_cr(region, c + 1, d) + base);
642
643 if self.buf[header.0] != b'@'
647 || self.buf[plus.0] != b'+'
648 || quality.1 - quality.0 != seq.1 - seq.0
649 {
650 return Ok(false);
651 }
652
653 self.check_line_limit(seq.1 - seq.0)?;
654 self.check_line_limit(quality.1 - quality.0)?;
655 self.check_record_limit(seq.1 - seq.0, "sequence")?;
656
657 self.scan_header = (header.0 + 1, header.1);
658 self.scan_seq.clear();
659 self.scan_seq.push(seq);
660 self.scan_quality.clear();
661 self.scan_quality.push(quality);
662 self.joined = false;
663 self.pos = base + d + 1;
664 self.line += 4;
665 Ok(true)
666 }
667
668 fn scan_fastq(&mut self) -> Result<bool> {
674 let mut lines = Lines::new(self.pos, self.end, self.eof);
675
676 let header = match lines.next(&self.buf) {
677 Some(range) => range,
678 None => return Ok(false),
679 };
680 if self.buf[header.0] != b'@' {
681 return Err(Error::parse(
682 self.line + 1,
683 ParseError::ExpectedHeader {
684 found: self.buf[header.0],
685 },
686 ));
687 }
688
689 self.scan_seq.clear();
691 let mut seq_len = 0;
692 loop {
693 match lines.peek_first_byte(&self.buf) {
694 None => return Ok(false),
695 Some(b'+') => {
696 lines.next(&self.buf);
697 break;
698 }
699 Some(_) => match lines.next(&self.buf) {
700 None => return Ok(false),
701 Some(range) => {
702 seq_len += range.1 - range.0;
703 self.scan_seq.push(range);
704 }
705 },
706 }
707 }
708
709 self.scan_quality.clear();
712 let mut quality_len = 0;
713 while quality_len < seq_len {
714 match lines.next(&self.buf) {
715 None => return Ok(false),
716 Some(range) => {
717 quality_len += range.1 - range.0;
718 self.scan_quality.push(range);
719 }
720 }
721 }
722 if quality_len != seq_len {
723 let (id, _) = crate::record::split_header(&self.buf[header.0 + 1..header.1]);
724 return Err(Error::LengthMismatch {
725 id: String::from_utf8_lossy(id).into_owned(),
726 seq: seq_len,
727 quality: quality_len,
728 });
729 }
730
731 self.finish_scan(header, &lines)
732 }
733
734 fn scan_fasta(&mut self) -> Result<bool> {
736 let mut lines = Lines::new(self.pos, self.end, self.eof);
737
738 let header = match lines.next(&self.buf) {
739 Some(range) => range,
740 None => return Ok(false),
741 };
742 if self.buf[header.0] != b'>' && self.buf[header.0] != b';' {
743 return Err(Error::parse(
744 self.line + 1,
745 ParseError::ExpectedHeader {
746 found: self.buf[header.0],
747 },
748 ));
749 }
750
751 self.scan_seq.clear();
752 self.scan_quality.clear();
753 loop {
754 match lines.peek_first_byte(&self.buf) {
755 Some(b'>') => break,
758 None if lines.exhausted() => break,
759 None => return Ok(false),
760 Some(_) => match lines.next(&self.buf) {
761 None => return Ok(false),
762 Some(range) => self.scan_seq.push(range),
763 },
764 }
765 }
766
767 self.finish_scan(header, &lines)
768 }
769
770 fn finish_scan(&mut self, header: (usize, usize), lines: &Lines) -> Result<bool> {
773 for &(start, stop) in self.scan_seq.iter().chain(self.scan_quality.iter()) {
774 self.check_line_limit(stop - start)?;
775 }
776 let length: usize = self.scan_seq.iter().map(|&(a, b)| b - a).sum();
777 self.check_record_limit(length, "sequence")?;
778
779 self.scan_header = (header.0 + 1, header.1);
780 self.joined = self.scan_seq.len() > 1 || self.scan_quality.len() > 1;
783 self.pos = lines.pos;
784 self.line += lines.consumed;
785 Ok(true)
786 }
787
788 fn skip_blank_lines(&mut self) -> Result<bool> {
790 loop {
791 match self.peek_byte()? {
792 None => return Ok(false),
793 Some(b'\n') => {
794 self.pos += 1;
795 self.line += 1;
796 }
797 Some(b'\r') => self.pos += 1,
798 Some(_) => return Ok(true),
799 }
800 }
801 }
802
803 fn detect_format(&mut self) -> Result<Option<Format>> {
805 if !self.skip_blank_lines()? {
806 return Ok(None);
807 }
808 let byte = self.buf[self.pos];
809 match Format::from_first_byte(byte) {
810 Some(format) => Ok(Some(format)),
811 None => Err(Error::parse(
812 self.line + 1,
813 ParseError::ExpectedHeader { found: byte },
814 )),
815 }
816 }
817
818 fn read_line(&mut self) -> Result<Option<(usize, usize)>> {
823 let mut search_from = self.pos;
824 loop {
825 if let Some(offset) = memchr::memchr(b'\n', &self.buf[search_from..self.end]) {
826 let newline = search_from + offset;
827 let start = self.pos;
828 let mut stop = newline;
829 if stop > start && self.buf[stop - 1] == b'\r' {
830 stop -= 1;
831 }
832 self.check_line_limit(stop - start)?;
833 self.pos = newline + 1;
834 self.line += 1;
835 return Ok(Some((start, stop)));
836 }
837 if self.eof {
838 if self.pos == self.end {
839 return Ok(None);
840 }
841 let start = self.pos;
843 let mut stop = self.end;
844 if stop > start && self.buf[stop - 1] == b'\r' {
845 stop -= 1;
846 }
847 self.check_line_limit(stop - start)?;
848 self.pos = self.end;
849 self.line += 1;
850 return Ok(Some((start, stop)));
851 }
852 self.check_line_limit(self.end - self.pos)?;
856 let previous_end = self.end;
857 let shift = self.refill()?;
858 search_from = previous_end - shift;
859 }
860 }
861
862 fn peek_byte(&mut self) -> Result<Option<u8>> {
864 while self.pos == self.end && !self.eof {
865 self.refill()?;
866 }
867 if self.pos == self.end {
868 Ok(None)
869 } else {
870 Ok(Some(self.buf[self.pos]))
871 }
872 }
873
874 fn refill(&mut self) -> Result<usize> {
877 let mut shift = 0;
878 if self.pos > 0 {
879 self.buf.copy_within(self.pos..self.end, 0);
880 shift = self.pos;
881 self.end -= self.pos;
882 self.pos = 0;
883 }
884 if self.end == self.buf.len() {
885 let grown = self.buf.len().saturating_mul(2).max(MIN_BUFFER_SIZE);
887 self.buf.resize(grown, 0);
888 }
889 loop {
890 match self.inner.read(&mut self.buf[self.end..]) {
891 Ok(0) => {
892 self.eof = true;
893 break;
894 }
895 Ok(n) => {
896 self.end += n;
897 break;
898 }
899 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
900 Err(e) => return Err(Error::Io(e)),
901 }
902 }
903 Ok(shift)
904 }
905}
906
907impl<R: Read> fmt::Debug for FastxReader<R> {
908 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
909 f.debug_struct("FastxReader")
910 .field("format", &self.format)
911 .field("buffer_size", &self.buf.len())
912 .field("buffered", &(self.end - self.pos))
913 .field("line", &self.line)
914 .field("eof", &self.eof)
915 .finish_non_exhaustive()
916 }
917}
918
919impl<R: Read> Iterator for FastxReader<R> {
920 type Item = Result<Sequence>;
921
922 fn next(&mut self) -> Option<Self::Item> {
923 match self.read_record() {
924 Ok(Some(record)) => Some(Ok(record)),
925 Ok(None) => None,
926 Err(e) => Some(Err(e)),
927 }
928 }
929}
930
931pub struct Records<'a, R: Read> {
933 reader: &'a mut FastxReader<R>,
934}
935
936impl<R: Read> Iterator for Records<'_, R> {
937 type Item = Result<Sequence>;
938
939 fn next(&mut self) -> Option<Self::Item> {
940 match self.reader.read_record() {
941 Ok(Some(record)) => Some(Ok(record)),
942 Ok(None) => None,
943 Err(e) => Some(Err(e)),
944 }
945 }
946}
947
948#[derive(Debug, Clone)]
962pub struct ReaderBuilder {
963 format: Option<Format>,
964 buffer_size: usize,
965 quality_encoding: QualityEncoding,
966 max_line_length: Option<usize>,
967 max_record_length: Option<usize>,
968}
969
970impl Default for ReaderBuilder {
971 fn default() -> Self {
972 ReaderBuilder {
973 format: None,
974 buffer_size: DEFAULT_BUFFER_SIZE,
975 quality_encoding: QualityEncoding::Phred33,
976 max_line_length: None,
977 max_record_length: None,
978 }
979 }
980}
981
982impl ReaderBuilder {
983 pub fn new() -> ReaderBuilder {
985 ReaderBuilder::default()
986 }
987
988 pub fn format(mut self, format: Format) -> Self {
990 self.format = Some(format);
991 self
992 }
993
994 pub fn buffer_size(mut self, bytes: usize) -> Self {
996 self.buffer_size = bytes;
997 self
998 }
999
1000 pub fn quality_encoding(mut self, encoding: QualityEncoding) -> Self {
1022 self.quality_encoding = encoding;
1023 self
1024 }
1025
1026 pub fn max_line_length(mut self, bytes: usize) -> Self {
1033 self.max_line_length = Some(bytes);
1034 self
1035 }
1036
1037 pub fn max_record_length(mut self, bytes: usize) -> Self {
1042 self.max_record_length = Some(bytes);
1043 self
1044 }
1045
1046 pub fn build<R: Read>(&self, inner: R) -> FastxReader<R> {
1048 let mut reader = FastxReader::with_capacity(inner, self.buffer_size);
1049 reader.format = self.format;
1050 reader.quality_encoding = self.quality_encoding;
1051 reader.max_line_length = self.max_line_length;
1052 reader.max_record_length = self.max_record_length;
1053 reader
1054 }
1055
1056 pub fn open<P: AsRef<Path>>(&self, path: P) -> Result<FastxReader<Box<dyn Read + Send>>> {
1058 let path = path.as_ref();
1059 let mut builder = self.clone();
1060 if builder.format.is_none() {
1061 builder.format = Format::from_path(path);
1062 }
1063 Ok(builder.build(open_reader(path)?))
1064 }
1065}
1066
1067pub type BoxedReader = FastxReader<Box<dyn Read + Send>>;
1069
1070pub fn open<P: AsRef<Path>>(path: P) -> Result<BoxedReader> {
1078 ReaderBuilder::default().open(path)
1079}
1080
1081pub fn from_stdin() -> Result<BoxedReader> {
1083 let stream = decompress(Box::new(io::stdin()))?;
1084 Ok(FastxReader::new(stream))
1085}
1086
1087fn open_reader(path: &Path) -> Result<Box<dyn Read + Send>> {
1089 let file = File::open(path)
1090 .map_err(|e| Error::Io(io::Error::new(e.kind(), format!("{}: {e}", path.display()))))?;
1091 decompress(Box::new(BufReader::with_capacity(64 * 1024, file)))
1092}
1093
1094fn decompress(mut stream: Box<dyn Read + Send>) -> Result<Box<dyn Read + Send>> {
1099 let mut magic = [0u8; HEADER_LEN_PROBE];
1103 let mut filled = 0;
1104 while filled < magic.len() {
1105 match stream.read(&mut magic[filled..]) {
1106 Ok(0) => break,
1107 Ok(n) => filled += n,
1108 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
1109 Err(e) => return Err(Error::Io(e)),
1110 }
1111 }
1112 let head = io::Cursor::new(magic[..filled].to_vec());
1113 let rejoined = head.chain(stream);
1114 match Compression::from_magic(&magic[..filled]) {
1115 Compression::None => Ok(Box::new(rejoined)),
1116 Compression::Gzip | Compression::Bgzf => gunzip(rejoined, &magic[..filled]),
1120 Compression::Zstd => unzstd(rejoined),
1121 }
1122}
1123
1124const HEADER_LEN_PROBE: usize = 18;
1127
1128#[cfg(feature = "gzip")]
1134fn gunzip<R: Read + Send + 'static>(stream: R, head: &[u8]) -> Result<Box<dyn Read + Send>> {
1135 #[cfg(feature = "parallel")]
1136 if crate::bgzf::is_bgzf(head) {
1137 return Ok(Box::new(crate::bgzf::ParallelBgzfReader::new(stream)));
1138 }
1139 let _ = head;
1140 Ok(Box::new(flate2::read::MultiGzDecoder::new(stream)))
1141}
1142
1143#[cfg(not(feature = "gzip"))]
1144fn gunzip<R: Read + Send + 'static>(_stream: R, _head: &[u8]) -> Result<Box<dyn Read + Send>> {
1145 Err(Error::FeatureDisabled("gzip"))
1146}
1147
1148#[cfg(feature = "zstd")]
1150fn unzstd<R: Read + Send + 'static>(stream: R) -> Result<Box<dyn Read + Send>> {
1151 Ok(Box::new(zstd::stream::read::Decoder::new(stream)?))
1152}
1153
1154#[cfg(not(feature = "zstd"))]
1155fn unzstd<R: Read + Send + 'static>(_stream: R) -> Result<Box<dyn Read + Send>> {
1156 Err(Error::FeatureDisabled("zstd"))
1157}
1158
1159#[cfg(test)]
1160mod tests {
1161 use super::*;
1162
1163 fn ids(data: &[u8]) -> Vec<String> {
1164 FastxReader::new(data).map(|r| r.unwrap().id).collect()
1165 }
1166
1167 #[test]
1168 fn reads_simple_fasta() {
1169 let data = b">a desc here\nACGT\n>b\nTTTT\nGGGG\n";
1170 let records: Vec<_> = FastxReader::new(&data[..])
1171 .collect::<Result<Vec<_>>>()
1172 .unwrap();
1173 assert_eq!(records.len(), 2);
1174 assert_eq!(records[0].id, "a");
1175 assert_eq!(records[0].description.as_deref(), Some("desc here"));
1176 assert_eq!(records[0].seq, b"ACGT");
1177 assert_eq!(records[1].seq, b"TTTTGGGG");
1178 assert!(records[1].quality.is_none());
1179 }
1180
1181 #[test]
1182 fn reads_simple_fastq() {
1183 let data = b"@a\nACGT\n+\nIIII\n@b desc\nTT\n+b desc\n!!\n";
1184 let records: Vec<_> = FastxReader::new(&data[..])
1185 .collect::<Result<Vec<_>>>()
1186 .unwrap();
1187 assert_eq!(records.len(), 2);
1188 assert_eq!(records[0].quality.as_deref(), Some(&b"IIII"[..]));
1189 assert_eq!(records[1].id, "b");
1190 assert_eq!(records[1].description.as_deref(), Some("desc"));
1191 assert_eq!(records[1].quality.as_deref(), Some(&b"!!"[..]));
1192 }
1193
1194 #[test]
1195 fn detects_format() {
1196 let mut reader = FastxReader::new(&b">a\nAC\n"[..]);
1197 assert_eq!(reader.format(), None);
1198 reader.next().unwrap().unwrap();
1199 assert_eq!(reader.format(), Some(Format::Fasta));
1200
1201 let mut reader = FastxReader::new(&b"@a\nAC\n+\nII\n"[..]);
1202 reader.next().unwrap().unwrap();
1203 assert_eq!(reader.format(), Some(Format::Fastq));
1204 }
1205
1206 #[test]
1207 fn handles_crlf_and_missing_final_newline() {
1208 let data = b">a\r\nACGT\r\nAC\r\n>b\r\nTT";
1209 let records: Vec<_> = FastxReader::new(&data[..])
1210 .collect::<Result<Vec<_>>>()
1211 .unwrap();
1212 assert_eq!(records[0].seq, b"ACGTAC");
1213 assert_eq!(records[1].seq, b"TT");
1214 }
1215
1216 #[test]
1217 fn handles_blank_lines_between_records() {
1218 let data = b"\n\n>a\nACGT\n\n\n>b\nTT\n\n";
1219 assert_eq!(ids(&data[..]), ["a", "b"]);
1220 let records: Vec<_> = FastxReader::new(&data[..])
1222 .collect::<Result<Vec<_>>>()
1223 .unwrap();
1224 assert_eq!(records[0].seq, b"ACGT");
1225 }
1226
1227 #[test]
1228 fn handles_empty_input() {
1229 assert_eq!(FastxReader::new(&b""[..]).count(), 0);
1230 assert_eq!(FastxReader::new(&b"\n\n\n"[..]).count(), 0);
1231 }
1232
1233 #[test]
1234 fn multi_line_fastq() {
1235 let data = b"@a\nACGT\nACGT\n+\nIIII\nJJJJ\n@b\nTT\n+\n!!\n";
1236 let records: Vec<_> = FastxReader::new(&data[..])
1237 .collect::<Result<Vec<_>>>()
1238 .unwrap();
1239 assert_eq!(records[0].seq, b"ACGTACGT");
1240 assert_eq!(records[0].quality.as_deref(), Some(&b"IIIIJJJJ"[..]));
1241 assert_eq!(records[1].id, "b");
1242 }
1243
1244 #[test]
1245 fn quality_starting_with_at_sign() {
1246 let data = b"@a\nACGT\n+\n@@@@\n@b\nTTTT\n+\nIIII\n";
1248 let records: Vec<_> = FastxReader::new(&data[..])
1249 .collect::<Result<Vec<_>>>()
1250 .unwrap();
1251 assert_eq!(records.len(), 2);
1252 assert_eq!(records[0].quality.as_deref(), Some(&b"@@@@"[..]));
1253 assert_eq!(records[1].id, "b");
1254 }
1255
1256 #[test]
1257 fn tiny_buffer_still_parses() {
1258 let long = "A".repeat(50_000);
1260 let data = format!(">a\n{long}\n>b\nACGT\n");
1261 let mut reader = FastxReader::with_capacity(data.as_bytes(), 1);
1262 let records: Vec<_> = reader.records().collect::<Result<Vec<_>>>().unwrap();
1263 assert_eq!(records.len(), 2);
1264 assert_eq!(records[0].seq.len(), 50_000);
1265 assert_eq!(records[1].seq, b"ACGT");
1266 }
1267
1268 #[test]
1269 fn description_buffer_is_reused_without_leaking_between_records() {
1270 let data = b"@a first one\nAC\n+\nII\n\
1275 @b\nGT\n+\nII\n\
1276 @c third\nTT\n+\nII\n\
1277 @d\nCC\n+\nII\n\
1278 @e a much longer description than any before it\nGG\n+\nII\n";
1279 let mut reader = FastxReader::new(&data[..]);
1280 let mut record = Sequence::default();
1281 let mut seen = Vec::new();
1282 while reader.read_into(&mut record).unwrap() {
1283 seen.push((record.id.clone(), record.description.clone()));
1284 }
1285 assert_eq!(
1286 seen,
1287 vec![
1288 ("a".to_string(), Some("first one".to_string())),
1289 ("b".to_string(), None),
1290 ("c".to_string(), Some("third".to_string())),
1291 ("d".to_string(), None),
1292 (
1293 "e".to_string(),
1294 Some("a much longer description than any before it".to_string())
1295 ),
1296 ]
1297 );
1298
1299 let mut record = Sequence::fasta("old", b"AAAA").with_description("stale description");
1301 let data = b"@new\nAC\n+\nII\n";
1302 assert!(FastxReader::new(&data[..]).read_into(&mut record).unwrap());
1303 assert_eq!(record.id, "new");
1304 assert_eq!(record.description, None);
1305 assert_eq!(record.seq, b"AC");
1306 }
1307
1308 #[test]
1309 fn read_into_reuses_allocations() {
1310 let data = b">a\nACGT\n>b\nTT\n";
1311 let mut reader = FastxReader::new(&data[..]);
1312 let mut record = Sequence::default();
1313 assert!(reader.read_into(&mut record).unwrap());
1314 assert_eq!(record.id, "a");
1315 assert!(reader.read_into(&mut record).unwrap());
1316 assert_eq!(record.id, "b");
1317 assert_eq!(record.seq, b"TT");
1318 assert!(!reader.read_into(&mut record).unwrap());
1319 }
1320
1321 #[test]
1322 fn empty_fasta_record_is_allowed() {
1323 let data = b">a\n>b\nACGT\n";
1324 let records: Vec<_> = FastxReader::new(&data[..])
1325 .collect::<Result<Vec<_>>>()
1326 .unwrap();
1327 assert_eq!(records[0].seq, b"");
1328 assert_eq!(records[1].seq, b"ACGT");
1329 }
1330
1331 #[test]
1332 fn rejects_garbage() {
1333 let err = FastxReader::new(&b"not a sequence file\n"[..])
1334 .next()
1335 .unwrap()
1336 .unwrap_err();
1337 assert!(matches!(
1338 err,
1339 Error::Parse {
1340 kind: ParseError::ExpectedHeader { found: b'n' },
1341 ..
1342 }
1343 ));
1344 }
1345
1346 #[test]
1347 fn rejects_truncated_fastq() {
1348 let err = FastxReader::new(&b"@a\nACGT\n"[..])
1349 .next()
1350 .unwrap()
1351 .unwrap_err();
1352 assert!(matches!(
1353 err,
1354 Error::Parse {
1355 kind: ParseError::UnexpectedEof { .. },
1356 ..
1357 }
1358 ));
1359
1360 let err = FastxReader::new(&b"@a\nACGT\n+\nII\n"[..])
1361 .next()
1362 .unwrap()
1363 .unwrap_err();
1364 assert!(matches!(
1365 err,
1366 Error::LengthMismatch {
1367 seq: 4,
1368 quality: 2,
1369 ..
1370 }
1371 ));
1372 }
1373
1374 #[test]
1375 fn rejects_empty_id() {
1376 let err = FastxReader::new(&b">\nACGT\n"[..])
1377 .next()
1378 .unwrap()
1379 .unwrap_err();
1380 assert!(matches!(
1381 err,
1382 Error::Parse {
1383 kind: ParseError::EmptyId,
1384 ..
1385 }
1386 ));
1387 }
1388
1389 #[test]
1390 fn reports_line_numbers() {
1391 let data = b">a\nACGT\n>b\nACGT\nnope";
1392 let mut reader = FastxReader::with_format(&data[..], Format::Fasta);
1393 reader.next().unwrap().unwrap();
1394 assert_eq!(reader.line_number(), 2);
1395 }
1396
1397 #[test]
1398 fn phred64_input_is_normalised_to_phred33() {
1399 let data = b"@old\nACGT\n+\nhhhB\n";
1401
1402 let record = ReaderBuilder::new()
1403 .quality_encoding(QualityEncoding::Phred64)
1404 .build(&data[..])
1405 .read_record()
1406 .unwrap()
1407 .unwrap();
1408 assert_eq!(record.quality.as_deref(), Some(&b"III#"[..]));
1409 assert_eq!(record.quality_scores().unwrap(), vec![40, 40, 40, 2]);
1410
1411 let record = FastxReader::new(&data[..]).read_record().unwrap().unwrap();
1413 assert_eq!(record.quality.as_deref(), Some(&b"hhhB"[..]));
1414 }
1415
1416 #[test]
1417 fn line_length_limit_is_enforced() {
1418 let long = format!(">a\n{}\n", "A".repeat(10_000));
1419 let err = ReaderBuilder::new()
1420 .max_line_length(1_000)
1421 .build(long.as_bytes())
1422 .read_record()
1423 .unwrap_err();
1424 assert!(
1425 matches!(
1426 err,
1427 Error::TooLarge {
1428 what: "line",
1429 limit: 1_000,
1430 ..
1431 }
1432 ),
1433 "{err}"
1434 );
1435
1436 let record = ReaderBuilder::new()
1438 .max_line_length(1_000_000)
1439 .build(long.as_bytes())
1440 .read_record()
1441 .unwrap()
1442 .unwrap();
1443 assert_eq!(record.seq.len(), 10_000);
1444 }
1445
1446 #[test]
1447 fn record_length_limit_catches_many_short_lines() {
1448 let mut data = String::from(">a\n");
1450 for _ in 0..200 {
1451 data.push_str(&"A".repeat(50));
1452 data.push('\n');
1453 }
1454 let err = ReaderBuilder::new()
1455 .max_line_length(1_000)
1456 .max_record_length(5_000)
1457 .build(data.as_bytes())
1458 .read_record()
1459 .unwrap_err();
1460 assert!(
1461 matches!(
1462 err,
1463 Error::TooLarge {
1464 what: "sequence",
1465 limit: 5_000,
1466 ..
1467 }
1468 ),
1469 "{err}"
1470 );
1471 }
1472
1473 #[test]
1474 fn limits_are_unlimited_by_default() {
1475 let long = format!(">chrom\n{}\n", "ACGT".repeat(50_000));
1477 let record = FastxReader::with_capacity(long.as_bytes(), 4096)
1478 .read_record()
1479 .unwrap()
1480 .unwrap();
1481 assert_eq!(record.seq.len(), 200_000);
1482 }
1483
1484 #[test]
1485 fn forced_format_reads_fasta_as_written() {
1486 let data = b">a\nACGT\n";
1487 let mut reader = FastxReader::with_format(&data[..], Format::Fasta);
1488 assert_eq!(reader.format(), Some(Format::Fasta));
1489 assert_eq!(reader.next().unwrap().unwrap().seq, b"ACGT");
1490 }
1491}