1pub mod errors;
18pub mod parsing_utils;
19pub mod symbols;
20
21use errors::ChemStringErrors;
22use parsing_utils::parse_string;
23
24pub struct ChemString(Vec<String>);
28
29impl ChemString {
30 pub fn parse(s: impl Into<String>) -> Result<Self, ChemStringErrors> {
34 let s: String = s.into();
35 if s.is_empty() {
36 return Err(ChemStringErrors::EmptyString);
37 }
38 let s = s.split_whitespace().collect::<String>().to_lowercase();
39
40 if s.chars().any(|c| !c.is_ascii_alphabetic()) {
41 return Err(ChemStringErrors::InvalidCharacter);
42 }
43
44 let result = parse_string(s);
45
46 Ok(ChemString(result))
47 }
48
49 pub fn results(&self) -> Vec<String> {
52 self.0.clone()
53 }
54
55 pub fn count(&self) -> usize {
57 self.0.len()
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 #[test]
66 fn valid_chem_string_with_one_possible_result_is_parsed() {
67 let chem_string = "seal";
68
69 let result = ChemString::parse(chem_string).unwrap();
70
71 assert_eq!(result.0, vec!["Se Al"]);
72 }
73
74 #[test]
75 fn valid_chem_string_with_multiple_possible_results_is_parsed() {
76 let chem_string = "bichon";
77 let mut possible_results = ["Bi C H O N", "B I C H O N", "Bi C Ho N", "B I C Ho N"];
78
79 let mut result = ChemString::parse(chem_string).unwrap();
80 possible_results.sort();
81 result.0.sort();
82
83 assert_eq!(result.0, possible_results);
84 }
85
86 #[test]
87 fn invalid_chem_strings_are_not_parsed() {
88 let invalid_chem_strings = vec![
89 "",
90 "123",
91 "bichon123",
92 "bichon 123",
93 "?#!$@",
94 "bichon??",
95 "bichon ??",
96 ];
97
98 for invalid_chem_string in invalid_chem_strings {
99 let result = ChemString::parse(invalid_chem_string);
100
101 assert!(result.is_err());
102 }
103 }
104}