Skip to main content

noodles_sam/header/parser/record/value/map/
program.rs

1use std::{error, fmt};
2
3use bstr::{BStr, BString};
4
5use super::field::{consume_delimiter, consume_separator, parse_tag, parse_value, value};
6use crate::header::{
7    parser::Context,
8    record::value::{
9        Map,
10        map::{
11            self, OtherFields, Program,
12            program::{Tag, tag},
13            tag::Other,
14        },
15    },
16};
17
18/// An error returned when a SAM header program record value fails to parse.
19#[derive(Clone, Debug, Eq, PartialEq)]
20pub enum ParseError {
21    InvalidField(super::field::ParseError),
22    InvalidTag(super::field::tag::ParseError),
23    InvalidValue(value::ParseError),
24    MissingId,
25    InvalidId(value::ParseError),
26    InvalidOther(Other<tag::Standard>, value::ParseError),
27    DuplicateTag(Tag),
28}
29
30impl error::Error for ParseError {
31    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
32        match self {
33            Self::InvalidField(e) => Some(e),
34            Self::InvalidTag(e) => Some(e),
35            Self::InvalidId(e) => Some(e),
36            Self::InvalidOther(_, e) => Some(e),
37            _ => None,
38        }
39    }
40}
41
42impl fmt::Display for ParseError {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        match self {
45            Self::InvalidField(_) => write!(f, "invalid field"),
46            Self::InvalidTag(_) => write!(f, "invalid tag"),
47            Self::InvalidValue(_) => write!(f, "invalid value"),
48            Self::MissingId => write!(f, "missing ID field"),
49            Self::InvalidId(_) => write!(f, "invalid ID"),
50            Self::InvalidOther(tag, _) => write!(f, "invalid other ({tag})"),
51            Self::DuplicateTag(tag) => write!(f, "duplicate tag: {tag}"),
52        }
53    }
54}
55
56pub(crate) fn parse_program(
57    src: &mut &[u8],
58    ctx: &Context,
59) -> Result<(BString, Map<Program>), ParseError> {
60    let mut id = None;
61
62    let mut other_fields = OtherFields::new();
63
64    while !src.is_empty() {
65        consume_delimiter(src).map_err(ParseError::InvalidField)?;
66        let tag = parse_tag(src).map_err(ParseError::InvalidTag)?;
67        consume_separator(src).map_err(ParseError::InvalidField)?;
68
69        match tag {
70            tag::ID => parse_id(src).and_then(|v| try_replace(&mut id, ctx, tag::ID, v))?,
71            Tag::Other(t) => parse_other(src, t)
72                .and_then(|value| try_insert(&mut other_fields, ctx, t, value))?,
73        }
74    }
75
76    let id = id.ok_or(ParseError::MissingId)?;
77
78    Ok((
79        id.into(),
80        Map {
81            inner: Program,
82            other_fields,
83        },
84    ))
85}
86
87fn parse_id<'a>(src: &mut &'a [u8]) -> Result<&'a BStr, ParseError> {
88    parse_value(src).map_err(ParseError::InvalidId)
89}
90
91fn parse_other<'a>(src: &mut &'a [u8], tag: Other<tag::Standard>) -> Result<&'a BStr, ParseError> {
92    parse_value(src).map_err(|e| ParseError::InvalidOther(tag, e))
93}
94
95fn try_replace<T>(
96    option: &mut Option<T>,
97    ctx: &Context,
98    tag: Tag,
99    value: T,
100) -> Result<(), ParseError> {
101    if option.replace(value).is_some() && !ctx.allow_duplicate_tags() {
102        Err(ParseError::DuplicateTag(tag))
103    } else {
104        Ok(())
105    }
106}
107
108fn try_insert<V>(
109    other_fields: &mut OtherFields<tag::Standard>,
110    ctx: &Context,
111    tag: map::tag::Other<tag::Standard>,
112    value: V,
113) -> Result<(), ParseError>
114where
115    V: Into<BString>,
116{
117    if other_fields.insert(tag, value.into()).is_some() && !ctx.allow_duplicate_tags() {
118        Err(ParseError::DuplicateTag(Tag::Other(tag)))
119    } else {
120        Ok(())
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn test_parse_program() {
130        let mut src = &b"\tID:pg0"[..];
131        let ctx = Context::default();
132        let actual = parse_program(&mut src, &ctx);
133        let expected = (BString::from("pg0"), Map::<Program>::default());
134        assert_eq!(actual, Ok(expected));
135    }
136
137    #[test]
138    fn test_parse_program_with_missing_id() {
139        let mut src = &b"\tPN:pg0"[..];
140        let ctx = Context::default();
141        assert_eq!(parse_program(&mut src, &ctx), Err(ParseError::MissingId));
142    }
143}