omics_coordinate/
strand.rs

1//! Strands of a molecule.
2
3use thiserror::Error;
4
5///////////////////////////////////////////////////////////////////////////////////////
6// Assertions
7////////////////////////////////////////////////////////////////////////////////////////
8
9const _: () = {
10    // A strand should always fit into a single byte.
11    assert!(size_of::<Strand>() == 1);
12
13    /// A function to ensure that types are `Copy`.
14    const fn is_copy<T: Copy>() {}
15    is_copy::<Strand>();
16};
17
18////////////////////////////////////////////////////////////////////////////////////////
19// Errors
20////////////////////////////////////////////////////////////////////////////////////////
21
22/// A error related to parsing a strand.
23#[derive(Error, Debug, PartialEq, Eq)]
24pub enum ParseError {
25    /// An invalid strand.
26    ///
27    /// Occurs when a strand cannot be parsed.
28    #[error("invalid strand: {value}")]
29    Invalid {
30        /// The value that was attempted to be parsed.
31        value: String,
32    },
33}
34
35/// A [`Result`](std::result::Result) with an [`ParseError`].
36pub type ParseResult<T> = std::result::Result<T, ParseError>;
37
38/// A strand-related error.
39#[derive(Error, Debug, PartialEq, Eq)]
40pub enum Error {
41    /// A parse error.
42    #[error("parse error: {0}")]
43    Parse(#[from] ParseError),
44}
45
46/// A [`Result`](std::result::Result) with an [`Error`].
47pub type Result<T> = std::result::Result<T, Error>;
48
49////////////////////////////////////////////////////////////////////////////////////////
50// Strand
51////////////////////////////////////////////////////////////////////////////////////////
52
53/// The strand of a double-stranded molecule.
54///
55/// For a more in-depth discussion on this, please see [this section of the
56/// docs](crate#strand).
57#[repr(u8)]
58#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
59pub enum Strand {
60    /// The positive strand (`+`).
61    ///
62    /// This is also known as the _sense_ strand.
63    Positive,
64
65    /// The negative strand (`-`).
66    ///
67    /// This is also known as the _antisense_ strand.
68    Negative,
69}
70
71impl Strand {
72    /// Complements a strand.
73    ///
74    /// # Examples
75    ///
76    /// ```
77    /// use omics_coordinate::Strand;
78    ///
79    /// assert_eq!(Strand::Positive.complement(), Strand::Negative);
80    /// assert_eq!(Strand::Negative.complement(), Strand::Positive);
81    /// ```
82    pub fn complement(&self) -> Strand {
83        match self {
84            Strand::Positive => Strand::Negative,
85            Strand::Negative => Strand::Positive,
86        }
87    }
88}
89
90impl std::str::FromStr for Strand {
91    type Err = Error;
92
93    fn from_str(s: &str) -> Result<Self> {
94        match s {
95            "+" => Ok(Strand::Positive),
96            "-" => Ok(Strand::Negative),
97            _ => Err(Error::Parse(ParseError::Invalid {
98                value: s.to_string(),
99            })),
100        }
101    }
102}
103
104// NOTE: technically, this is just a duplication of of [`FromStr`] above. That
105// being said, this is required for the [`Coordinate::try_new()`] call to work
106// correctly with string slices.
107
108impl TryFrom<&str> for Strand {
109    type Error = Error;
110
111    fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
112        value.parse()
113    }
114}
115
116impl std::fmt::Display for Strand {
117    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118        match self {
119            Strand::Positive => write!(f, "+"),
120            Strand::Negative => write!(f, "-"),
121        }
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    #[test]
130    fn parse() {
131        let s = "+".parse::<Strand>().unwrap();
132        assert_eq!(s, Strand::Positive);
133
134        let s = "-".parse::<Strand>().unwrap();
135        assert_eq!(s, Strand::Negative);
136
137        let err = "a".parse::<Strand>().unwrap_err();
138        assert_eq!(err.to_string(), "parse error: invalid strand: a");
139    }
140
141    #[test]
142    fn serialize() {
143        assert_eq!(Strand::Positive.to_string(), "+");
144        assert_eq!(Strand::Negative.to_string(), "-");
145    }
146
147    #[test]
148    fn complement() {
149        assert_eq!(Strand::Positive.complement(), Strand::Negative);
150        assert_eq!(Strand::Negative.complement(), Strand::Positive);
151    }
152}