Skip to main content

use_reaction/
reaction_equation.rs

1use std::fmt;
2
3use crate::{Product, Reactant, ReactionArrow, ReactionValidationError};
4
5/// A chemical reaction equation with reactants, products, and an arrow.
6#[derive(Clone, Debug, Eq, PartialEq)]
7pub struct ReactionEquation {
8    reactants: Vec<Reactant>,
9    products: Vec<Product>,
10    arrow: ReactionArrow,
11}
12
13impl ReactionEquation {
14    /// Creates an empty reaction equation with a forward arrow.
15    #[must_use]
16    pub const fn new() -> Self {
17        Self {
18            reactants: Vec::new(),
19            products: Vec::new(),
20            arrow: ReactionArrow::Forward,
21        }
22    }
23
24    /// Adds a reactant and returns the updated equation.
25    #[must_use]
26    pub fn with_reactant<T>(mut self, reactant: T) -> Self
27    where
28        T: Into<Reactant>,
29    {
30        self.reactants.push(reactant.into());
31        self
32    }
33
34    /// Adds a product and returns the updated equation.
35    #[must_use]
36    pub fn with_product<T>(mut self, product: T) -> Self
37    where
38        T: Into<Product>,
39    {
40        self.products.push(product.into());
41        self
42    }
43
44    /// Sets the reaction arrow.
45    #[must_use]
46    pub const fn with_arrow(mut self, arrow: ReactionArrow) -> Self {
47        self.arrow = arrow;
48        self
49    }
50
51    /// Returns reactants in insertion order.
52    #[must_use]
53    pub fn reactants(&self) -> &[Reactant] {
54        &self.reactants
55    }
56
57    /// Returns products in insertion order.
58    #[must_use]
59    pub fn products(&self) -> &[Product] {
60        &self.products
61    }
62
63    /// Returns the reaction arrow.
64    #[must_use]
65    pub const fn arrow(&self) -> ReactionArrow {
66        self.arrow
67    }
68
69    /// Validates that the equation has at least one reactant and one product.
70    ///
71    /// # Errors
72    ///
73    /// Returns [`ReactionValidationError::EmptyReaction`] when no reaction-side terms are present,
74    /// [`ReactionValidationError::MissingReactants`] when no reactants are present, or
75    /// [`ReactionValidationError::MissingProducts`] when no products are present.
76    pub fn validate(&self) -> Result<(), ReactionValidationError> {
77        match (self.reactants.is_empty(), self.products.is_empty()) {
78            (true, true) => Err(ReactionValidationError::EmptyReaction),
79            (true, false) => Err(ReactionValidationError::MissingReactants),
80            (false, true) => Err(ReactionValidationError::MissingProducts),
81            (false, false) => Ok(()),
82        }
83    }
84}
85
86impl Default for ReactionEquation {
87    fn default() -> Self {
88        Self::new()
89    }
90}
91
92impl fmt::Display for ReactionEquation {
93    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
94        write_side(formatter, &self.reactants)?;
95        write!(formatter, " {} ", self.arrow)?;
96        write_side(formatter, &self.products)
97    }
98}
99
100fn write_side<T>(formatter: &mut fmt::Formatter<'_>, terms: &[T]) -> fmt::Result
101where
102    T: fmt::Display,
103{
104    for (index, term) in terms.iter().enumerate() {
105        if index > 0 {
106            formatter.write_str(" + ")?;
107        }
108        write!(formatter, "{term}")?;
109    }
110
111    Ok(())
112}