Skip to main content

use_chemical_formula/
element_symbol.rs

1use std::fmt;
2
3use crate::FormulaValidationError;
4
5/// A validated chemical element symbol shape.
6#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
7pub struct ElementSymbol(String);
8
9impl ElementSymbol {
10    /// Creates an element symbol after basic shape validation.
11    ///
12    /// # Errors
13    ///
14    /// Returns [`FormulaValidationError::InvalidSymbol`] when the input is not an ASCII
15    /// uppercase letter followed by zero or one ASCII lowercase letter.
16    pub fn new(symbol: &str) -> Result<Self, FormulaValidationError> {
17        if is_valid_element_symbol(symbol) {
18            Ok(Self(symbol.to_owned()))
19        } else {
20            Err(FormulaValidationError::InvalidSymbol(symbol.to_owned()))
21        }
22    }
23
24    /// Returns the symbol text.
25    #[must_use]
26    pub fn as_str(&self) -> &str {
27        &self.0
28    }
29
30    /// Consumes the symbol and returns the owned text.
31    #[must_use]
32    pub fn into_string(self) -> String {
33        self.0
34    }
35}
36
37impl AsRef<str> for ElementSymbol {
38    fn as_ref(&self) -> &str {
39        self.as_str()
40    }
41}
42
43impl TryFrom<&str> for ElementSymbol {
44    type Error = FormulaValidationError;
45
46    fn try_from(value: &str) -> Result<Self, Self::Error> {
47        Self::new(value)
48    }
49}
50
51impl fmt::Display for ElementSymbol {
52    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
53        formatter.write_str(self.as_str())
54    }
55}
56
57/// Returns `true` when the value has a basic chemical element-symbol shape.
58#[must_use]
59pub fn is_valid_element_symbol(symbol: &str) -> bool {
60    let mut characters = symbol.chars();
61    let Some(first) = characters.next() else {
62        return false;
63    };
64
65    if !first.is_ascii_uppercase() {
66        return false;
67    }
68
69    match characters.next() {
70        None => true,
71        Some(second) if second.is_ascii_lowercase() => characters.next().is_none(),
72        Some(_) => false,
73    }
74}