noodles_vcf/io/reader/
record_buf.rs

1//! VCF record reader.
2
3mod alternate_bases;
4mod filters;
5mod ids;
6mod info;
7mod position;
8mod quality_score;
9mod reference_bases;
10mod reference_sequence_name;
11mod samples;
12pub(crate) mod value;
13
14use std::{error, fmt};
15
16use self::{
17    alternate_bases::parse_alternate_bases, filters::parse_filters, ids::parse_ids,
18    info::parse_info, position::parse_position, quality_score::parse_quality_score,
19    reference_bases::parse_reference_bases, reference_sequence_name::parse_reference_sequence_name,
20    samples::parse_samples,
21};
22use crate::{variant::RecordBuf, Header};
23
24const MISSING: &str = ".";
25
26/// An error when a raw VCF record fails to parse.
27#[allow(clippy::enum_variant_names)]
28#[derive(Clone, Debug, Eq, PartialEq)]
29pub enum ParseError {
30    /// The position is invalid.
31    InvalidPosition(position::ParseError),
32    /// The IDs are invalid.
33    InvalidIds(ids::ParseError),
34    /// The reference bases are invalid.
35    InvalidReferenceBases(reference_bases::ParseError),
36    /// The alternate bases are invalid.
37    InvalidAlternateBases(alternate_bases::ParseError),
38    /// The quality score is invalid.
39    InvalidQualityScore(quality_score::ParseError),
40    /// The filters are invalid.
41    InvalidFilters(filters::ParseError),
42    /// The info is invalid.
43    InvalidInfo(info::ParseError),
44    /// The samples are invalid.
45    InvalidSamples(samples::ParseError),
46}
47
48impl error::Error for ParseError {
49    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
50        match self {
51            Self::InvalidPosition(e) => Some(e),
52            Self::InvalidIds(e) => Some(e),
53            Self::InvalidReferenceBases(e) => Some(e),
54            Self::InvalidAlternateBases(e) => Some(e),
55            Self::InvalidQualityScore(e) => Some(e),
56            Self::InvalidFilters(e) => Some(e),
57            Self::InvalidInfo(e) => Some(e),
58            Self::InvalidSamples(e) => Some(e),
59        }
60    }
61}
62
63impl fmt::Display for ParseError {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        match self {
66            Self::InvalidPosition(_) => write!(f, "invalid position"),
67            Self::InvalidIds(_) => write!(f, "invalid IDs"),
68            Self::InvalidReferenceBases(_) => write!(f, "invalid reference bases"),
69            Self::InvalidAlternateBases(_) => write!(f, "invalid alternate bases"),
70            Self::InvalidQualityScore(_) => write!(f, "invalid quality score"),
71            Self::InvalidFilters(_) => write!(f, "invalid filters"),
72            Self::InvalidInfo(_) => write!(f, "invalid info"),
73            Self::InvalidSamples(_) => write!(f, "invalid samples"),
74        }
75    }
76}
77
78pub(crate) fn parse_record_buf(
79    mut s: &str,
80    header: &Header,
81    record: &mut RecordBuf,
82) -> Result<(), ParseError> {
83    let field = next_field(&mut s);
84    parse_reference_sequence_name(field, record.reference_sequence_name_mut());
85
86    let field = next_field(&mut s);
87    *record.variant_start_mut() = parse_position(field).map_err(ParseError::InvalidPosition)?;
88
89    record.ids_mut().as_mut().clear();
90    let field = next_field(&mut s);
91    if field != MISSING {
92        parse_ids(field, record.ids_mut()).map_err(ParseError::InvalidIds)?;
93    }
94
95    let field = next_field(&mut s);
96    parse_reference_bases(field, record.reference_bases_mut())
97        .map_err(ParseError::InvalidReferenceBases)?;
98
99    record.alternate_bases_mut().as_mut().clear();
100    let field = next_field(&mut s);
101    if field != MISSING {
102        parse_alternate_bases(field, record.alternate_bases_mut())
103            .map_err(ParseError::InvalidAlternateBases)?;
104    }
105
106    let field = next_field(&mut s);
107    *record.quality_score_mut() = match field {
108        MISSING => None,
109        _ => parse_quality_score(field)
110            .map(Some)
111            .map_err(ParseError::InvalidQualityScore)?,
112    };
113
114    let field = next_field(&mut s);
115    match field {
116        MISSING => record.filters_mut().as_mut().clear(),
117        _ => parse_filters(field, record.filters_mut()).map_err(ParseError::InvalidFilters)?,
118    }
119
120    record.info_mut().clear();
121    let field = next_field(&mut s);
122    if field != MISSING {
123        parse_info(header, field, record.info_mut()).map_err(ParseError::InvalidInfo)?;
124    }
125
126    parse_samples(header, s, record.samples_mut()).map_err(ParseError::InvalidSamples)?;
127
128    Ok(())
129}
130
131fn next_field<'a>(s: &mut &'a str) -> &'a str {
132    const DELIMITER: char = '\t';
133
134    let (field, rest) = s
135        .split_once(DELIMITER)
136        .unwrap_or_else(|| s.split_at(s.len()));
137
138    *s = rest;
139
140    field
141}