use_chemical_formula/
lib.rs1#![forbid(unsafe_code)]
2#![allow(clippy::module_name_repetitions)]
3#![doc = include_str!("../README.md")]
4
5mod chemical_formula;
8mod element_count;
9mod element_symbol;
10mod error;
11mod formula_group;
12mod formula_part;
13mod formula_term;
14mod hydrate_part;
15mod parse;
16
17pub use chemical_formula::ChemicalFormula;
18pub use element_count::{ElementCount, FormulaMultiplier};
19pub use element_symbol::{ElementSymbol, is_valid_element_symbol};
20pub use error::{FormulaParseError, FormulaValidationError};
21pub use formula_group::FormulaGroup;
22pub use formula_part::FormulaPart;
23pub use formula_term::FormulaTerm;
24pub use hydrate_part::HydratePart;
25
26#[cfg(test)]
27mod tests {
28 use super::{ChemicalFormula, ElementSymbol, FormulaParseError};
29
30 #[test]
31 fn parses_simple_binary_formulas() {
32 let water = ChemicalFormula::parse("H2O").expect("water formula should parse");
33 let carbon_dioxide = ChemicalFormula::parse("CO2").expect("carbon dioxide should parse");
34 let sodium_chloride = ChemicalFormula::parse("NaCl").expect("sodium chloride should parse");
35
36 assert_eq!(water.to_string(), "H2O");
37 assert_eq!(carbon_dioxide.to_string(), "CO2");
38 assert_eq!(sodium_chloride.to_string(), "NaCl");
39 assert_eq!(water.element_counts().get("H"), Some(&2));
40 assert_eq!(water.element_counts().get("O"), Some(&1));
41 }
42
43 #[test]
44 fn parses_one_and_two_letter_symbols() {
45 let iron_oxide = ChemicalFormula::parse("Fe2O3").expect("iron oxide should parse");
46 let counts = iron_oxide.element_counts();
47
48 assert_eq!(iron_oxide.to_string(), "Fe2O3");
49 assert_eq!(counts.get("Fe"), Some(&2));
50 assert_eq!(counts.get("O"), Some(&3));
51 assert_eq!(
52 ElementSymbol::new("Cl").map(|symbol| symbol.to_string()),
53 Ok(String::from("Cl"))
54 );
55 }
56
57 #[test]
58 fn parses_numeric_counts() {
59 let glucose = ChemicalFormula::parse("C6H12O6").expect("glucose formula should parse");
60 let ammonium_nitrate =
61 ChemicalFormula::parse("NH4NO3").expect("ammonium nitrate should parse");
62 let glucose_counts = glucose.element_counts();
63 let nitrate_counts = ammonium_nitrate.element_counts();
64
65 assert_eq!(glucose_counts.get("C"), Some(&6));
66 assert_eq!(glucose_counts.get("H"), Some(&12));
67 assert_eq!(glucose_counts.get("O"), Some(&6));
68 assert_eq!(nitrate_counts.get("N"), Some(&2));
69 assert_eq!(nitrate_counts.get("H"), Some(&4));
70 assert_eq!(nitrate_counts.get("O"), Some(&3));
71 }
72
73 #[test]
74 fn parses_groups_and_group_multipliers() {
75 let calcium_hydroxide =
76 ChemicalFormula::parse("Ca(OH)2").expect("calcium hydroxide should parse");
77 let aluminum_sulfate =
78 ChemicalFormula::parse("Al2(SO4)3").expect("aluminum sulfate should parse");
79 let hydroxide_counts = calcium_hydroxide.element_counts();
80 let sulfate_counts = aluminum_sulfate.element_counts();
81
82 assert_eq!(calcium_hydroxide.to_string(), "Ca(OH)2");
83 assert_eq!(hydroxide_counts.get("Ca"), Some(&1));
84 assert_eq!(hydroxide_counts.get("O"), Some(&2));
85 assert_eq!(hydroxide_counts.get("H"), Some(&2));
86 assert_eq!(aluminum_sulfate.to_string(), "Al2(SO4)3");
87 assert_eq!(sulfate_counts.get("Al"), Some(&2));
88 assert_eq!(sulfate_counts.get("S"), Some(&3));
89 assert_eq!(sulfate_counts.get("O"), Some(&12));
90 }
91
92 #[test]
93 fn parses_hydrate_formulas() {
94 let hydrate = ChemicalFormula::parse("CuSO4·5H2O").expect("hydrate should parse");
95 let ascii_dot = ChemicalFormula::parse("CuSO4.5H2O").expect("dot hydrate should parse");
96 let counts = hydrate.element_counts();
97
98 assert_eq!(hydrate.to_string(), "CuSO4·5H2O");
99 assert_eq!(ascii_dot.to_string(), "CuSO4·5H2O");
100 assert_eq!(counts.get("Cu"), Some(&1));
101 assert_eq!(counts.get("S"), Some(&1));
102 assert_eq!(counts.get("O"), Some(&9));
103 assert_eq!(counts.get("H"), Some(&10));
104 }
105
106 #[test]
107 fn parses_nested_groups() {
108 let formula = ChemicalFormula::parse("K4(ON(SO3)2)2").expect("nested formula should parse");
109 let counts = formula.element_counts();
110
111 assert_eq!(formula.to_string(), "K4(ON(SO3)2)2");
112 assert_eq!(counts.get("K"), Some(&4));
113 assert_eq!(counts.get("O"), Some(&14));
114 assert_eq!(counts.get("N"), Some(&2));
115 assert_eq!(counts.get("S"), Some(&4));
116 }
117
118 #[test]
119 fn rejects_invalid_formulas() {
120 assert_eq!(
121 ChemicalFormula::parse(""),
122 Err(FormulaParseError::EmptyFormula)
123 );
124 assert!(matches!(
125 ChemicalFormula::parse("h2O"),
126 Err(FormulaParseError::InvalidSymbol(_))
127 ));
128 assert_eq!(
129 ChemicalFormula::parse("Ca(OH2"),
130 Err(FormulaParseError::UnmatchedOpenGroup)
131 );
132 assert_eq!(
133 ChemicalFormula::parse("Ca)OH("),
134 Err(FormulaParseError::UnmatchedCloseGroup)
135 );
136 assert_eq!(
137 ChemicalFormula::parse("Ca()2"),
138 Err(FormulaParseError::EmptyGroup)
139 );
140 assert_eq!(
141 ChemicalFormula::parse("H0O"),
142 Err(FormulaParseError::ZeroCount)
143 );
144 assert_eq!(
145 ChemicalFormula::parse("Ca(OH)0"),
146 Err(FormulaParseError::ZeroMultiplier)
147 );
148 assert_eq!(
149 ChemicalFormula::parse("CuSO4·0H2O"),
150 Err(FormulaParseError::ZeroMultiplier)
151 );
152 assert_eq!(
153 ChemicalFormula::parse("CuSO4·"),
154 Err(FormulaParseError::TrailingSeparator)
155 );
156 assert!(matches!(
157 ChemicalFormula::parse("H 2O"),
158 Err(FormulaParseError::UnexpectedCharacter(' '))
159 ));
160 }
161}