1use std::{convert::TryFrom, str::FromStr};
16use thiserror::Error;
17
18pub trait Code {
48 fn single_letter(&self) -> char;
49 fn three_letters(&self) -> String;
50 fn write_single_letter(
51 &self,
52 writer: impl std::io::Write,
53 ) -> std::result::Result<(), std::io::Error>;
54 fn write_three_letters(
55 &self,
56 writer: impl std::io::Write,
57 ) -> std::result::Result<(), std::io::Error>;
58}
59
60#[derive(Error, Debug)]
61pub enum ErrorKind {
62 #[error("cannot parse amino acid from: `{0}`")]
63 ParseAminoAcidError(String),
64}
65
66macro_rules! aa {
67
68 ($($name:ident $one_letter_code:literal $full_name:literal $three_letters_code:literal),*) => {
69
70 #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone)]
80 pub enum AminoAcid {
81 $(
82 $name,
83 )*
84 }
85
86 impl ::std::fmt::Display for AminoAcid {
87 fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
88 match *self {
89 $(
90 Self::$name => write!(f, $full_name)
91 ),*
92 }
93 }
94 }
95
96 impl Code for AminoAcid {
97 fn single_letter(&self) -> char {
98 match *self {
99 $(
100 Self::$name => $one_letter_code
101 ),*
102 }
103
104 }
105
106 fn three_letters(&self) -> String {
107 match *self {
108 $(
109 Self::$name => String::from($three_letters_code),
110 )*
111 }
112 }
113
114 fn write_single_letter(&self, mut writer: impl std::io::Write) -> std::result::Result<(), std::io::Error> {
115 match *self {
116 $(
117 Self::$name => write!(writer, "{}", $one_letter_code)
118 ),*
119 }
120 }
121
122 fn write_three_letters(&self, mut writer: impl std::io::Write) -> std::result::Result<(), std::io::Error> {
123 match *self {
124 $(
125 Self::$name => write!(writer, $three_letters_code)
126 ),*
127 }
128 }
129 }
130
131 impl FromStr for AminoAcid {
132 type Err = ErrorKind;
133
134 fn from_str(s: &str) -> Result<Self, Self::Err> {
135 match s {
136 $(
137 $three_letters_code | $full_name => Ok(Self::$name),
138 )*
139 _ => Err(ErrorKind::ParseAminoAcidError(s.to_string())),
140 }
141 }
142 }
143
144 impl TryFrom<char> for AminoAcid {
145 type Error = ErrorKind;
146
147 fn try_from(value: char) -> Result<Self, Self::Error> {
148 match value {
149 $(
150 $one_letter_code => Ok(Self::$name),
151 )*
152 _ => Err(ErrorKind::ParseAminoAcidError(value.to_string())),
153 }
154 }
155 }
156 };
157}
158
159aa! {
160 Alanine 'A' "Alanine" "Ala",
161 Arginine 'R' "Arginine" "Arg",
162 Asparagine 'N' "Asparagine" "Asn",
163 AsparticAcid 'D' "Aspartic acid" "Asp",
164 Cysteine 'C' "Cysteine" "Cys",
165 GlutamicAcid 'E' "Glutamic acid" "Glu",
166 Glutamine 'Q' "Glutamine" "Gln",
167 Glycine 'G' "Glycine" "Gly",
168 Histidine 'H' "Histidine" "His",
169 Isoleucine 'I' "Isoleucine" "Ile",
170 Leucine 'L' "Leucine" "Leu",
171 Lysine 'K' "Lysine" "Lys",
172 Methionine 'M' "Methionine" "Met",
173 Phenylalanine 'F' "Phenylalanine" "Phe",
174 Proline 'P' "Proline" "Pro",
175 Serine 'S' "Serine" "Ser",
176 Threonine 'T' "Threonine" "Thr",
177 Tryptophan 'W' "Tryptophan" "Trp",
178 Tyrosine 'Y' "Tyrosine" "Tyr",
179 Valine 'V' "Valine" "Val"
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn test_from_string() {
188 let ala: AminoAcid = "Ala".parse().unwrap();
189
190 assert_eq!(ala, AminoAcid::Alanine);
191 }
192
193 #[test]
194 fn test_from_char() {
195 let tyr: AminoAcid = AminoAcid::try_from('Y').unwrap();
196 assert_eq!(tyr, AminoAcid::Tyrosine)
197 }
198}