Skip to main content

use_oxidation_state/
lib.rs

1#![forbid(unsafe_code)]
2#![allow(clippy::module_name_repetitions)]
3#![doc = include_str!("../README.md")]
4
5//! Oxidation-state primitives.
6
7mod element_oxidation_state;
8mod error;
9mod formula_oxidation_state;
10mod oxidation_magnitude;
11mod oxidation_sign;
12mod oxidation_state;
13mod oxidation_state_assignment;
14mod oxidation_state_set;
15mod roman;
16
17pub use element_oxidation_state::ElementOxidationState;
18pub use error::OxidationStateValidationError;
19pub use formula_oxidation_state::FormulaOxidationState;
20pub use oxidation_magnitude::OxidationMagnitude;
21pub use oxidation_sign::OxidationSign;
22pub use oxidation_state::OxidationState;
23pub use oxidation_state_assignment::OxidationStateAssignment;
24pub use oxidation_state_set::OxidationStateSet;
25pub use roman::{RomanOxidationState, roman_numeral};
26
27#[cfg(test)]
28mod tests {
29    use super::{
30        ElementOxidationState, FormulaOxidationState, OxidationMagnitude, OxidationSign,
31        OxidationState, OxidationStateAssignment, OxidationStateSet, OxidationStateValidationError,
32        RomanOxidationState, roman_numeral,
33    };
34
35    fn positive(magnitude: u8) -> OxidationState {
36        OxidationState::positive(magnitude).expect("positive state should be valid")
37    }
38
39    fn negative(magnitude: u8) -> OxidationState {
40        OxidationState::negative(magnitude).expect("negative state should be valid")
41    }
42
43    #[test]
44    fn creates_positive_oxidation_states() {
45        let hydrogen = positive(1);
46        let iron = positive(3);
47
48        assert_eq!(hydrogen.sign(), OxidationSign::Positive);
49        assert_eq!(hydrogen.magnitude_value(), 1);
50        assert_eq!(hydrogen.signed_value(), 1);
51        assert!(hydrogen.is_positive());
52        assert_eq!(hydrogen.to_string(), "+1");
53        assert_eq!(iron.to_string(), "+3");
54    }
55
56    #[test]
57    fn creates_negative_oxidation_states() {
58        let oxygen = negative(2);
59        let chloride = negative(1);
60
61        assert_eq!(oxygen.sign(), OxidationSign::Negative);
62        assert_eq!(oxygen.magnitude_value(), 2);
63        assert_eq!(oxygen.signed_value(), -2);
64        assert!(oxygen.is_negative());
65        assert_eq!(oxygen.to_string(), "-2");
66        assert_eq!(chloride.to_string(), "-1");
67    }
68
69    #[test]
70    fn creates_zero_oxidation_state() {
71        let elemental = OxidationState::zero();
72
73        assert_eq!(elemental.sign(), OxidationSign::Zero);
74        assert_eq!(elemental.magnitude(), OxidationMagnitude::ZERO);
75        assert_eq!(elemental.signed_value(), 0);
76        assert!(elemental.is_zero());
77        assert_eq!(elemental.to_string(), "0");
78        assert_eq!(elemental.to_roman(), None);
79    }
80
81    #[test]
82    fn rejects_invalid_state_combinations() {
83        assert_eq!(
84            OxidationState::positive(0),
85            Err(OxidationStateValidationError::ZeroSignedMagnitude)
86        );
87        assert_eq!(
88            OxidationState::negative(0),
89            Err(OxidationStateValidationError::ZeroSignedMagnitude)
90        );
91        assert_eq!(
92            OxidationState::new(
93                OxidationSign::Zero,
94                OxidationMagnitude::new(1).expect("magnitude should be valid"),
95            ),
96            Err(OxidationStateValidationError::NonZeroZeroMagnitude)
97        );
98        assert_eq!(
99            OxidationMagnitude::new(9),
100            Err(OxidationStateValidationError::MagnitudeAboveMaximum {
101                magnitude: 9,
102                maximum: OxidationMagnitude::MAX,
103            })
104        );
105    }
106
107    #[test]
108    fn converts_to_roman_numerals() {
109        assert_eq!(roman_numeral(0), None);
110        assert_eq!(roman_numeral(1), Some("I"));
111        assert_eq!(roman_numeral(2), Some("II"));
112        assert_eq!(roman_numeral(3), Some("III"));
113        assert_eq!(roman_numeral(4), Some("IV"));
114        assert_eq!(roman_numeral(5), Some("V"));
115        assert_eq!(roman_numeral(6), Some("VI"));
116        assert_eq!(roman_numeral(7), Some("VII"));
117        assert_eq!(roman_numeral(8), Some("VIII"));
118        assert_eq!(roman_numeral(9), None);
119        assert_eq!(positive(2).to_roman(), Some(String::from("II")));
120        assert_eq!(negative(2).to_roman(), Some(String::from("II")));
121        assert_eq!(
122            RomanOxidationState::from_state(positive(7)).map(|roman| roman.to_string()),
123            Some(String::from("VII"))
124        );
125    }
126
127    #[test]
128    fn assigns_element_oxidation_states() {
129        let iron_two = ElementOxidationState::new("Fe", positive(2))
130            .expect("element assignment should be valid");
131        let iron_three = ElementOxidationState::new("Fe", positive(3))
132            .expect("element assignment should be valid");
133        let copper_one = ElementOxidationState::new("Cu", positive(1))
134            .expect("element assignment should be valid");
135        let copper_two = ElementOxidationState::new("Cu", positive(2))
136            .expect("element assignment should be valid");
137        let manganese_seven = ElementOxidationState::new("Mn", positive(7))
138            .expect("element assignment should be valid");
139        let oxygen = ElementOxidationState::new("O", negative(2))
140            .expect("element assignment should be valid");
141        let elemental_iron = ElementOxidationState::new("Fe", OxidationState::zero())
142            .expect("element assignment should be valid");
143
144        assert_eq!(iron_two.to_string(), "Fe(II)");
145        assert_eq!(iron_three.to_string(), "Fe(III)");
146        assert_eq!(copper_one.to_string(), "Cu(I)");
147        assert_eq!(copper_two.to_string(), "Cu(II)");
148        assert_eq!(manganese_seven.to_string(), "Mn(VII)");
149        assert_eq!(oxygen.to_string(), "O(-2)");
150        assert_eq!(elemental_iron.to_string(), "Fe(0)");
151        assert_eq!(iron_three.element_symbol(), "Fe");
152        assert_eq!(iron_three.state(), positive(3));
153    }
154
155    #[test]
156    fn rejects_invalid_element_symbols() {
157        assert_eq!(
158            ElementOxidationState::new(" ", positive(1)),
159            Err(OxidationStateValidationError::EmptyElementSymbol)
160        );
161        assert_eq!(
162            ElementOxidationState::new("iron", positive(2)),
163            Err(OxidationStateValidationError::InvalidElementSymbol(
164                String::from("iron")
165            ))
166        );
167        assert_eq!(
168            ElementOxidationState::new("FE", positive(2)),
169            Err(OxidationStateValidationError::InvalidElementSymbol(
170                String::from("FE")
171            ))
172        );
173    }
174
175    #[test]
176    fn displays_generic_assignments() {
177        let hydrogen =
178            OxidationStateAssignment::new("H", positive(1)).expect("assignment should be valid");
179        let oxygen =
180            OxidationStateAssignment::new("O", negative(2)).expect("assignment should be valid");
181        let sodium =
182            OxidationStateAssignment::new("Na", positive(1)).expect("assignment should be valid");
183        let chlorine =
184            OxidationStateAssignment::new("Cl", negative(1)).expect("assignment should be valid");
185
186        assert_eq!(hydrogen.to_string(), "H: +1");
187        assert_eq!(oxygen.to_string(), "O: -2");
188        assert_eq!(sodium.to_string(), "Na: +1");
189        assert_eq!(chlorine.to_string(), "Cl: -1");
190        assert_eq!(
191            OxidationStateAssignment::new(" ", positive(1)),
192            Err(OxidationStateValidationError::EmptyAssignmentLabel)
193        );
194    }
195
196    #[test]
197    fn stores_formula_context_entries() {
198        let mut states = OxidationStateSet::new();
199
200        assert!(states.is_empty());
201        assert_eq!(
202            states.insert(
203                OxidationStateAssignment::new("Fe", positive(2))
204                    .expect("assignment should be valid")
205            ),
206            None
207        );
208        assert_eq!(
209            states.insert(
210                OxidationStateAssignment::new("O", negative(2))
211                    .expect("assignment should be valid")
212            ),
213            None
214        );
215        let replaced = states.insert(
216            OxidationStateAssignment::new("Fe", positive(3)).expect("assignment should be valid"),
217        );
218
219        assert_eq!(states.len(), 2);
220        assert!(states.contains_label("Fe"));
221        assert_eq!(
222            states.get("Fe").map(OxidationStateAssignment::state),
223            Some(positive(3))
224        );
225        assert_eq!(
226            replaced.map(|assignment| assignment.state()),
227            Some(positive(2))
228        );
229        assert_eq!(states.to_string(), "Fe: +3, O: -2");
230
231        let formula =
232            FormulaOxidationState::new("Fe2O3", states).expect("formula context should be valid");
233
234        assert_eq!(formula.formula_label(), "Fe2O3");
235        assert_eq!(formula.states().len(), 2);
236        assert_eq!(formula.to_string(), "Fe2O3 [Fe: +3, O: -2]");
237        assert_eq!(
238            FormulaOxidationState::new(" ", OxidationStateSet::new()),
239            Err(OxidationStateValidationError::EmptyFormulaLabel)
240        );
241    }
242
243    #[test]
244    fn builds_sets_from_assignments() {
245        let assignments = [
246            OxidationStateAssignment::new("S", positive(4)).expect("assignment should be valid"),
247            OxidationStateAssignment::new("O", negative(2)).expect("assignment should be valid"),
248            OxidationStateAssignment::new("S", positive(6)).expect("assignment should be valid"),
249        ];
250        let states = OxidationStateSet::from_assignments(assignments);
251
252        assert_eq!(states.len(), 2);
253        assert_eq!(
254            states.get("S").map(OxidationStateAssignment::state),
255            Some(positive(6))
256        );
257        assert_eq!(states.iter().count(), 2);
258        assert_eq!(states.into_iter().count(), 2);
259    }
260}