Skip to main content

use_stoichiometry/
reaction_entry.rs

1use std::fmt;
2
3use use_chemical_formula::ChemicalFormula;
4
5use crate::{
6    ReactionSide, StoichiometricCoefficient, StoichiometricTerm, StoichiometryValidationError,
7};
8
9/// A stoichiometric entry on one side of a reaction.
10#[derive(Clone, Debug, Eq, PartialEq)]
11pub struct ReactionEntry {
12    term: StoichiometricTerm,
13    side: ReactionSide,
14}
15
16impl ReactionEntry {
17    /// Creates a reaction entry.
18    ///
19    /// # Errors
20    ///
21    /// Returns [`StoichiometryValidationError::ZeroCoefficient`] if the coefficient is
22    /// structurally invalid.
23    pub fn new(
24        coefficient: StoichiometricCoefficient,
25        formula: ChemicalFormula,
26        side: ReactionSide,
27    ) -> Result<Self, StoichiometryValidationError> {
28        Ok(Self {
29            term: StoichiometricTerm::new(coefficient, formula)?,
30            side,
31        })
32    }
33
34    /// Creates a reaction entry from a raw coefficient value.
35    ///
36    /// # Errors
37    ///
38    /// Returns [`StoichiometryValidationError::ZeroCoefficient`] when `coefficient` is zero.
39    pub fn from_value(
40        coefficient: u32,
41        formula: ChemicalFormula,
42        side: ReactionSide,
43    ) -> Result<Self, StoichiometryValidationError> {
44        Self::new(StoichiometricCoefficient::new(coefficient)?, formula, side)
45    }
46
47    /// Returns the coefficient.
48    #[must_use]
49    pub const fn coefficient(&self) -> StoichiometricCoefficient {
50        self.term.coefficient()
51    }
52
53    /// Returns the formula.
54    #[must_use]
55    pub const fn formula(&self) -> &ChemicalFormula {
56        self.term.formula()
57    }
58
59    /// Returns the reaction side.
60    #[must_use]
61    pub const fn side(&self) -> ReactionSide {
62        self.side
63    }
64
65    /// Returns the stoichiometric term.
66    #[must_use]
67    pub const fn term(&self) -> &StoichiometricTerm {
68        &self.term
69    }
70
71    /// Consumes the entry and returns its parts.
72    #[must_use]
73    pub fn into_parts(self) -> (StoichiometricCoefficient, ChemicalFormula, ReactionSide) {
74        let (coefficient, formula) = self.term.into_parts();
75        (coefficient, formula, self.side)
76    }
77}
78
79impl fmt::Display for ReactionEntry {
80    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
81        write!(formatter, "{}", self.term)
82    }
83}
84
85/// A reactant-side reaction entry.
86#[derive(Clone, Debug, Eq, PartialEq)]
87pub struct ReactantEntry(ReactionEntry);
88
89impl ReactantEntry {
90    /// Creates a reactant entry.
91    ///
92    /// # Errors
93    ///
94    /// Returns [`StoichiometryValidationError::ZeroCoefficient`] if the coefficient is
95    /// structurally invalid.
96    pub fn new(
97        coefficient: StoichiometricCoefficient,
98        formula: ChemicalFormula,
99    ) -> Result<Self, StoichiometryValidationError> {
100        Ok(Self(ReactionEntry::new(
101            coefficient,
102            formula,
103            ReactionSide::Reactant,
104        )?))
105    }
106
107    /// Creates a reactant wrapper from a reaction entry.
108    ///
109    /// # Errors
110    ///
111    /// Returns [`StoichiometryValidationError::ExpectedReactant`] when the entry is not a
112    /// reactant.
113    pub fn from_entry(entry: ReactionEntry) -> Result<Self, StoichiometryValidationError> {
114        if entry.side().is_reactant() {
115            Ok(Self(entry))
116        } else {
117            Err(StoichiometryValidationError::ExpectedReactant)
118        }
119    }
120
121    /// Returns the wrapped reaction entry.
122    #[must_use]
123    pub const fn as_entry(&self) -> &ReactionEntry {
124        &self.0
125    }
126
127    /// Consumes the wrapper and returns the reaction entry.
128    #[must_use]
129    pub fn into_entry(self) -> ReactionEntry {
130        self.0
131    }
132}
133
134impl fmt::Display for ReactantEntry {
135    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
136        write!(formatter, "{}", self.0)
137    }
138}
139
140/// A product-side reaction entry.
141#[derive(Clone, Debug, Eq, PartialEq)]
142pub struct ProductEntry(ReactionEntry);
143
144impl ProductEntry {
145    /// Creates a product entry.
146    ///
147    /// # Errors
148    ///
149    /// Returns [`StoichiometryValidationError::ZeroCoefficient`] if the coefficient is
150    /// structurally invalid.
151    pub fn new(
152        coefficient: StoichiometricCoefficient,
153        formula: ChemicalFormula,
154    ) -> Result<Self, StoichiometryValidationError> {
155        Ok(Self(ReactionEntry::new(
156            coefficient,
157            formula,
158            ReactionSide::Product,
159        )?))
160    }
161
162    /// Creates a product wrapper from a reaction entry.
163    ///
164    /// # Errors
165    ///
166    /// Returns [`StoichiometryValidationError::ExpectedProduct`] when the entry is not a
167    /// product.
168    pub fn from_entry(entry: ReactionEntry) -> Result<Self, StoichiometryValidationError> {
169        if entry.side().is_product() {
170            Ok(Self(entry))
171        } else {
172            Err(StoichiometryValidationError::ExpectedProduct)
173        }
174    }
175
176    /// Returns the wrapped reaction entry.
177    #[must_use]
178    pub const fn as_entry(&self) -> &ReactionEntry {
179        &self.0
180    }
181
182    /// Consumes the wrapper and returns the reaction entry.
183    #[must_use]
184    pub fn into_entry(self) -> ReactionEntry {
185        self.0
186    }
187}
188
189impl fmt::Display for ProductEntry {
190    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
191        write!(formatter, "{}", self.0)
192    }
193}