noodles_bcf/
record.rs

1//! BCF record.
2
3mod alternate_bases;
4pub(crate) mod codec;
5mod fields;
6mod filters;
7mod ids;
8mod info;
9mod reference_bases;
10pub mod samples;
11mod value;
12
13use std::{fmt, io, str};
14
15use noodles_core::Position;
16use noodles_vcf::{self as vcf, header::StringMaps};
17
18use self::fields::Fields;
19pub(crate) use self::value::Value;
20pub use self::{
21    alternate_bases::AlternateBases, filters::Filters, ids::Ids, info::Info,
22    reference_bases::ReferenceBases, samples::Samples,
23};
24
25/// A BCF record.
26#[derive(Clone, Default, PartialEq)]
27pub struct Record(Fields);
28
29impl Record {
30    pub(crate) fn fields_mut(&mut self) -> &mut Fields {
31        &mut self.0
32    }
33
34    /// Returns the reference sequence ID of the record.
35    ///
36    /// The reference sequence ID represents an index in the contig string map, which associates an
37    /// ID (by position) with a contig record in the VCF header (by name). That is, to get the
38    /// associated contig record in the VCF header, the contig string map must first be queried by
39    /// position to find the chromosome name, and then the contigs in the VCF header can be queried
40    /// by name.
41    ///
42    /// # Examples
43    ///
44    /// ```
45    /// use noodles_bcf as bcf;
46    /// let record = bcf::Record::default();
47    /// assert_eq!(record.reference_sequence_id()?, 0);
48    /// # Ok::<_, std::io::Error>(())
49    /// ```
50    pub fn reference_sequence_id(&self) -> io::Result<usize> {
51        let n = self.0.reference_sequence_id();
52        usize::try_from(n).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
53    }
54
55    /// Returns the reference sequence name.
56    ///
57    /// # Examples
58    ///
59    /// ```
60    /// use noodles_bcf as bcf;
61    /// use noodles_vcf::{
62    ///     self as vcf,
63    ///     header::{record::value::{map::Contig, Map}, StringMaps},
64    /// };
65    ///
66    /// let header = vcf::Header::builder()
67    ///     .add_contig("sq0", Map::<Contig>::new())
68    ///     .build();
69    /// let string_maps = StringMaps::try_from(&header)?;
70    ///
71    /// let record = bcf::Record::default();
72    /// assert_eq!(record.reference_sequence_name(&string_maps)?, "sq0");
73    /// # Ok::<_, Box<dyn std::error::Error>>(())
74    /// ```
75    pub fn reference_sequence_name<'h>(&self, string_maps: &'h StringMaps) -> io::Result<&'h str> {
76        self.reference_sequence_id().and_then(|i| {
77            string_maps.contigs().get_index(i).ok_or_else(|| {
78                io::Error::new(
79                    io::ErrorKind::InvalidData,
80                    "missing reference sequence name in contig string map",
81                )
82            })
83        })
84    }
85
86    /// Returns the variant start position.
87    ///
88    /// This position is 1-based, inclusive.
89    ///
90    /// # Examples
91    ///
92    /// ```
93    /// use noodles_bcf as bcf;
94    /// use noodles_core::Position;
95    /// let record = bcf::Record::default();
96    /// assert_eq!(record.variant_start().transpose()?, Some(Position::MIN));
97    /// # Ok::<_, std::io::Error>(())
98    /// ```
99    pub fn variant_start(&self) -> Option<io::Result<Position>> {
100        self.0.variant_start().map(|n| {
101            usize::try_from(n)
102                .map(|m| m + 1)
103                .and_then(Position::try_from)
104                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
105        })
106    }
107
108    pub(crate) fn rlen(&self) -> io::Result<usize> {
109        let n = self.0.span();
110        usize::try_from(n).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
111    }
112
113    /// Returns the end position of this record.
114    ///
115    /// This position is 1-based, inclusive.
116    ///
117    /// # Examples
118    ///
119    /// ```
120    /// use noodles_bcf as bcf;
121    /// use noodles_core::Position;
122    /// let record = bcf::Record::default();
123    /// assert_eq!(record.end()?, Position::MIN);
124    /// # Ok::<_, std::io::Error>(())
125    /// ```
126    pub fn end(&self) -> io::Result<Position> {
127        let Some(start) = self.variant_start().transpose()? else {
128            todo!();
129        };
130
131        let len = self.rlen()?;
132
133        start.checked_add(len - 1).ok_or_else(|| {
134            io::Error::new(
135                io::ErrorKind::InvalidData,
136                "calculation of the end position overflowed",
137            )
138        })
139    }
140
141    /// Returns the quality score.
142    ///
143    /// # Examples
144    ///
145    /// ```
146    /// use noodles_bcf as bcf;
147    /// let record = bcf::Record::default();
148    /// assert!(record.quality_score()?.is_none());
149    /// # Ok::<_, std::io::Error>(())
150    /// ```
151    pub fn quality_score(&self) -> io::Result<Option<f32>> {
152        self.0.quality_score()
153    }
154
155    /// Returns the IDs.
156    ///
157    /// # Examples
158    ///
159    /// ```
160    /// use noodles_bcf as bcf;
161    /// use noodles_vcf::variant::record::Ids;
162    /// let record = bcf::Record::default();
163    /// assert!(record.ids().is_empty());
164    /// ```
165    pub fn ids(&self) -> Ids<'_> {
166        self.0.ids()
167    }
168
169    /// Returns the reference bases.
170    ///
171    /// # Examples
172    ///
173    /// ```
174    /// use noodles_bcf as bcf;
175    /// let record = bcf::Record::default();
176    /// assert_eq!(record.reference_bases().as_ref(), b"N");
177    /// ```
178    pub fn reference_bases(&self) -> ReferenceBases<'_> {
179        self.0.reference_bases()
180    }
181
182    /// Returns the alternate bases.
183    ///
184    /// # Examples
185    ///
186    /// ```
187    /// use noodles_bcf as bcf;
188    /// use noodles_vcf::variant::record::AlternateBases;
189    /// let record = bcf::Record::default();
190    /// assert!(record.alternate_bases().is_empty());
191    /// ```
192    pub fn alternate_bases(&self) -> AlternateBases<'_> {
193        self.0.alternate_bases()
194    }
195
196    /// Returns the filters.
197    ///
198    /// # Examples
199    ///
200    /// ```
201    /// use noodles_bcf as bcf;
202    /// use noodles_vcf::variant::record::Filters;
203    /// let record = bcf::Record::default();
204    /// assert!(record.filters().is_empty());
205    /// ```
206    pub fn filters(&self) -> Filters<'_> {
207        self.0.filters()
208    }
209
210    /// Returns the info.
211    ///
212    /// # Examples
213    ///
214    /// ```
215    /// use noodles_bcf as bcf;
216    /// use noodles_vcf::variant::record::Info;
217    /// let record = bcf::Record::default();
218    /// assert!(record.info().is_empty());
219    /// ```
220    pub fn info(&self) -> Info<'_> {
221        self.0.info()
222    }
223
224    /// Returns the samples.
225    ///
226    /// # Examples
227    ///
228    /// ```
229    /// use noodles_bcf as bcf;
230    /// use noodles_vcf::variant::record::Samples;
231    /// let record = bcf::Record::default();
232    /// assert!(record.samples()?.is_empty());
233    /// # Ok::<_, std::io::Error>(())
234    /// ```
235    pub fn samples(&self) -> io::Result<Samples<'_>> {
236        self.0.samples()
237    }
238}
239
240impl fmt::Debug for Record {
241    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242        f.debug_struct("Record")
243            .field("reference_sequence_id", &self.reference_sequence_id())
244            .field("position", &self.variant_start())
245            .field("ids", &self.ids())
246            .field("reference_bases", &self.reference_bases())
247            .field("alternate_bases", &self.alternate_bases())
248            .field("quality_score", &self.quality_score())
249            .field("filters", &self.filters())
250            .field("info", &self.info())
251            .field("samples", &self.samples())
252            .finish()
253    }
254}
255
256impl vcf::variant::Record for Record {
257    fn reference_sequence_name<'a, 'h: 'a>(
258        &'a self,
259        header: &'h vcf::Header,
260    ) -> io::Result<&'a str> {
261        self.reference_sequence_name(header.string_maps())
262    }
263
264    fn variant_start(&self) -> Option<io::Result<Position>> {
265        self.variant_start()
266    }
267
268    fn ids(&self) -> Box<dyn vcf::variant::record::Ids + '_> {
269        Box::new(self.ids())
270    }
271
272    fn reference_bases(&self) -> Box<dyn vcf::variant::record::ReferenceBases + '_> {
273        Box::new(self.reference_bases())
274    }
275
276    fn alternate_bases(&self) -> Box<dyn vcf::variant::record::AlternateBases + '_> {
277        Box::new(self.alternate_bases())
278    }
279
280    fn quality_score(&self) -> Option<io::Result<f32>> {
281        self.quality_score().transpose()
282    }
283
284    fn filters(&self) -> Box<dyn vcf::variant::record::Filters + '_> {
285        Box::new(self.filters())
286    }
287
288    fn info(&self) -> Box<dyn vcf::variant::record::Info + '_> {
289        Box::new(self.info())
290    }
291
292    fn samples(&self) -> io::Result<Box<dyn vcf::variant::record::Samples + '_>> {
293        self.samples()
294            .map(|samples| Box::new(samples) as Box<dyn vcf::variant::record::Samples>)
295    }
296}