atglib/models/
frame.rs

1use std::fmt;
2use std::ops::Add;
3use std::str::FromStr;
4
5use serde::{Deserialize, Serialize};
6
7/// Frame indicates the reading frame offset of an Exon
8///
9/// It is based on GTF nomenclature:
10/// - 0 indicates that the feature begins with a whole codon at the 5' most base.
11/// - 1 means that there is one extra base (the third base of a codon) before the first whole codon
12/// - 2 means that there are two extra bases (the second and third bases of the codon) before the first codon.
13///
14/// *Important*: The reading-frame offset is handled differently in RefGene
15///
16/// # Examples
17/// ```rust
18/// use std::str::FromStr;
19/// use atglib::models::Frame;
20///
21/// let frame_str = Frame::from_str("1").unwrap();
22/// let frame_int = Frame::from_int(1).unwrap();
23///
24/// assert_eq!(frame_str, frame_int);
25/// ```
26#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
27pub enum Frame {
28    None, // used for non-coding exons. Converts to `-1` or `.`.
29    Zero, // e.g. --ATG....
30    One,  // e.g. --XATG....
31    Two,  // e.g. --XXATG....
32}
33
34impl fmt::Display for Frame {
35    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
36        write!(
37            f,
38            "{}",
39            match self {
40                Frame::None => ".",
41                Frame::Zero => "0",
42                Frame::One => "1",
43                Frame::Two => "2",
44            }
45        )
46    }
47}
48
49impl Frame {
50    /// Returns a Frame based on the integer frame offset
51    pub fn from_int(s: u32) -> Result<Self, String> {
52        match s % 3 {
53            0 => Ok(Frame::Zero),
54            1 => Ok(Frame::One),
55            2 => Ok(Frame::Two),
56            _ => Err(format!("invalid frame indicator {}", s)),
57        }
58    }
59
60    /// Returns `Frame` from a GTF value
61    ///
62    /// This method is the same as [`Frame::from_str`] but is present
63    /// for consistency reasons
64    ///
65    /// # Examples
66    ///
67    /// ```rust
68    /// use atglib::models::Frame;
69    /// let frame = Frame::from_gtf("1").unwrap();
70    ///
71    /// assert_eq!(frame, Frame::One);
72    /// ```
73    pub fn from_gtf(s: &str) -> Result<Self, String> {
74        Frame::from_str(s)
75    }
76
77    /// Returns `Frame` from a RefGene value
78    ///
79    /// RefGene uses a different specification than GTF when it comes
80    /// to specifying 1 or 2.
81    /// RefGene specifies how many nucleotides of the first codon are
82    /// present on the previous exon.
83    ///
84    /// # Examples
85    ///
86    /// ```rust
87    /// use atglib::models::Frame;
88    /// let frame = Frame::from_refgene("1").unwrap();
89    ///
90    /// assert_eq!(frame, Frame::Two);
91    /// ```
92    pub fn from_refgene(s: &str) -> Result<Self, String> {
93        match s {
94            "-1" => Ok(Frame::None),
95            "." => Ok(Frame::None), // TODO: Not sure if its really needed
96            "0" => Ok(Frame::Zero),
97            "1" => Ok(Frame::Two), // yes, that's correct
98            "2" => Ok(Frame::One), // this as well
99            _ => Err(format!("invalid frame indicator {}", s)),
100        }
101    }
102
103    /// Returns the GTF String for `Frame`
104    ///
105    /// This method is the same as the fmt::Display trait but is present
106    /// for consistency reasons
107    ///
108    /// # Examples
109    ///
110    /// ```rust
111    /// use atglib::models::Frame;
112    /// let frame = Frame::One.to_gtf();
113    ///
114    /// assert_eq!(frame, "1".to_string());
115    /// ```
116    pub fn to_gtf(&self) -> String {
117        self.to_string()
118    }
119
120    /// Returns the RefGene String for `Frame`
121    ///
122    /// RefGene uses a different specification than GTF when it comes
123    /// to specifying 1 or 2.
124    /// RefGene specifies how many nucleotides of the first codon are
125    /// present on the previous exon.
126    ///
127    /// # Examples
128    ///
129    /// ```rust
130    /// use atglib::models::Frame;
131    /// let frame = Frame::One.to_refgene();
132    ///
133    /// assert_eq!(frame, "2".to_string());
134    /// ```
135    pub fn to_refgene(&self) -> String {
136        match self {
137            Frame::Zero => "0",
138            Frame::One => "2", // yes, that's correct
139            Frame::Two => "1", // this as well
140            _ => "-1",
141        }
142        .to_string()
143    }
144
145    /// Returns `true` if the Frame is 0, 1 or 2
146    pub fn is_known(&self) -> bool {
147        matches!(self, Frame::Zero | Frame::One | Frame::Two)
148    }
149
150    fn to_int(self) -> Result<u32, String> {
151        match self {
152            Frame::Zero => Ok(0),
153            Frame::One => Ok(1),
154            Frame::Two => Ok(2),
155            _ => Err("unspecified frame cannot be converted to int".to_string()),
156        }
157    }
158}
159
160impl Add for Frame {
161    type Output = Result<Self, String>;
162
163    /// Calculates the next resulting Frame offset
164    fn add(self, other: Self) -> Result<Self, String> {
165        match (self.to_int(), other.to_int()) {
166            (Ok(x), Ok(y)) => Self::from_int((x + y) % 3),
167            (Ok(_), _) => Ok(self),
168            (_, Ok(_)) => Ok(other),
169            _ => Err("unable to add two unspecified frames".to_string()),
170        }
171    }
172}
173
174impl FromStr for Frame {
175    type Err = String;
176    /// Creates a [`Frame`] from a string (as in GTF format)
177    ///
178    /// Only accepts the following options:
179    /// - `-1`
180    /// - `0`
181    /// - `1`
182    /// - `2`
183    /// - `.`
184    fn from_str(s: &str) -> Result<Self, String> {
185        match s {
186            "-1" => Ok(Frame::None),
187            "0" => Ok(Frame::Zero),
188            "1" => Ok(Frame::One),
189            "2" => Ok(Frame::Two),
190            "." => Ok(Frame::None),
191            _ => Err(format!("invalid frame indicator {}", s)),
192        }
193    }
194}