Skip to main content

use_compound/
lib.rs

1#![forbid(unsafe_code)]
2#![allow(clippy::module_name_repetitions)]
3#![doc = include_str!("../README.md")]
4
5//! Chemical compound identity primitives.
6
7mod 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}