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}