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::ByteSlice;
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: Vec<u8>,
19    description: Option<Vec<u8>>,
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<Vec<u8>>) -> Self
32    where
33        N: Into<Vec<u8>>,
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) -> &[u8] {
51        &self.name
52    }
53
54    /// Returns the description if it is set.
55    ///
56    /// # Examples
57    ///
58    /// ```
59    /// use noodles_fasta::record::Definition;
60    ///
61    /// let definition = Definition::new("sq0", None);
62    /// assert_eq!(definition.description(), None);
63    ///
64    /// let definition = Definition::new("sq0", Some(Vec::from("LN:13")));
65    /// assert_eq!(definition.description(), Some(&b"LN:13"[..]));
66    /// ```
67    pub fn description(&self) -> Option<&[u8]> {
68        self.description.as_deref()
69    }
70}
71
72impl fmt::Display for Definition {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        write!(f, "{PREFIX}{}", self.name.as_bstr())?;
75
76        if let Some(description) = self.description() {
77            write!(f, " {}", description.as_bstr())?;
78        }
79
80        Ok(())
81    }
82}
83
84/// An error returned when a raw record definition fails to parse.
85#[derive(Clone, Copy, Debug, Eq, PartialEq)]
86pub enum ParseError {
87    /// The input is empty.
88    Empty,
89    /// The prefix (`>`) is missing.
90    MissingPrefix,
91    /// The sequence name is missing.
92    MissingName,
93}
94
95impl error::Error for ParseError {}
96
97impl fmt::Display for ParseError {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        match self {
100            Self::Empty => f.write_str("empty input"),
101            Self::MissingPrefix => write!(f, "missing prefix ('{PREFIX}')"),
102            Self::MissingName => f.write_str("missing name"),
103        }
104    }
105}
106
107impl FromStr for Definition {
108    type Err = ParseError;
109
110    fn from_str(s: &str) -> Result<Self, Self::Err> {
111        if s.is_empty() {
112            return Err(ParseError::Empty);
113        } else if !s.starts_with(PREFIX) {
114            return Err(ParseError::MissingPrefix);
115        }
116
117        let line = &s[1..];
118        let mut components = line.splitn(2, |c: char| c.is_ascii_whitespace());
119
120        let name = components
121            .next()
122            .and_then(|s| if s.is_empty() { None } else { Some(s.into()) })
123            .ok_or(ParseError::MissingName)?;
124
125        let description = components.next().map(|s| s.trim().into());
126
127        Ok(Self { name, description })
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn test_fmt() {
137        let definition = Definition::new("sq0", None);
138        assert_eq!(definition.to_string(), ">sq0");
139
140        let definition = Definition::new("sq0", Some(Vec::from("LN:13")));
141        assert_eq!(definition.to_string(), ">sq0 LN:13");
142    }
143
144    #[test]
145    fn test_from_str() {
146        assert_eq!(">sq0".parse(), Ok(Definition::new("sq0", None)));
147
148        assert_eq!(
149            ">sq0  LN:13".parse(),
150            Ok(Definition::new("sq0", Some(Vec::from("LN:13"))))
151        );
152
153        assert_eq!("".parse::<Definition>(), Err(ParseError::Empty));
154        assert_eq!("sq0".parse::<Definition>(), Err(ParseError::MissingPrefix));
155        assert_eq!(">".parse::<Definition>(), Err(ParseError::MissingName));
156    }
157}