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}