1use std::fmt;
4use std::fs::File;
5use std::io::{self, BufReader, Read};
6use std::path::Path;
7
8use crate::error::{Error, ParseError, Result};
9use crate::format::{Compression, Format};
10use crate::qual::{self, QualityEncoding};
11use crate::record::Sequence;
12
13pub const DEFAULT_BUFFER_SIZE: usize = 128 * 1024;
16
17const MIN_BUFFER_SIZE: usize = 4 * 1024;
19
20pub struct FastxReader<R: Read> {
60 inner: R,
61 buf: Vec<u8>,
62 pos: usize,
64 end: usize,
66 eof: bool,
67 format: Option<Format>,
68 line: u64,
69 quality_encoding: QualityEncoding,
70 max_line_length: Option<usize>,
71 max_record_length: Option<usize>,
72}
73
74impl<R: Read> FastxReader<R> {
75 pub fn new(inner: R) -> FastxReader<R> {
77 FastxReader::with_capacity(inner, DEFAULT_BUFFER_SIZE)
78 }
79
80 pub fn with_format(inner: R, format: Format) -> FastxReader<R> {
82 let mut reader = FastxReader::new(inner);
83 reader.format = Some(format);
84 reader
85 }
86
87 pub fn with_capacity(inner: R, capacity: usize) -> FastxReader<R> {
89 FastxReader {
90 inner,
91 buf: vec![0; capacity.max(MIN_BUFFER_SIZE)],
92 pos: 0,
93 end: 0,
94 eof: false,
95 format: None,
96 line: 0,
97 quality_encoding: QualityEncoding::Phred33,
98 max_line_length: None,
99 max_record_length: None,
100 }
101 }
102
103 pub fn format(&self) -> Option<Format> {
106 self.format
107 }
108
109 pub fn quality_encoding(&self) -> QualityEncoding {
114 self.quality_encoding
115 }
116
117 pub fn line_number(&self) -> u64 {
119 self.line
120 }
121
122 pub fn into_inner(self) -> R {
124 self.inner
125 }
126
127 pub fn read_into(&mut self, record: &mut Sequence) -> Result<bool> {
132 let format = match self.format {
133 Some(format) => {
134 if !self.skip_blank_lines()? {
135 return Ok(false);
136 }
137 format
138 }
139 None => match self.detect_format()? {
140 Some(format) => {
141 self.format = Some(format);
142 format
143 }
144 None => return Ok(false),
145 },
146 };
147 record.clear();
148 match format {
149 Format::Fasta => self.read_fasta_into(record)?,
150 Format::Fastq => self.read_fastq_into(record)?,
151 }
152 Ok(true)
153 }
154
155 pub fn read_record(&mut self) -> Result<Option<Sequence>> {
157 let mut record = Sequence::default();
158 if self.read_into(&mut record)? {
159 Ok(Some(record))
160 } else {
161 Ok(None)
162 }
163 }
164
165 pub fn records(&mut self) -> Records<'_, R> {
169 Records { reader: self }
170 }
171
172 pub fn for_each_record<F>(&mut self, mut f: F) -> Result<()>
187 where
188 F: FnMut(&Sequence) -> Result<()>,
189 {
190 let mut record = Sequence::default();
191 while self.read_into(&mut record)? {
192 f(&record)?;
193 }
194 Ok(())
195 }
196
197 pub fn count_records(&mut self) -> Result<u64> {
199 let mut n = 0;
200 let mut record = Sequence::default();
201 while self.read_into(&mut record)? {
202 n += 1;
203 }
204 Ok(n)
205 }
206
207 fn read_fasta_into(&mut self, record: &mut Sequence) -> Result<()> {
210 let (start, end) = match self.read_line()? {
211 Some(range) => range,
212 None => {
213 return Err(Error::parse(
214 self.line,
215 ParseError::UnexpectedEof {
216 expected: "a FASTA header",
217 },
218 ))
219 }
220 };
221 if self.buf[start] != b'>' && self.buf[start] != b';' {
222 return Err(Error::parse(
223 self.line,
224 ParseError::ExpectedHeader {
225 found: self.buf[start],
226 },
227 ));
228 }
229 record.set_header(&self.buf[start + 1..end]);
230 if record.id.is_empty() {
231 return Err(Error::parse(self.line, ParseError::EmptyId));
232 }
233 loop {
234 match self.peek_byte()? {
235 None | Some(b'>') => break,
236 _ => {
237 let (start, end) = self.read_line()?.expect("peeked byte is available");
238 record.seq.extend_from_slice(&self.buf[start..end]);
239 self.check_record_limit(record.seq.len(), "sequence")?;
240 }
241 }
242 }
243 Ok(())
244 }
245
246 fn read_fastq_into(&mut self, record: &mut Sequence) -> Result<()> {
247 let (start, end) = match self.read_line()? {
248 Some(range) => range,
249 None => {
250 return Err(Error::parse(
251 self.line,
252 ParseError::UnexpectedEof {
253 expected: "a FASTQ header",
254 },
255 ))
256 }
257 };
258 if self.buf[start] != b'@' {
259 return Err(Error::parse(
260 self.line,
261 ParseError::ExpectedHeader {
262 found: self.buf[start],
263 },
264 ));
265 }
266 record.set_header(&self.buf[start + 1..end]);
267 if record.id.is_empty() {
268 return Err(Error::parse(self.line, ParseError::EmptyId));
269 }
270
271 loop {
274 match self.peek_byte()? {
275 None => {
276 return Err(Error::parse(
277 self.line,
278 ParseError::UnexpectedEof {
279 expected: "a FASTQ '+' separator",
280 },
281 ))
282 }
283 Some(b'+') => {
284 self.read_line()?;
285 break;
286 }
287 _ => {
288 let (start, end) = self.read_line()?.expect("peeked byte is available");
289 record.seq.extend_from_slice(&self.buf[start..end]);
290 self.check_record_limit(record.seq.len(), "sequence")?;
291 }
292 }
293 }
294
295 let quality = record.quality.get_or_insert_with(Vec::new);
298 while quality.len() < record.seq.len() {
299 match self.read_line()? {
300 Some((start, end)) => quality.extend_from_slice(&self.buf[start..end]),
301 None => {
302 return Err(Error::LengthMismatch {
303 id: record.id.clone(),
304 seq: record.seq.len(),
305 quality: quality.len(),
306 })
307 }
308 }
309 }
310 if quality.len() != record.seq.len() {
311 return Err(Error::LengthMismatch {
312 id: record.id.clone(),
313 seq: record.seq.len(),
314 quality: quality.len(),
315 });
316 }
317 if self.quality_encoding != QualityEncoding::Phred33 {
320 let from = self.quality_encoding.offset();
321 for c in quality.iter_mut() {
322 *c = qual::encode(qual::score(*c, from), qual::PHRED33);
323 }
324 }
325 Ok(())
326 }
327
328 fn check_line_limit(&self, length: usize) -> Result<()> {
330 match self.max_line_length {
331 Some(limit) if length > limit => Err(Error::TooLarge {
332 line: self.line + 1,
333 what: "line",
334 limit,
335 }),
336 _ => Ok(()),
337 }
338 }
339
340 fn check_record_limit(&self, length: usize, what: &'static str) -> Result<()> {
342 match self.max_record_length {
343 Some(limit) if length > limit => Err(Error::TooLarge {
344 line: self.line,
345 what,
346 limit,
347 }),
348 _ => Ok(()),
349 }
350 }
351
352 fn skip_blank_lines(&mut self) -> Result<bool> {
354 loop {
355 match self.peek_byte()? {
356 None => return Ok(false),
357 Some(b'\n') => {
358 self.pos += 1;
359 self.line += 1;
360 }
361 Some(b'\r') => self.pos += 1,
362 Some(_) => return Ok(true),
363 }
364 }
365 }
366
367 fn detect_format(&mut self) -> Result<Option<Format>> {
369 if !self.skip_blank_lines()? {
370 return Ok(None);
371 }
372 let byte = self.buf[self.pos];
373 match Format::from_first_byte(byte) {
374 Some(format) => Ok(Some(format)),
375 None => Err(Error::parse(
376 self.line + 1,
377 ParseError::ExpectedHeader { found: byte },
378 )),
379 }
380 }
381
382 fn read_line(&mut self) -> Result<Option<(usize, usize)>> {
387 let mut search_from = self.pos;
388 loop {
389 if let Some(offset) = memchr::memchr(b'\n', &self.buf[search_from..self.end]) {
390 let newline = search_from + offset;
391 let start = self.pos;
392 let mut stop = newline;
393 if stop > start && self.buf[stop - 1] == b'\r' {
394 stop -= 1;
395 }
396 self.check_line_limit(stop - start)?;
397 self.pos = newline + 1;
398 self.line += 1;
399 return Ok(Some((start, stop)));
400 }
401 if self.eof {
402 if self.pos == self.end {
403 return Ok(None);
404 }
405 let start = self.pos;
407 let mut stop = self.end;
408 if stop > start && self.buf[stop - 1] == b'\r' {
409 stop -= 1;
410 }
411 self.check_line_limit(stop - start)?;
412 self.pos = self.end;
413 self.line += 1;
414 return Ok(Some((start, stop)));
415 }
416 self.check_line_limit(self.end - self.pos)?;
420 let previous_end = self.end;
421 let shift = self.refill()?;
422 search_from = previous_end - shift;
423 }
424 }
425
426 fn peek_byte(&mut self) -> Result<Option<u8>> {
428 while self.pos == self.end && !self.eof {
429 self.refill()?;
430 }
431 if self.pos == self.end {
432 Ok(None)
433 } else {
434 Ok(Some(self.buf[self.pos]))
435 }
436 }
437
438 fn refill(&mut self) -> Result<usize> {
441 let mut shift = 0;
442 if self.pos > 0 {
443 self.buf.copy_within(self.pos..self.end, 0);
444 shift = self.pos;
445 self.end -= self.pos;
446 self.pos = 0;
447 }
448 if self.end == self.buf.len() {
449 let grown = self.buf.len().saturating_mul(2).max(MIN_BUFFER_SIZE);
451 self.buf.resize(grown, 0);
452 }
453 loop {
454 match self.inner.read(&mut self.buf[self.end..]) {
455 Ok(0) => {
456 self.eof = true;
457 break;
458 }
459 Ok(n) => {
460 self.end += n;
461 break;
462 }
463 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
464 Err(e) => return Err(Error::Io(e)),
465 }
466 }
467 Ok(shift)
468 }
469}
470
471impl<R: Read> fmt::Debug for FastxReader<R> {
472 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
473 f.debug_struct("FastxReader")
474 .field("format", &self.format)
475 .field("buffer_size", &self.buf.len())
476 .field("buffered", &(self.end - self.pos))
477 .field("line", &self.line)
478 .field("eof", &self.eof)
479 .finish_non_exhaustive()
480 }
481}
482
483impl<R: Read> Iterator for FastxReader<R> {
484 type Item = Result<Sequence>;
485
486 fn next(&mut self) -> Option<Self::Item> {
487 match self.read_record() {
488 Ok(Some(record)) => Some(Ok(record)),
489 Ok(None) => None,
490 Err(e) => Some(Err(e)),
491 }
492 }
493}
494
495pub struct Records<'a, R: Read> {
497 reader: &'a mut FastxReader<R>,
498}
499
500impl<R: Read> Iterator for Records<'_, R> {
501 type Item = Result<Sequence>;
502
503 fn next(&mut self) -> Option<Self::Item> {
504 match self.reader.read_record() {
505 Ok(Some(record)) => Some(Ok(record)),
506 Ok(None) => None,
507 Err(e) => Some(Err(e)),
508 }
509 }
510}
511
512#[derive(Debug, Clone)]
526pub struct ReaderBuilder {
527 format: Option<Format>,
528 buffer_size: usize,
529 quality_encoding: QualityEncoding,
530 max_line_length: Option<usize>,
531 max_record_length: Option<usize>,
532}
533
534impl Default for ReaderBuilder {
535 fn default() -> Self {
536 ReaderBuilder {
537 format: None,
538 buffer_size: DEFAULT_BUFFER_SIZE,
539 quality_encoding: QualityEncoding::Phred33,
540 max_line_length: None,
541 max_record_length: None,
542 }
543 }
544}
545
546impl ReaderBuilder {
547 pub fn new() -> ReaderBuilder {
549 ReaderBuilder::default()
550 }
551
552 pub fn format(mut self, format: Format) -> Self {
554 self.format = Some(format);
555 self
556 }
557
558 pub fn buffer_size(mut self, bytes: usize) -> Self {
560 self.buffer_size = bytes;
561 self
562 }
563
564 pub fn quality_encoding(mut self, encoding: QualityEncoding) -> Self {
586 self.quality_encoding = encoding;
587 self
588 }
589
590 pub fn max_line_length(mut self, bytes: usize) -> Self {
597 self.max_line_length = Some(bytes);
598 self
599 }
600
601 pub fn max_record_length(mut self, bytes: usize) -> Self {
606 self.max_record_length = Some(bytes);
607 self
608 }
609
610 pub fn build<R: Read>(&self, inner: R) -> FastxReader<R> {
612 let mut reader = FastxReader::with_capacity(inner, self.buffer_size);
613 reader.format = self.format;
614 reader.quality_encoding = self.quality_encoding;
615 reader.max_line_length = self.max_line_length;
616 reader.max_record_length = self.max_record_length;
617 reader
618 }
619
620 pub fn open<P: AsRef<Path>>(&self, path: P) -> Result<FastxReader<Box<dyn Read + Send>>> {
622 let path = path.as_ref();
623 let mut builder = self.clone();
624 if builder.format.is_none() {
625 builder.format = Format::from_path(path);
626 }
627 Ok(builder.build(open_reader(path)?))
628 }
629}
630
631pub type BoxedReader = FastxReader<Box<dyn Read + Send>>;
633
634pub fn open<P: AsRef<Path>>(path: P) -> Result<BoxedReader> {
642 ReaderBuilder::default().open(path)
643}
644
645pub fn from_stdin() -> Result<BoxedReader> {
647 let stream = decompress(Box::new(io::stdin()))?;
648 Ok(FastxReader::new(stream))
649}
650
651fn open_reader(path: &Path) -> Result<Box<dyn Read + Send>> {
653 let file = File::open(path)
654 .map_err(|e| Error::Io(io::Error::new(e.kind(), format!("{}: {e}", path.display()))))?;
655 decompress(Box::new(BufReader::with_capacity(64 * 1024, file)))
656}
657
658fn decompress(mut stream: Box<dyn Read + Send>) -> Result<Box<dyn Read + Send>> {
660 let mut magic = [0u8; 2];
661 let mut filled = 0;
662 while filled < magic.len() {
663 match stream.read(&mut magic[filled..]) {
664 Ok(0) => break,
665 Ok(n) => filled += n,
666 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
667 Err(e) => return Err(Error::Io(e)),
668 }
669 }
670 let head = io::Cursor::new(magic[..filled].to_vec());
671 let rejoined = head.chain(stream);
672 match Compression::from_magic(&magic[..filled]) {
673 Compression::None => Ok(Box::new(rejoined)),
674 Compression::Gzip | Compression::Bgzf => gunzip(rejoined),
678 }
679}
680
681#[cfg(feature = "gzip")]
683fn gunzip<R: Read + Send + 'static>(stream: R) -> Result<Box<dyn Read + Send>> {
684 Ok(Box::new(flate2::read::MultiGzDecoder::new(stream)))
685}
686
687#[cfg(not(feature = "gzip"))]
688fn gunzip<R: Read + Send + 'static>(_stream: R) -> Result<Box<dyn Read + Send>> {
689 Err(Error::FeatureDisabled("gzip"))
690}
691
692#[cfg(test)]
693mod tests {
694 use super::*;
695
696 fn ids(data: &[u8]) -> Vec<String> {
697 FastxReader::new(data).map(|r| r.unwrap().id).collect()
698 }
699
700 #[test]
701 fn reads_simple_fasta() {
702 let data = b">a desc here\nACGT\n>b\nTTTT\nGGGG\n";
703 let records: Vec<_> = FastxReader::new(&data[..])
704 .collect::<Result<Vec<_>>>()
705 .unwrap();
706 assert_eq!(records.len(), 2);
707 assert_eq!(records[0].id, "a");
708 assert_eq!(records[0].description.as_deref(), Some("desc here"));
709 assert_eq!(records[0].seq, b"ACGT");
710 assert_eq!(records[1].seq, b"TTTTGGGG");
711 assert!(records[1].quality.is_none());
712 }
713
714 #[test]
715 fn reads_simple_fastq() {
716 let data = b"@a\nACGT\n+\nIIII\n@b desc\nTT\n+b desc\n!!\n";
717 let records: Vec<_> = FastxReader::new(&data[..])
718 .collect::<Result<Vec<_>>>()
719 .unwrap();
720 assert_eq!(records.len(), 2);
721 assert_eq!(records[0].quality.as_deref(), Some(&b"IIII"[..]));
722 assert_eq!(records[1].id, "b");
723 assert_eq!(records[1].description.as_deref(), Some("desc"));
724 assert_eq!(records[1].quality.as_deref(), Some(&b"!!"[..]));
725 }
726
727 #[test]
728 fn detects_format() {
729 let mut reader = FastxReader::new(&b">a\nAC\n"[..]);
730 assert_eq!(reader.format(), None);
731 reader.next().unwrap().unwrap();
732 assert_eq!(reader.format(), Some(Format::Fasta));
733
734 let mut reader = FastxReader::new(&b"@a\nAC\n+\nII\n"[..]);
735 reader.next().unwrap().unwrap();
736 assert_eq!(reader.format(), Some(Format::Fastq));
737 }
738
739 #[test]
740 fn handles_crlf_and_missing_final_newline() {
741 let data = b">a\r\nACGT\r\nAC\r\n>b\r\nTT";
742 let records: Vec<_> = FastxReader::new(&data[..])
743 .collect::<Result<Vec<_>>>()
744 .unwrap();
745 assert_eq!(records[0].seq, b"ACGTAC");
746 assert_eq!(records[1].seq, b"TT");
747 }
748
749 #[test]
750 fn handles_blank_lines_between_records() {
751 let data = b"\n\n>a\nACGT\n\n\n>b\nTT\n\n";
752 assert_eq!(ids(&data[..]), ["a", "b"]);
753 let records: Vec<_> = FastxReader::new(&data[..])
755 .collect::<Result<Vec<_>>>()
756 .unwrap();
757 assert_eq!(records[0].seq, b"ACGT");
758 }
759
760 #[test]
761 fn handles_empty_input() {
762 assert_eq!(FastxReader::new(&b""[..]).count(), 0);
763 assert_eq!(FastxReader::new(&b"\n\n\n"[..]).count(), 0);
764 }
765
766 #[test]
767 fn multi_line_fastq() {
768 let data = b"@a\nACGT\nACGT\n+\nIIII\nJJJJ\n@b\nTT\n+\n!!\n";
769 let records: Vec<_> = FastxReader::new(&data[..])
770 .collect::<Result<Vec<_>>>()
771 .unwrap();
772 assert_eq!(records[0].seq, b"ACGTACGT");
773 assert_eq!(records[0].quality.as_deref(), Some(&b"IIIIJJJJ"[..]));
774 assert_eq!(records[1].id, "b");
775 }
776
777 #[test]
778 fn quality_starting_with_at_sign() {
779 let data = b"@a\nACGT\n+\n@@@@\n@b\nTTTT\n+\nIIII\n";
781 let records: Vec<_> = FastxReader::new(&data[..])
782 .collect::<Result<Vec<_>>>()
783 .unwrap();
784 assert_eq!(records.len(), 2);
785 assert_eq!(records[0].quality.as_deref(), Some(&b"@@@@"[..]));
786 assert_eq!(records[1].id, "b");
787 }
788
789 #[test]
790 fn tiny_buffer_still_parses() {
791 let long = "A".repeat(50_000);
793 let data = format!(">a\n{long}\n>b\nACGT\n");
794 let mut reader = FastxReader::with_capacity(data.as_bytes(), 1);
795 let records: Vec<_> = reader.records().collect::<Result<Vec<_>>>().unwrap();
796 assert_eq!(records.len(), 2);
797 assert_eq!(records[0].seq.len(), 50_000);
798 assert_eq!(records[1].seq, b"ACGT");
799 }
800
801 #[test]
802 fn read_into_reuses_allocations() {
803 let data = b">a\nACGT\n>b\nTT\n";
804 let mut reader = FastxReader::new(&data[..]);
805 let mut record = Sequence::default();
806 assert!(reader.read_into(&mut record).unwrap());
807 assert_eq!(record.id, "a");
808 assert!(reader.read_into(&mut record).unwrap());
809 assert_eq!(record.id, "b");
810 assert_eq!(record.seq, b"TT");
811 assert!(!reader.read_into(&mut record).unwrap());
812 }
813
814 #[test]
815 fn empty_fasta_record_is_allowed() {
816 let data = b">a\n>b\nACGT\n";
817 let records: Vec<_> = FastxReader::new(&data[..])
818 .collect::<Result<Vec<_>>>()
819 .unwrap();
820 assert_eq!(records[0].seq, b"");
821 assert_eq!(records[1].seq, b"ACGT");
822 }
823
824 #[test]
825 fn rejects_garbage() {
826 let err = FastxReader::new(&b"not a sequence file\n"[..])
827 .next()
828 .unwrap()
829 .unwrap_err();
830 assert!(matches!(
831 err,
832 Error::Parse {
833 kind: ParseError::ExpectedHeader { found: b'n' },
834 ..
835 }
836 ));
837 }
838
839 #[test]
840 fn rejects_truncated_fastq() {
841 let err = FastxReader::new(&b"@a\nACGT\n"[..])
842 .next()
843 .unwrap()
844 .unwrap_err();
845 assert!(matches!(
846 err,
847 Error::Parse {
848 kind: ParseError::UnexpectedEof { .. },
849 ..
850 }
851 ));
852
853 let err = FastxReader::new(&b"@a\nACGT\n+\nII\n"[..])
854 .next()
855 .unwrap()
856 .unwrap_err();
857 assert!(matches!(
858 err,
859 Error::LengthMismatch {
860 seq: 4,
861 quality: 2,
862 ..
863 }
864 ));
865 }
866
867 #[test]
868 fn rejects_empty_id() {
869 let err = FastxReader::new(&b">\nACGT\n"[..])
870 .next()
871 .unwrap()
872 .unwrap_err();
873 assert!(matches!(
874 err,
875 Error::Parse {
876 kind: ParseError::EmptyId,
877 ..
878 }
879 ));
880 }
881
882 #[test]
883 fn reports_line_numbers() {
884 let data = b">a\nACGT\n>b\nACGT\nnope";
885 let mut reader = FastxReader::with_format(&data[..], Format::Fasta);
886 reader.next().unwrap().unwrap();
887 assert_eq!(reader.line_number(), 2);
888 }
889
890 #[test]
891 fn phred64_input_is_normalised_to_phred33() {
892 let data = b"@old\nACGT\n+\nhhhB\n";
894
895 let record = ReaderBuilder::new()
896 .quality_encoding(QualityEncoding::Phred64)
897 .build(&data[..])
898 .read_record()
899 .unwrap()
900 .unwrap();
901 assert_eq!(record.quality.as_deref(), Some(&b"III#"[..]));
902 assert_eq!(record.quality_scores().unwrap(), vec![40, 40, 40, 2]);
903
904 let record = FastxReader::new(&data[..]).read_record().unwrap().unwrap();
906 assert_eq!(record.quality.as_deref(), Some(&b"hhhB"[..]));
907 }
908
909 #[test]
910 fn line_length_limit_is_enforced() {
911 let long = format!(">a\n{}\n", "A".repeat(10_000));
912 let err = ReaderBuilder::new()
913 .max_line_length(1_000)
914 .build(long.as_bytes())
915 .read_record()
916 .unwrap_err();
917 assert!(
918 matches!(
919 err,
920 Error::TooLarge {
921 what: "line",
922 limit: 1_000,
923 ..
924 }
925 ),
926 "{err}"
927 );
928
929 let record = ReaderBuilder::new()
931 .max_line_length(1_000_000)
932 .build(long.as_bytes())
933 .read_record()
934 .unwrap()
935 .unwrap();
936 assert_eq!(record.seq.len(), 10_000);
937 }
938
939 #[test]
940 fn record_length_limit_catches_many_short_lines() {
941 let mut data = String::from(">a\n");
943 for _ in 0..200 {
944 data.push_str(&"A".repeat(50));
945 data.push('\n');
946 }
947 let err = ReaderBuilder::new()
948 .max_line_length(1_000)
949 .max_record_length(5_000)
950 .build(data.as_bytes())
951 .read_record()
952 .unwrap_err();
953 assert!(
954 matches!(
955 err,
956 Error::TooLarge {
957 what: "sequence",
958 limit: 5_000,
959 ..
960 }
961 ),
962 "{err}"
963 );
964 }
965
966 #[test]
967 fn limits_are_unlimited_by_default() {
968 let long = format!(">chrom\n{}\n", "ACGT".repeat(50_000));
970 let record = FastxReader::with_capacity(long.as_bytes(), 4096)
971 .read_record()
972 .unwrap()
973 .unwrap();
974 assert_eq!(record.seq.len(), 200_000);
975 }
976
977 #[test]
978 fn forced_format_reads_fasta_as_written() {
979 let data = b">a\nACGT\n";
980 let mut reader = FastxReader::with_format(&data[..], Format::Fasta);
981 assert_eq!(reader.format(), Some(Format::Fasta));
982 assert_eq!(reader.next().unwrap().unwrap().seq, b"ACGT");
983 }
984}