bio_types/
strand.rs

1// Copyright 2014-2016 Johannes Köster.
2// Licensed under the MIT license (http://opensource.org/licenses/MIT)
3// This file may not be copied, modified, or distributed
4// except according to those terms.
5
6//! Data types for strand information on annotations.
7
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10use std::fmt::{self, Display, Formatter};
11use std::ops::Neg;
12use std::str::FromStr;
13use thiserror::Error;
14
15/// Strand information.
16#[derive(Debug, Clone, Copy)]
17#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
18pub enum Strand {
19    Forward,
20    Reverse,
21    Unknown,
22}
23
24impl Strand {
25    /// Returns a `Strand` enum representing the given char.
26    ///
27    /// The mapping is as follows:
28    ///     * '+', 'f', or 'F' becomes `Strand::Forward`
29    ///     * '-', 'r', or 'R' becomes `Strand::Reverse`
30    ///     * '.', '?' becomes `Strand::Unknown`
31    ///     * Any other inputs will return an `Err(StrandError::InvalidChar)`
32    pub fn from_char(strand_char: &char) -> Result<Strand, StrandError> {
33        match *strand_char {
34            '+' | 'f' | 'F' => Ok(Strand::Forward),
35            '-' | 'r' | 'R' => Ok(Strand::Reverse),
36            '.' | '?' => Ok(Strand::Unknown),
37            invalid => Err(StrandError::InvalidChar(invalid)),
38        }
39    }
40
41    /// Symbol denoting the strand. By convention, in BED and GFF
42    /// files, the forward strand is `+`, the reverse strand is `-`,
43    /// and unknown or unspecified strands are `.`.
44    pub fn strand_symbol(&self) -> &str {
45        match *self {
46            Strand::Forward => "+",
47            Strand::Reverse => "-",
48            Strand::Unknown => ".",
49        }
50    }
51
52    pub fn is_unknown(&self) -> bool {
53        matches!(*self, Strand::Unknown)
54    }
55}
56
57#[allow(clippy::match_like_matches_macro)]
58impl PartialEq for Strand {
59    /// Returns true if both are `Forward` or both are `Reverse`, otherwise returns false.
60    fn eq(&self, other: &Strand) -> bool {
61        match (self, other) {
62            (&Strand::Forward, &Strand::Forward) => true,
63            (&Strand::Reverse, &Strand::Reverse) => true,
64            _ => false,
65        }
66    }
67}
68
69impl Neg for Strand {
70    type Output = Strand;
71    fn neg(self) -> Strand {
72        match self {
73            Strand::Forward => Strand::Reverse,
74            Strand::Reverse => Strand::Forward,
75            Strand::Unknown => Strand::Unknown,
76        }
77    }
78}
79
80#[allow(clippy::match_like_matches_macro)]
81impl Same for Strand {
82    fn same(&self, s1: &Self) -> bool {
83        match (*self, *s1) {
84            (Strand::Forward, Strand::Forward) => true,
85            (Strand::Reverse, Strand::Reverse) => true,
86            (Strand::Unknown, Strand::Unknown) => true,
87            _ => false,
88        }
89    }
90}
91
92impl Display for Strand {
93    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
94        f.write_str(self.strand_symbol())
95    }
96}
97
98impl FromStr for Strand {
99    type Err = StrandError;
100    fn from_str(s: &str) -> Result<Self, Self::Err> {
101        match s {
102            "+" | "(+)" => Ok(Strand::Forward),
103            "-" | "(-)" => Ok(Strand::Reverse),
104            "." | "" => Ok(Strand::Unknown),
105            _ => Err(StrandError::ParseError),
106        }
107    }
108}
109
110impl From<ReqStrand> for Strand {
111    fn from(rstr: ReqStrand) -> Self {
112        match rstr {
113            ReqStrand::Forward => Strand::Forward,
114            ReqStrand::Reverse => Strand::Reverse,
115        }
116    }
117}
118
119impl From<Option<ReqStrand>> for Strand {
120    fn from(orstr: Option<ReqStrand>) -> Self {
121        match orstr {
122            Some(ReqStrand::Forward) => Strand::Forward,
123            Some(ReqStrand::Reverse) => Strand::Reverse,
124            None => Strand::Unknown,
125        }
126    }
127}
128
129impl From<NoStrand> for Strand {
130    fn from(_: NoStrand) -> Self {
131        Strand::Unknown
132    }
133}
134
135/// Strand information for annotations that require a strand.
136#[derive(Debug, Clone, Hash, PartialEq, Eq, Ord, PartialOrd, Copy)]
137#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
138pub enum ReqStrand {
139    Forward,
140    Reverse,
141}
142
143impl ReqStrand {
144    /// Returns a `ReqStrand` enum representing the given char.
145    ///
146    /// The mapping is as follows:
147    ///     * '+', 'f', or 'F' becomes `Strand::Forward`
148    ///     * '-', 'r', or 'R' becomes `Strand::Reverse`
149    ///     * Any other inputs will return an `Err(StrandError::InvalidChar)`
150    pub fn from_char(strand_char: &char) -> Result<ReqStrand, StrandError> {
151        match *strand_char {
152            '+' | 'f' | 'F' => Ok(ReqStrand::Forward),
153            '-' | 'r' | 'R' => Ok(ReqStrand::Reverse),
154            invalid => Err(StrandError::InvalidChar(invalid)),
155        }
156    }
157
158    /// Symbol denoting the strand. By convention, in BED and GFF
159    /// files, the forward strand is `+` and the reverse strand is `-`.
160    pub fn strand_symbol(&self) -> &str {
161        match *self {
162            ReqStrand::Forward => "+",
163            ReqStrand::Reverse => "-",
164        }
165    }
166
167    /// Convert the (optional) strand of some other annotation
168    /// according to this strand. That is, reverse the strand of the
169    /// other annotation for `ReqStrand::Reverse` and leave it
170    /// unchanged for `ReqStrand::Forward`.
171    ///
172    /// # Arguments
173    ///
174    /// * `x` is the strand information from some other annotation.
175    ///
176    /// ```
177    /// use bio_types::strand::{ReqStrand,Strand};
178    /// assert_eq!(ReqStrand::Forward.on_strand(Strand::Reverse),
179    ///            ReqStrand::Reverse.on_strand(Strand::Forward));
180    /// ```
181    pub fn on_strand<T>(&self, x: T) -> T
182    where
183        T: Neg<Output = T>,
184    {
185        match self {
186            ReqStrand::Forward => x,
187            ReqStrand::Reverse => -x,
188        }
189    }
190}
191
192impl Same for ReqStrand {
193    fn same(&self, s1: &Self) -> bool {
194        self == s1
195    }
196}
197
198impl Display for ReqStrand {
199    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
200        f.write_str(self.strand_symbol())
201    }
202}
203
204impl FromStr for ReqStrand {
205    type Err = StrandError;
206    fn from_str(s: &str) -> Result<Self, Self::Err> {
207        match s {
208            "+" | "(+)" => Ok(ReqStrand::Forward),
209            "-" | "(-)" => Ok(ReqStrand::Reverse),
210            _ => Err(StrandError::ParseError),
211        }
212    }
213}
214
215impl From<Strand> for Option<ReqStrand> {
216    fn from(strand: Strand) -> Option<ReqStrand> {
217        match strand {
218            Strand::Forward => Some(ReqStrand::Forward),
219            Strand::Reverse => Some(ReqStrand::Reverse),
220            Strand::Unknown => None,
221        }
222    }
223}
224
225impl From<NoStrand> for Option<ReqStrand> {
226    fn from(_: NoStrand) -> Option<ReqStrand> {
227        None
228    }
229}
230
231impl Neg for ReqStrand {
232    type Output = ReqStrand;
233    fn neg(self) -> ReqStrand {
234        match self {
235            ReqStrand::Forward => ReqStrand::Reverse,
236            ReqStrand::Reverse => ReqStrand::Forward,
237        }
238    }
239}
240
241/// Strand information for annotations that definitively have no
242/// strand information.
243#[derive(Debug, Clone, Hash, PartialEq, Eq, Ord, PartialOrd, Copy)]
244#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
245pub enum NoStrand {
246    Unknown,
247}
248
249impl Neg for NoStrand {
250    type Output = NoStrand;
251    fn neg(self) -> NoStrand {
252        match self {
253            NoStrand::Unknown => NoStrand::Unknown,
254        }
255    }
256}
257
258impl Same for NoStrand {
259    fn same(&self, _s1: &Self) -> bool {
260        true
261    }
262}
263
264impl FromStr for NoStrand {
265    type Err = StrandError;
266    fn from_str(s: &str) -> Result<Self, Self::Err> {
267        match s {
268            "" => Ok(NoStrand::Unknown),
269            _ => Err(StrandError::ParseError),
270        }
271    }
272}
273
274impl Display for NoStrand {
275    fn fmt(&self, _f: &mut Formatter) -> fmt::Result {
276        Ok(())
277    }
278}
279
280/// Equality-like operator for comparing strand information. Unknown
281/// strands are not equal, but they are the "same" as other unknown
282/// strands.
283pub trait Same {
284    /// Indicate when two strands are the "same" -- two
285    /// unknown/unspecified strands are the "same" but are not equal.
286    fn same(&self, other: &Self) -> bool;
287}
288
289impl<T> Same for Option<T>
290where
291    T: Same,
292{
293    fn same(&self, s1: &Self) -> bool {
294        match (self, s1) {
295            (&Option::None, &Option::None) => true,
296            (&Option::Some(ref x), &Option::Some(ref x1)) => x.same(x1),
297            (_, _) => false,
298        }
299    }
300}
301
302#[derive(Error, Debug)]
303pub enum StrandError {
304    #[error("invalid character for strand conversion: {0:?}: can not be converted to a Strand")]
305    InvalidChar(char),
306    #[error("error parsing strand")]
307    ParseError,
308}
309
310#[cfg(test)]
311mod tests {
312    use super::*;
313
314    #[test]
315    fn test_strand() {
316        assert_eq!(Strand::from_char(&'+').unwrap(), Strand::Forward);
317        assert_eq!(Strand::from_char(&'-').unwrap(), Strand::Reverse);
318        assert!(Strand::from_char(&'.').unwrap().is_unknown());
319        assert!(Strand::from_char(&'o').is_err());
320        assert_eq!(Strand::Forward.strand_symbol(), "+");
321        assert_eq!(Strand::Reverse.strand_symbol(), "-");
322        assert_eq!(Strand::Unknown.strand_symbol(), ".");
323    }
324
325    #[test]
326    fn test_req_strand() {
327        assert_eq!(ReqStrand::from_char(&'+').unwrap(), ReqStrand::Forward);
328        assert_eq!(ReqStrand::from_char(&'-').unwrap(), ReqStrand::Reverse);
329        assert!(ReqStrand::from_char(&'o').is_err());
330        assert_eq!(ReqStrand::Forward.strand_symbol(), "+");
331        assert_eq!(ReqStrand::Reverse.strand_symbol(), "-");
332    }
333}