Skip to main content

use_reaction/
reaction_condition.rs

1use std::fmt;
2
3use crate::{Catalyst, ReactionValidationError, Solvent};
4
5/// A lightweight reaction condition label.
6#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
7pub enum ReactionCondition {
8    /// Temperature descriptor.
9    Temperature(String),
10    /// Pressure descriptor.
11    Pressure(String),
12    /// Catalyst descriptor.
13    Catalyst(Catalyst),
14    /// Solvent descriptor.
15    Solvent(Solvent),
16    /// Heat label.
17    Heat,
18    /// Light label.
19    Light,
20    /// Electrolysis label.
21    Electrolysis,
22    /// Custom condition label and optional value.
23    Custom {
24        /// Custom condition label.
25        label: String,
26        /// Optional custom condition value.
27        value: Option<String>,
28    },
29}
30
31impl ReactionCondition {
32    /// Creates a temperature condition descriptor.
33    ///
34    /// # Errors
35    ///
36    /// Returns [`ReactionValidationError::EmptyTemperatureLabel`] when `label` is empty after
37    /// trimming.
38    pub fn temperature(label: &str) -> Result<Self, ReactionValidationError> {
39        let trimmed = label.trim();
40        if trimmed.is_empty() {
41            Err(ReactionValidationError::EmptyTemperatureLabel)
42        } else {
43            Ok(Self::Temperature(trimmed.to_owned()))
44        }
45    }
46
47    /// Creates a pressure condition descriptor.
48    ///
49    /// # Errors
50    ///
51    /// Returns [`ReactionValidationError::EmptyPressureLabel`] when `label` is empty after
52    /// trimming.
53    pub fn pressure(label: &str) -> Result<Self, ReactionValidationError> {
54        let trimmed = label.trim();
55        if trimmed.is_empty() {
56            Err(ReactionValidationError::EmptyPressureLabel)
57        } else {
58            Ok(Self::Pressure(trimmed.to_owned()))
59        }
60    }
61
62    /// Creates a catalyst condition descriptor.
63    ///
64    /// # Errors
65    ///
66    /// Returns [`ReactionValidationError::EmptyCatalystLabel`] when `label` is empty after
67    /// trimming.
68    pub fn catalyst(label: &str) -> Result<Self, ReactionValidationError> {
69        Ok(Self::Catalyst(Catalyst::new(label)?))
70    }
71
72    /// Creates a solvent condition descriptor.
73    ///
74    /// # Errors
75    ///
76    /// Returns [`ReactionValidationError::EmptySolventLabel`] when `label` is empty after
77    /// trimming.
78    pub fn solvent(label: &str) -> Result<Self, ReactionValidationError> {
79        Ok(Self::Solvent(Solvent::new(label)?))
80    }
81
82    /// Creates a custom condition descriptor.
83    ///
84    /// # Errors
85    ///
86    /// Returns [`ReactionValidationError::EmptyConditionLabel`] when `label` is empty after
87    /// trimming, or [`ReactionValidationError::EmptyConditionValue`] when `value` is present but
88    /// empty after trimming.
89    pub fn custom(label: &str, value: Option<&str>) -> Result<Self, ReactionValidationError> {
90        let label = label.trim();
91        if label.is_empty() {
92            return Err(ReactionValidationError::EmptyConditionLabel);
93        }
94
95        let value = value
96            .map(str::trim)
97            .map(|trimmed| {
98                if trimmed.is_empty() {
99                    Err(ReactionValidationError::EmptyConditionValue)
100                } else {
101                    Ok(trimmed.to_owned())
102                }
103            })
104            .transpose()?;
105
106        Ok(Self::Custom {
107            label: label.to_owned(),
108            value,
109        })
110    }
111
112    /// Validates this condition descriptor.
113    ///
114    /// # Errors
115    ///
116    /// Returns a [`ReactionValidationError`] when a directly constructed condition contains an
117    /// empty label or value.
118    pub fn validate(&self) -> Result<(), ReactionValidationError> {
119        match self {
120            Self::Temperature(label) if label.trim().is_empty() => {
121                Err(ReactionValidationError::EmptyTemperatureLabel)
122            },
123            Self::Pressure(label) if label.trim().is_empty() => {
124                Err(ReactionValidationError::EmptyPressureLabel)
125            },
126            Self::Custom { label, .. } if label.trim().is_empty() => {
127                Err(ReactionValidationError::EmptyConditionLabel)
128            },
129            Self::Custom {
130                value: Some(value), ..
131            } if value.trim().is_empty() => Err(ReactionValidationError::EmptyConditionValue),
132            _ => Ok(()),
133        }
134    }
135}
136
137impl From<Catalyst> for ReactionCondition {
138    fn from(catalyst: Catalyst) -> Self {
139        Self::Catalyst(catalyst)
140    }
141}
142
143impl From<Solvent> for ReactionCondition {
144    fn from(solvent: Solvent) -> Self {
145        Self::Solvent(solvent)
146    }
147}
148
149impl fmt::Display for ReactionCondition {
150    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
151        match self {
152            Self::Temperature(label) => write!(formatter, "temperature: {label}"),
153            Self::Pressure(label) => write!(formatter, "pressure: {label}"),
154            Self::Catalyst(catalyst) => write!(formatter, "catalyst: {catalyst}"),
155            Self::Solvent(solvent) => write!(formatter, "solvent: {solvent}"),
156            Self::Heat => formatter.write_str("heat"),
157            Self::Light => formatter.write_str("light"),
158            Self::Electrolysis => formatter.write_str("electrolysis"),
159            Self::Custom { label, value } => match value {
160                Some(value) => write!(formatter, "{label}: {value}"),
161                None => formatter.write_str(label),
162            },
163        }
164    }
165}