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}