Skip to main content

dryice/io/
reader.rs

1//! Reader for the `dryice` format.
2
3use std::{io::Read, marker::PhantomData};
4
5use crate::{
6    block::{
7        BlockDecoder,
8        name::{NameCodec, OmittedNameCodec, RawNameCodec},
9        quality::{OmittedQualityCodec, QualityCodec, RawQualityCodec},
10        sequence::{OmittedSequenceCodec, RawAsciiCodec, SequenceCodec, TwoBitExactCodec},
11    },
12    error::DryIceError,
13    fields::{AllFields, HasKey, HasName, HasQuality, HasSequence, SelectionExpr, SelectionPlan},
14    format,
15    key::{Bytes8Key, Bytes16Key, NoRecordKey, RecordKey},
16    record::{SeqRecord, SeqRecordExt, SeqRecordLike},
17};
18
19/// Private marker type used to track a missing reader source in the builder.
20#[doc(hidden)]
21pub struct MissingInner;
22
23/// Builder-state marker for the default full-row read mode.
24#[doc(hidden)]
25pub struct ReadAllFields;
26
27/// Builder-state marker for a future selected-read mode.
28#[doc(hidden)]
29pub struct ReadSelectedFields<F>(PhantomData<F>);
30
31/// Reader type returned when a field selection is specified on the builder.
32///
33/// This reader still advances through whole records in order, but it only
34/// prepares the fields implied by the selected field set. The selected field
35/// methods then live on [`SelectedRecord`] rather than on the reader itself.
36pub struct SelectedDryIceReader<
37    R,
38    S = RawAsciiCodec,
39    Q = RawQualityCodec,
40    N = RawNameCodec,
41    K = NoRecordKey,
42    F = ReadAllFields,
43> {
44    inner: DryIceReader<R, S, Q, N, K>,
45    _fields: PhantomData<F>,
46}
47
48/// Borrowed current-record view returned by a selected reader.
49///
50/// The methods available on this view are determined by the selected field set
51/// used to build the reader. If a field was not selected, no accessor for that
52/// field is available on the type.
53pub struct SelectedRecord<
54    'a,
55    R,
56    S = RawAsciiCodec,
57    Q = RawQualityCodec,
58    N = RawNameCodec,
59    K = NoRecordKey,
60    F = ReadAllFields,
61> {
62    reader: &'a DryIceReader<R, S, Q, N, K>,
63    _fields: PhantomData<F>,
64}
65
66/// Convenient alias for the borrowed current-record view returned by
67/// [`SelectedDryIceReader::next_record`].
68pub type SelectedRecordView<'a, R, S, Q, N, K, F> = SelectedRecord<'a, R, S, Q, N, K, F>;
69
70/// Convenient alias for the optional next-record return value of a selected reader.
71pub type SelectedNextRecord<'a, R, S, Q, N, K, F> =
72    Option<SelectedRecordView<'a, R, S, Q, N, K, F>>;
73
74fn verify_block_codecs<S, Q, N>(
75    header: &crate::block::header::BlockHeader,
76) -> Result<(), DryIceError>
77where
78    S: SequenceCodec,
79    Q: QualityCodec,
80    N: NameCodec,
81{
82    if header.sequence_codec_tag != S::TYPE_TAG {
83        return Err(DryIceError::SequenceCodecMismatch {
84            expected: S::TYPE_TAG,
85            found: header.sequence_codec_tag,
86        });
87    }
88    if header.quality_codec_tag != Q::TYPE_TAG {
89        return Err(DryIceError::QualityCodecMismatch {
90            expected: Q::TYPE_TAG,
91            found: header.quality_codec_tag,
92        });
93    }
94    if header.name_codec_tag != N::TYPE_TAG {
95        return Err(DryIceError::NameCodecMismatch {
96            expected: N::TYPE_TAG,
97            found: header.name_codec_tag,
98        });
99    }
100    Ok(())
101}
102
103/// Builder for [`DryIceReader`].
104pub struct DryIceReaderBuilder<
105    R = MissingInner,
106    S = RawAsciiCodec,
107    Q = RawQualityCodec,
108    N = RawNameCodec,
109    K = NoRecordKey,
110    M = ReadAllFields,
111> {
112    inner: R,
113    _codec: PhantomData<S>,
114    _quality: PhantomData<Q>,
115    _name: PhantomData<N>,
116    _key: PhantomData<K>,
117    _mode: PhantomData<M>,
118}
119
120impl
121    DryIceReaderBuilder<
122        MissingInner,
123        RawAsciiCodec,
124        RawQualityCodec,
125        RawNameCodec,
126        NoRecordKey,
127        ReadAllFields,
128    >
129{
130    fn new() -> Self {
131        Self {
132            inner: MissingInner,
133            _codec: PhantomData,
134            _quality: PhantomData,
135            _name: PhantomData,
136            _key: PhantomData,
137            _mode: PhantomData,
138        }
139    }
140}
141
142impl<S, Q, N, K, M> DryIceReaderBuilder<MissingInner, S, Q, N, K, M> {
143    /// Set the reader's input source.
144    #[must_use]
145    pub fn inner<R>(self, inner: R) -> DryIceReaderBuilder<R, S, Q, N, K, M> {
146        DryIceReaderBuilder {
147            inner,
148            _codec: self._codec,
149            _quality: self._quality,
150            _name: self._name,
151            _key: self._key,
152            _mode: self._mode,
153        }
154    }
155}
156
157impl<R, Q, N, K, M> DryIceReaderBuilder<R, RawAsciiCodec, Q, N, K, M> {
158    /// Configure the reader to use a user-defined sequence codec.
159    #[must_use]
160    pub fn sequence_codec<S: SequenceCodec>(self) -> DryIceReaderBuilder<R, S, Q, N, K, M> {
161        DryIceReaderBuilder {
162            inner: self.inner,
163            _codec: PhantomData,
164            _quality: PhantomData,
165            _name: PhantomData,
166            _key: PhantomData,
167            _mode: PhantomData,
168        }
169    }
170
171    /// Configure the reader to use the built-in 2-bit exact codec.
172    #[must_use]
173    pub fn two_bit_exact(self) -> DryIceReaderBuilder<R, TwoBitExactCodec, Q, N, K, M> {
174        self.sequence_codec::<TwoBitExactCodec>()
175    }
176
177    /// Configure the reader to expect omitted sequence payloads.
178    #[must_use]
179    pub fn omit_sequence(self) -> DryIceReaderBuilder<R, OmittedSequenceCodec, Q, N, K, M> {
180        self.sequence_codec::<OmittedSequenceCodec>()
181    }
182}
183
184impl<R, S, N, K, M> DryIceReaderBuilder<R, S, RawQualityCodec, N, K, M> {
185    /// Configure the reader to use a user-defined quality codec.
186    #[must_use]
187    pub fn quality_codec<Q: QualityCodec>(self) -> DryIceReaderBuilder<R, S, Q, N, K, M> {
188        DryIceReaderBuilder {
189            inner: self.inner,
190            _codec: PhantomData,
191            _quality: PhantomData,
192            _name: PhantomData,
193            _key: PhantomData,
194            _mode: PhantomData,
195        }
196    }
197
198    /// Configure the reader to expect omitted quality payloads.
199    #[must_use]
200    pub fn omit_quality(self) -> DryIceReaderBuilder<R, S, OmittedQualityCodec, N, K, M> {
201        self.quality_codec::<OmittedQualityCodec>()
202    }
203}
204
205impl<R, S, Q, K, M> DryIceReaderBuilder<R, S, Q, RawNameCodec, K, M> {
206    /// Configure the reader to use a user-defined name codec.
207    #[must_use]
208    pub fn name_codec<N: NameCodec>(self) -> DryIceReaderBuilder<R, S, Q, N, K, M> {
209        DryIceReaderBuilder {
210            inner: self.inner,
211            _codec: PhantomData,
212            _quality: PhantomData,
213            _name: PhantomData,
214            _key: PhantomData,
215            _mode: PhantomData,
216        }
217    }
218
219    /// Configure the reader to expect omitted name payloads.
220    #[must_use]
221    pub fn omit_names(self) -> DryIceReaderBuilder<R, S, Q, OmittedNameCodec, K, M> {
222        self.name_codec::<OmittedNameCodec>()
223    }
224}
225
226impl<R, S, Q, N, M> DryIceReaderBuilder<R, S, Q, N, NoRecordKey, M> {
227    /// Configure the reader for a user-defined record-key type.
228    #[must_use]
229    pub fn record_key<K: RecordKey>(self) -> DryIceReaderBuilder<R, S, Q, N, K, M> {
230        DryIceReaderBuilder {
231            inner: self.inner,
232            _codec: PhantomData,
233            _quality: PhantomData,
234            _name: PhantomData,
235            _key: PhantomData,
236            _mode: PhantomData,
237        }
238    }
239
240    /// Configure the reader for the built-in 8-byte key type.
241    #[must_use]
242    pub fn bytes8_key(self) -> DryIceReaderBuilder<R, S, Q, N, Bytes8Key, M> {
243        self.record_key::<Bytes8Key>()
244    }
245
246    /// Configure the reader for the built-in 16-byte key type.
247    #[must_use]
248    pub fn bytes16_key(self) -> DryIceReaderBuilder<R, S, Q, N, Bytes16Key, M> {
249        self.record_key::<Bytes16Key>()
250    }
251}
252
253impl<R, S, Q, N, K> DryIceReaderBuilder<R, S, Q, N, K, ReadAllFields> {
254    /// Configure a selected-decoding projection for reads built from this builder.
255    ///
256    /// The resulting reader still reads full blocks from disk, but it will only
257    /// decode the fields named in `fields` for each record it advances through.
258    /// This is useful for intermediate scan-style passes that need only a subset
259    /// of each record, such as sequence-only filtering or sequence-plus-key
260    /// partitioning.
261    #[must_use]
262    pub fn select<F: SelectionExpr>(
263        self,
264        _fields: F,
265    ) -> DryIceReaderBuilder<R, S, Q, N, K, ReadSelectedFields<F>> {
266        DryIceReaderBuilder {
267            inner: self.inner,
268            _codec: PhantomData,
269            _quality: PhantomData,
270            _name: PhantomData,
271            _key: PhantomData,
272            _mode: PhantomData,
273        }
274    }
275}
276
277impl<R: Read, S: SequenceCodec, Q: QualityCodec, N: NameCodec>
278    DryIceReaderBuilder<R, S, Q, N, NoRecordKey, ReadAllFields>
279{
280    /// Build an unkeyed reader in the default full-row mode.
281    pub fn build(mut self) -> Result<DryIceReader<R, S, Q, N, NoRecordKey>, DryIceError> {
282        format::read_file_header(&mut self.inner)?;
283        Ok(DryIceReader {
284            inner: self.inner,
285            current_block: None,
286            _codec: PhantomData,
287            _quality: PhantomData,
288            _name: PhantomData,
289            _key: PhantomData,
290        })
291    }
292}
293
294impl<R: Read, S: SequenceCodec, Q: QualityCodec, N: NameCodec, K: RecordKey>
295    DryIceReaderBuilder<R, S, Q, N, K, ReadAllFields>
296{
297    /// Build a keyed reader in the default full-row mode.
298    pub fn build(mut self) -> Result<DryIceReader<R, S, Q, N, K>, DryIceError> {
299        format::read_file_header(&mut self.inner)?;
300        Ok(DryIceReader {
301            inner: self.inner,
302            current_block: None,
303            _codec: PhantomData,
304            _quality: PhantomData,
305            _name: PhantomData,
306            _key: PhantomData,
307        })
308    }
309}
310
311impl<R: Read, S: SequenceCodec, Q: QualityCodec, N: NameCodec, F: SelectionPlan>
312    DryIceReaderBuilder<R, S, Q, N, NoRecordKey, ReadSelectedFields<F>>
313{
314    /// Build an unkeyed selected reader.
315    pub fn build(
316        mut self,
317    ) -> Result<SelectedDryIceReader<R, S, Q, N, NoRecordKey, F>, DryIceError> {
318        format::read_file_header(&mut self.inner)?;
319        Ok(SelectedDryIceReader {
320            inner: DryIceReader {
321                inner: self.inner,
322                current_block: None,
323                _codec: PhantomData,
324                _quality: PhantomData,
325                _name: PhantomData,
326                _key: PhantomData,
327            },
328            _fields: PhantomData,
329        })
330    }
331}
332
333impl<R: Read, S: SequenceCodec, Q: QualityCodec, N: NameCodec, K: RecordKey, F: SelectionPlan>
334    DryIceReaderBuilder<R, S, Q, N, K, ReadSelectedFields<F>>
335{
336    /// Build a keyed selected reader.
337    pub fn build(mut self) -> Result<SelectedDryIceReader<R, S, Q, N, K, F>, DryIceError> {
338        format::read_file_header(&mut self.inner)?;
339        Ok(SelectedDryIceReader {
340            inner: DryIceReader {
341                inner: self.inner,
342                current_block: None,
343                _codec: PhantomData,
344                _quality: PhantomData,
345                _name: PhantomData,
346                _key: PhantomData,
347            },
348            _fields: PhantomData,
349        })
350    }
351}
352
353/// Reads sequencing records from a `dryice` file.
354pub struct DryIceReader<
355    R,
356    S = RawAsciiCodec,
357    Q = RawQualityCodec,
358    N = RawNameCodec,
359    K = NoRecordKey,
360> {
361    inner: R,
362    current_block: Option<BlockDecoder>,
363    _codec: PhantomData<S>,
364    _quality: PhantomData<Q>,
365    _name: PhantomData<N>,
366    _key: PhantomData<K>,
367}
368
369impl DryIceReader<MissingInner, RawAsciiCodec, RawQualityCodec, RawNameCodec, NoRecordKey> {
370    /// Start building a new reader.
371    #[must_use]
372    pub fn builder() -> DryIceReaderBuilder<
373        MissingInner,
374        RawAsciiCodec,
375        RawQualityCodec,
376        RawNameCodec,
377        NoRecordKey,
378        ReadAllFields,
379    > {
380        DryIceReaderBuilder::new()
381    }
382}
383
384impl<R: Read> DryIceReader<R, RawAsciiCodec, RawQualityCodec, RawNameCodec, NoRecordKey> {
385    /// Open a `dryice` file for reading with default codecs.
386    ///
387    /// # Errors
388    ///
389    /// Returns an error if the file header is missing, corrupt, or uses an
390    /// unsupported format version.
391    pub fn new(mut inner: R) -> Result<Self, DryIceError> {
392        format::read_file_header(&mut inner)?;
393        Ok(Self {
394            inner,
395            current_block: None,
396            _codec: PhantomData,
397            _quality: PhantomData,
398            _name: PhantomData,
399            _key: PhantomData,
400        })
401    }
402
403    /// Open a reader configured for the built-in 2-bit exact sequence codec.
404    ///
405    /// # Errors
406    ///
407    /// Returns an error if the file header is missing, corrupt, or uses an
408    /// unsupported format version.
409    pub fn with_two_bit_exact(
410        mut inner: R,
411    ) -> Result<
412        DryIceReader<R, TwoBitExactCodec, RawQualityCodec, RawNameCodec, NoRecordKey>,
413        DryIceError,
414    > {
415        format::read_file_header(&mut inner)?;
416        Ok(DryIceReader {
417            inner,
418            current_block: None,
419            _codec: PhantomData,
420            _quality: PhantomData,
421            _name: PhantomData,
422            _key: PhantomData,
423        })
424    }
425
426    /// Open a reader configured for user-defined codecs.
427    ///
428    /// # Errors
429    ///
430    /// Returns an error if the file header is missing, corrupt, or uses an
431    /// unsupported format version.
432    pub fn with_codecs<S: SequenceCodec, Q: QualityCodec, N: NameCodec>(
433        mut inner: R,
434    ) -> Result<DryIceReader<R, S, Q, N, NoRecordKey>, DryIceError> {
435        format::read_file_header(&mut inner)?;
436        Ok(DryIceReader {
437            inner,
438            current_block: None,
439            _codec: PhantomData,
440            _quality: PhantomData,
441            _name: PhantomData,
442            _key: PhantomData,
443        })
444    }
445
446    /// Open a reader configured for a user-defined record-key type
447    /// with default codecs.
448    ///
449    /// # Errors
450    ///
451    /// Returns an error if the file header is missing, corrupt, or uses an
452    /// unsupported format version.
453    pub fn with_record_key<K2: RecordKey>(
454        mut inner: R,
455    ) -> Result<DryIceReader<R, RawAsciiCodec, RawQualityCodec, RawNameCodec, K2>, DryIceError>
456    {
457        format::read_file_header(&mut inner)?;
458        Ok(DryIceReader {
459            inner,
460            current_block: None,
461            _codec: PhantomData,
462            _quality: PhantomData,
463            _name: PhantomData,
464            _key: PhantomData,
465        })
466    }
467
468    /// Open a reader configured for the built-in 8-byte key type.
469    ///
470    /// # Errors
471    ///
472    /// Returns an error if the file header is missing, corrupt, or uses an
473    /// unsupported format version.
474    pub fn with_bytes8_key(
475        inner: R,
476    ) -> Result<DryIceReader<R, RawAsciiCodec, RawQualityCodec, RawNameCodec, Bytes8Key>, DryIceError>
477    {
478        Self::with_record_key::<Bytes8Key>(inner)
479    }
480
481    /// Open a reader configured for the built-in 16-byte key type.
482    ///
483    /// # Errors
484    ///
485    /// Returns an error if the file header is missing, corrupt, or uses an
486    /// unsupported format version.
487    pub fn with_bytes16_key(
488        inner: R,
489    ) -> Result<
490        DryIceReader<R, RawAsciiCodec, RawQualityCodec, RawNameCodec, Bytes16Key>,
491        DryIceError,
492    > {
493        Self::with_record_key::<Bytes16Key>(inner)
494    }
495}
496
497impl<R: Read> DryIceReader<R> {
498    /// Open a reader with fully user-specified codec and key type
499    /// parameters.
500    ///
501    /// This is the most general constructor, intended for library
502    /// authors who need to configure all four type parameters at
503    /// once. Most users should prefer [`new`](Self::new),
504    /// [`with_codecs`](Self::with_codecs), or the convenience
505    /// constructors instead.
506    ///
507    /// # Errors
508    ///
509    /// Returns an error if the file header is missing, corrupt,
510    /// or uses an unsupported format version.
511    pub fn open<S: SequenceCodec, Q: QualityCodec, N: NameCodec, K: RecordKey>(
512        mut inner: R,
513    ) -> Result<DryIceReader<R, S, Q, N, K>, DryIceError> {
514        format::read_file_header(&mut inner)?;
515        Ok(DryIceReader {
516            inner,
517            current_block: None,
518            _codec: PhantomData,
519            _quality: PhantomData,
520            _name: PhantomData,
521            _key: PhantomData,
522        })
523    }
524}
525
526impl<R, S, Q, N, K, F> SelectedDryIceReader<R, S, Q, N, K, F>
527where
528    R: Read,
529    S: SequenceCodec,
530    Q: QualityCodec,
531    N: NameCodec,
532    F: SelectionPlan,
533{
534    /// Advance to the next selected record in the file.
535    ///
536    /// # Errors
537    ///
538    /// Returns an error if the next block header or payload cannot be read or
539    /// decoded, or if the on-disk codec tags do not match the reader's
540    /// configured codecs.
541    pub fn next_record(&mut self) -> Result<SelectedNextRecord<'_, R, S, Q, N, K, F>, DryIceError> {
542        if self.inner.next_record_prepared::<F>()? {
543            Ok(Some(SelectedRecord {
544                reader: &self.inner,
545                _fields: PhantomData,
546            }))
547        } else {
548            Ok(None)
549        }
550    }
551}
552
553impl<R, S, Q, N, K, F> SelectedRecord<'_, R, S, Q, N, K, F>
554where
555    R: Read,
556    S: SequenceCodec,
557    Q: QualityCodec,
558    N: NameCodec,
559    F: SelectionExpr + HasName,
560{
561    /// Borrow the selected record name.
562    #[must_use]
563    pub fn name(&self) -> &[u8] {
564        self.reader.name()
565    }
566}
567
568impl<R, S, Q, N, K, F> SelectedRecord<'_, R, S, Q, N, K, F>
569where
570    R: Read,
571    S: SequenceCodec,
572    Q: QualityCodec,
573    N: NameCodec,
574    F: SelectionExpr + HasSequence,
575{
576    /// Borrow the selected record sequence.
577    #[must_use]
578    pub fn sequence(&self) -> &[u8] {
579        self.reader.sequence()
580    }
581}
582
583impl<R, S, Q, N, K, F> SelectedRecord<'_, R, S, Q, N, K, F>
584where
585    R: Read,
586    S: SequenceCodec,
587    Q: QualityCodec,
588    N: NameCodec,
589    F: SelectionExpr + HasQuality,
590{
591    /// Borrow the selected record quality.
592    #[must_use]
593    pub fn quality(&self) -> &[u8] {
594        self.reader.quality()
595    }
596}
597
598impl<R, S, Q, N, K, F> SelectedRecord<'_, R, S, Q, N, K, F>
599where
600    R: Read,
601    S: SequenceCodec,
602    Q: QualityCodec,
603    N: NameCodec,
604    K: RecordKey,
605    F: SelectionExpr + HasKey,
606{
607    /// Decode the selected record key.
608    ///
609    /// # Errors
610    ///
611    /// Returns an error if the current block does not contain keys of type `K`
612    /// or if the key bytes cannot be decoded into `K`.
613    pub fn record_key(&self) -> Result<K, DryIceError> {
614        self.reader.record_key()
615    }
616}
617
618impl<R: Read, S: SequenceCodec, Q: QualityCodec, N: NameCodec, K: RecordKey>
619    DryIceReader<R, S, Q, N, K>
620{
621    /// Decode the current record's accelerator key.
622    ///
623    /// # Errors
624    ///
625    /// Returns an error if no record key is present in the current block, if the
626    /// configured key type does not match the block's key metadata, or if the key
627    /// bytes cannot be decoded into `K`.
628    pub fn record_key(&self) -> Result<K, DryIceError> {
629        let block = self
630            .current_block
631            .as_ref()
632            .ok_or(DryIceError::MissingRecordKeySection)?;
633        block.verify_record_key::<K>()?;
634        K::decode_from(block.current_record_key_bytes()?)
635    }
636
637    /// Advance to the next record and return only its key.
638    ///
639    /// # Errors
640    ///
641    /// Returns an error if advancing to the next record fails or if the key
642    /// section is missing or incompatible with `K`.
643    pub fn next_key(&mut self) -> Result<Option<K>, DryIceError> {
644        if self.next_record()? {
645            Ok(Some(self.record_key()?))
646        } else {
647            Ok(None)
648        }
649    }
650}
651
652impl<R: Read, S: SequenceCodec, Q: QualityCodec, N: NameCodec, K> DryIceReader<R, S, Q, N, K> {
653    fn next_record_prepared<P>(&mut self) -> Result<bool, DryIceError>
654    where
655        P: SelectionPlan,
656    {
657        if let Some(block) = &mut self.current_block
658            && block.advance::<S, Q, N, P>()?
659        {
660            return Ok(true);
661        }
662
663        loop {
664            if let Some(header) = format::read_block_header(&mut self.inner)? {
665                verify_block_codecs::<S, Q, N>(&header)?;
666                let mut decoder = BlockDecoder::from_header_and_reader(header, &mut self.inner)?;
667                if decoder.advance::<S, Q, N, P>()? {
668                    self.current_block = Some(decoder);
669                    return Ok(true);
670                }
671            } else {
672                self.current_block = None;
673                return Ok(false);
674            }
675        }
676    }
677
678    /// Advance to the next record in the file.
679    ///
680    /// # Errors
681    ///
682    /// Returns an error if a block header or block payload cannot be read or
683    /// decoded, or if the block's codec tags do not match the reader's
684    /// configured codecs.
685    pub fn next_record(&mut self) -> Result<bool, DryIceError> {
686        self.next_record_prepared::<AllFields>()
687    }
688
689    /// Consume this reader into an iterator of owned [`SeqRecord`] values.
690    pub fn into_records(self) -> DryIceRecords<R, S, Q, N, K> {
691        DryIceRecords { reader: self }
692    }
693}
694
695impl<R: Read, S: SequenceCodec, Q: QualityCodec, N: NameCodec, K> SeqRecordLike
696    for DryIceReader<R, S, Q, N, K>
697{
698    fn name(&self) -> &[u8] {
699        self.current_block
700            .as_ref()
701            .expect("name() called with no current record")
702            .current_name()
703    }
704
705    fn sequence(&self) -> &[u8] {
706        self.current_block
707            .as_ref()
708            .expect("sequence() called with no current record")
709            .current_sequence()
710    }
711
712    fn quality(&self) -> &[u8] {
713        self.current_block
714            .as_ref()
715            .expect("quality() called with no current record")
716            .current_quality()
717    }
718}
719
720/// Iterator over records in a `dryice` file, yielding owned [`SeqRecord`] values.
721pub struct DryIceRecords<
722    R,
723    S = RawAsciiCodec,
724    Q = RawQualityCodec,
725    N = RawNameCodec,
726    K = NoRecordKey,
727> {
728    reader: DryIceReader<R, S, Q, N, K>,
729}
730
731impl<R: Read, S: SequenceCodec, Q: QualityCodec, N: NameCodec, K> Iterator
732    for DryIceRecords<R, S, Q, N, K>
733{
734    type Item = Result<SeqRecord, DryIceError>;
735
736    fn next(&mut self) -> Option<Self::Item> {
737        match self.reader.next_record() {
738            Ok(true) => Some(self.reader.to_seq_record()),
739            Ok(false) => None,
740            Err(e) => Some(Err(e)),
741        }
742    }
743}