1#![forbid(unsafe_code)]
2#![allow(clippy::module_name_repetitions)]
3#![doc = include_str!("../README.md")]
4
5mod compound;
8mod compound_formula;
9mod compound_identifier;
10mod compound_kind;
11mod compound_name;
12mod error;
13mod registry;
14
15pub use compound::Compound;
16pub use compound_formula::{CompoundFormula, EmpiricalFormula, MolecularFormula};
17pub use compound_identifier::CompoundIdentifier;
18pub use compound_kind::CompoundKind;
19pub use compound_name::{CommonName, CompoundName, SystematicName};
20pub use error::CompoundValidationError;
21pub use registry::CompoundRegistry;
22
23#[cfg(test)]
24mod tests {
25 use use_chemical_formula::ChemicalFormula;
26
27 use super::{
28 CommonName, Compound, CompoundFormula, CompoundIdentifier, CompoundKind, CompoundName,
29 CompoundRegistry, CompoundValidationError, EmpiricalFormula, MolecularFormula,
30 SystematicName,
31 };
32
33 fn formula(input: &str) -> ChemicalFormula {
34 ChemicalFormula::parse(input).expect("test formula should parse")
35 }
36
37 #[test]
38 fn creates_simple_compound() {
39 let water = Compound::new("water", formula("H2O")).expect("compound should be valid");
40
41 assert_eq!(water.name().as_str(), "water");
42 assert_eq!(water.formula().to_string(), "H2O");
43 assert!(water.common_name().is_none());
44 assert!(water.systematic_name().is_none());
45 assert!(water.kinds().is_empty());
46 assert!(water.identifiers().is_empty());
47 }
48
49 #[test]
50 fn validates_compound_names() {
51 assert_eq!(
52 CompoundName::new(" "),
53 Err(CompoundValidationError::EmptyName)
54 );
55 assert_eq!(
56 Compound::new("", formula("H2O")).map(|compound| compound.name().to_string()),
57 Err(CompoundValidationError::EmptyName)
58 );
59 assert_eq!(
60 CompoundName::new(" water ").map(|name| name.to_string()),
61 Ok(String::from("water"))
62 );
63 }
64
65 #[test]
66 fn handles_common_and_systematic_names() {
67 let calcium_hydroxide = Compound::new("calcium hydroxide", formula("Ca(OH)2"))
68 .expect("compound should be valid")
69 .try_with_common_name("slaked lime")
70 .expect("common name should be valid")
71 .try_with_systematic_name("calcium dihydroxide")
72 .expect("systematic name should be valid");
73
74 assert_eq!(
75 calcium_hydroxide.common_name().map(CommonName::as_str),
76 Some("slaked lime")
77 );
78 assert_eq!(
79 calcium_hydroxide
80 .systematic_name()
81 .map(SystematicName::as_str),
82 Some("calcium dihydroxide")
83 );
84 assert_eq!(
85 CommonName::new(""),
86 Err(CompoundValidationError::EmptyCommonName)
87 );
88 assert_eq!(
89 SystematicName::new(" "),
90 Err(CompoundValidationError::EmptySystematicName)
91 );
92 }
93
94 #[test]
95 fn wraps_empirical_and_molecular_formulas() {
96 let empirical = EmpiricalFormula::new(formula("CH2O"));
97 let molecular = MolecularFormula::new(formula("C6H12O6"));
98 let glucose = Compound::new("glucose", formula("C6H12O6"))
99 .expect("compound should be valid")
100 .with_empirical_formula(empirical.clone())
101 .with_molecular_formula(molecular.clone());
102
103 assert_eq!(empirical.to_string(), "CH2O");
104 assert_eq!(molecular.to_string(), "C6H12O6");
105 assert_eq!(
106 glucose.empirical_formula().map(ToString::to_string),
107 Some(String::from("CH2O"))
108 );
109 assert_eq!(
110 glucose.molecular_formula().map(ToString::to_string),
111 Some(String::from("C6H12O6"))
112 );
113 assert_eq!(
114 CompoundFormula::new(formula("H2O"))
115 .as_formula()
116 .to_string(),
117 "H2O"
118 );
119 }
120
121 #[test]
122 fn assigns_compound_kinds() {
123 let sodium_chloride = Compound::new("sodium chloride", formula("NaCl"))
124 .expect("compound should be valid")
125 .with_kind(CompoundKind::Ionic)
126 .with_kind(CompoundKind::Salt)
127 .with_kind(CompoundKind::Salt);
128
129 assert_eq!(
130 sodium_chloride.kinds(),
131 &[CompoundKind::Ionic, CompoundKind::Salt]
132 );
133 assert_eq!(CompoundKind::Coordination.to_string(), "coordination");
134 }
135
136 #[test]
137 fn handles_compound_identifiers() {
138 let cas = CompoundIdentifier::cas_number("7732-18-5").expect("CAS should be valid");
139 let custom = CompoundIdentifier::custom("local", "water")
140 .expect("custom identifier should be valid");
141 let water = Compound::new("water", formula("H2O"))
142 .expect("compound should be valid")
143 .try_with_identifier(cas.clone())
144 .expect("identifier should be valid")
145 .try_with_identifier(custom.clone())
146 .expect("identifier should be valid");
147
148 assert_eq!(cas.registry(), &CompoundRegistry::CasNumber);
149 assert_eq!(cas.value(), "7732-18-5");
150 assert_eq!(custom.registry().to_string(), "local");
151 assert_eq!(custom.value(), "water");
152 assert_eq!(water.identifiers(), &[cas, custom]);
153 }
154
155 #[test]
156 fn displays_compounds() {
157 let carbon_dioxide =
158 Compound::new("carbon dioxide", formula("CO2")).expect("compound should be valid");
159
160 assert_eq!(carbon_dioxide.to_string(), "carbon dioxide (CO2)");
161 }
162
163 #[test]
164 fn rejects_invalid_identifier_values() {
165 assert_eq!(
166 CompoundIdentifier::pub_chem_cid(""),
167 Err(CompoundValidationError::EmptyIdentifierValue)
168 );
169 assert_eq!(
170 CompoundIdentifier::custom("", "value"),
171 Err(CompoundValidationError::EmptyIdentifierNamespace)
172 );
173 assert_eq!(
174 CompoundIdentifier::custom("local", " "),
175 Err(CompoundValidationError::EmptyIdentifierValue)
176 );
177 }
178}