noodles_fasta/record/
definition.rs

1//! FASTA record definition and components.
2
3use std::{
4    error, fmt,
5    str::{self, FromStr},
6};
7
8use bstr::{BStr, BString};
9
10const PREFIX: char = '>';
11
12/// A FASTA record definition.
13///
14/// A definition represents a definition line, i.e, a reference sequence name and, optionally, a
15/// description.
16#[derive(Clone, Debug, Eq, PartialEq)]
17pub struct Definition {
18    name: BString,
19    description: Option<BString>,
20}
21
22impl Definition {
23    /// Creates a FASTA record definition.
24    ///
25    /// # Examples
26    ///
27    /// ```
28    /// use noodles_fasta::record::Definition;
29    /// let definition = Definition::new("sq0", None);
30    /// ```
31    pub fn new<N>(name: N, description: Option<BString>) -> Self
32    where
33        N: Into<BString>,
34    {
35        Self {
36            name: name.into(),
37            description,
38        }
39    }
40
41    /// Returns the record name.
42    ///
43    /// # Examples
44    ///
45    /// ```
46    /// use noodles_fasta::record::Definition;
47    /// let definition = Definition::new("sq0", None);
48    /// assert_eq!(definition.name(), b"sq0");
49    /// ```
50    pub fn name(&self) -> &BStr {
51        self.name.as_ref()
52    }
53
54    /// Returns the description if it is set.
55    ///
56    /// # Examples
57    ///
58    /// ```
59    /// use bstr::{BStr, BString};
60    /// use noodles_fasta::record::Definition;
61    ///
62    /// let definition = Definition::new("sq0", None);
63    /// assert_eq!(definition.description(), None);
64    ///
65    /// let definition = Definition::new("sq0", Some(BString::from("LN:13")));
66    /// assert_eq!(definition.description(), Some(BStr::new("LN:13")));
67    /// ```
68    pub fn description(&self) -> Option<&BStr> {
69        self.description.as_ref().map(|s| s.as_ref())
70    }
71}
72
73impl fmt::Display for Definition {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        write!(f, "{PREFIX}{}", self.name)?;
76
77        if let Some(description) = self.description() {
78            write!(f, " {description}")?;
79        }
80
81        Ok(())
82    }
83}
84
85/// An error returned when a raw record definition fails to parse.
86#[derive(Clone, Copy, Debug, Eq, PartialEq)]
87pub enum ParseError {
88    /// The input is empty.
89    Empty,
90    /// The prefix (`>`) is missing.
91    MissingPrefix,
92    /// The sequence name is missing.
93    MissingName,
94}
95
96impl error::Error for ParseError {}
97
98impl fmt::Display for ParseError {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        match self {
101            Self::Empty => f.write_str("empty input"),
102            Self::MissingPrefix => write!(f, "missing prefix ('{PREFIX}')"),
103            Self::MissingName => f.write_str("missing name"),
104        }
105    }
106}
107
108impl FromStr for Definition {
109    type Err = ParseError;
110
111    fn from_str(s: &str) -> Result<Self, Self::Err> {
112        if s.is_empty() {
113            return Err(ParseError::Empty);
114        } else if !s.starts_with(PREFIX) {
115            return Err(ParseError::MissingPrefix);
116        }
117
118        let line = &s[1..];
119        let mut components = line.splitn(2, |c: char| c.is_ascii_whitespace());
120
121        let name = components
122            .next()
123            .and_then(|s| if s.is_empty() { None } else { Some(s.into()) })
124            .ok_or(ParseError::MissingName)?;
125
126        let description = components.next().map(|s| s.trim().into());
127
128        Ok(Self { name, description })
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn test_fmt() {
138        let definition = Definition::new("sq0", None);
139        assert_eq!(definition.to_string(), ">sq0");
140
141        let definition = Definition::new("sq0", Some(BString::from("LN:13")));
142        assert_eq!(definition.to_string(), ">sq0 LN:13");
143    }
144
145    #[test]
146    fn test_from_str() {
147        assert_eq!(">sq0".parse(), Ok(Definition::new("sq0", None)));
148
149        assert_eq!(
150            ">sq0  LN:13".parse(),
151            Ok(Definition::new("sq0", Some(BString::from("LN:13"))))
152        );
153
154        assert_eq!("".parse::<Definition>(), Err(ParseError::Empty));
155        assert_eq!("sq0".parse::<Definition>(), Err(ParseError::MissingPrefix));
156        assert_eq!(">".parse::<Definition>(), Err(ParseError::MissingName));
157    }
158}