elements_rs 0.2.3

A comprehensive library for chemical elements and their isotopes with rich metadata
Documentation
//! Submodule implementing the `FromStr` trait for the `Element` enum.

use core::str::FromStr;

impl FromStr for crate::Element {
    type Err = crate::errors::Error;

    /// Parses an element from its symbol string.
    ///
    /// Parses element symbols into Element variants.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use elements_rs::Element;
    ///
    /// let hydrogen: Element = "H".parse().unwrap();
    /// assert_eq!(hydrogen, Element::H);
    ///
    /// let oxygen: Element = "O".parse().unwrap();
    /// assert_eq!(oxygen, Element::O);
    ///
    /// let carbon: Element = "C".parse().unwrap();
    /// assert_eq!(carbon, Element::C);
    /// ```
    #[inline]
    #[allow(clippy::too_many_lines)]
    fn from_str(value: &str) -> Result<Self, Self::Err> {
        Ok(match value {
            "H" => Self::H,
            "He" => Self::He,
            "Li" => Self::Li,
            "Be" => Self::Be,
            "B" => Self::B,
            "C" => Self::C,
            "N" => Self::N,
            "O" => Self::O,
            "F" => Self::F,
            "Ne" => Self::Ne,
            "Na" => Self::Na,
            "Mg" => Self::Mg,
            "Al" => Self::Al,
            "Si" => Self::Si,
            "P" => Self::P,
            "S" => Self::S,
            "Cl" => Self::Cl,
            "Ar" => Self::Ar,
            "K" => Self::K,
            "Ca" => Self::Ca,
            "Sc" => Self::Sc,
            "Ti" => Self::Ti,
            "V" => Self::V,
            "Cr" => Self::Cr,
            "Mn" => Self::Mn,
            "Fe" => Self::Fe,
            "Co" => Self::Co,
            "Ni" => Self::Ni,
            "Cu" => Self::Cu,
            "Zn" => Self::Zn,
            "Ga" => Self::Ga,
            "Ge" => Self::Ge,
            "As" => Self::As,
            "Se" => Self::Se,
            "Br" => Self::Br,
            "Kr" => Self::Kr,
            "Rb" => Self::Rb,
            "Sr" => Self::Sr,
            "Y" => Self::Y,
            "Zr" => Self::Zr,
            "Nb" => Self::Nb,
            "Mo" => Self::Mo,
            "Tc" => Self::Tc,
            "Ru" => Self::Ru,
            "Rh" => Self::Rh,
            "Pd" => Self::Pd,
            "Ag" => Self::Ag,
            "Cd" => Self::Cd,
            "In" => Self::In,
            "Sn" => Self::Sn,
            "Sb" => Self::Sb,
            "Te" => Self::Te,
            "I" => Self::I,
            "Xe" => Self::Xe,
            "Cs" => Self::Cs,
            "Ba" => Self::Ba,
            "La" => Self::La,
            "Ce" => Self::Ce,
            "Pr" => Self::Pr,
            "Nd" => Self::Nd,
            "Pm" => Self::Pm,
            "Sm" => Self::Sm,
            "Eu" => Self::Eu,
            "Gd" => Self::Gd,
            "Tb" => Self::Tb,
            "Dy" => Self::Dy,
            "Ho" => Self::Ho,
            "Er" => Self::Er,
            "Tm" => Self::Tm,
            "Yb" => Self::Yb,
            "Lu" => Self::Lu,
            "Hf" => Self::Hf,
            "Ta" => Self::Ta,
            "W" => Self::W,
            "Re" => Self::Re,
            "Os" => Self::Os,
            "Ir" => Self::Ir,
            "Pt" => Self::Pt,
            "Au" => Self::Au,
            "Hg" => Self::Hg,
            "Tl" => Self::Tl,
            "Pb" => Self::Pb,
            "Bi" => Self::Bi,
            "Po" => Self::Po,
            "At" => Self::At,
            "Rn" => Self::Rn,
            "Fr" => Self::Fr,
            "Ra" => Self::Ra,
            "Ac" => Self::Ac,
            "Th" => Self::Th,
            "Pa" => Self::Pa,
            "U" => Self::U,
            "Np" => Self::Np,
            "Pu" => Self::Pu,
            "Am" => Self::Am,
            "Cm" => Self::Cm,
            "Bk" => Self::Bk,
            "Cf" => Self::Cf,
            "Es" => Self::Es,
            "Fm" => Self::Fm,
            "Md" => Self::Md,
            "No" => Self::No,
            "Lr" => Self::Lr,
            "Rf" => Self::Rf,
            "Db" => Self::Db,
            "Sg" => Self::Sg,
            "Bh" => Self::Bh,
            "Hs" => Self::Hs,
            "Mt" => Self::Mt,
            "Ds" => Self::Ds,
            "Rg" => Self::Rg,
            "Cn" => Self::Cn,
            "Nh" => Self::Nh,
            "Fl" => Self::Fl,
            "Mc" => Self::Mc,
            "Lv" => Self::Lv,
            "Ts" => Self::Ts,
            "Og" => Self::Og,
            _ => {
                let mut characters: [char; 2] = [' '; 2];
                for (i, c) in value.chars().take(2).enumerate() {
                    characters[i] = c;
                }

                return Err(crate::errors::Error::Element(characters));
            }
        })
    }
}

#[cfg(test)]
mod tests {
    use strum::IntoEnumIterator;

    #[test]
    fn test_from_str() {
        for element in crate::Element::iter() {
            let symbol: &str = element.as_ref();

            // Test standard case parsing
            let parsed: Result<crate::Element, _> = symbol.parse();
            assert!(parsed.is_ok(), "Failed to parse standard symbol '{symbol}' for {element:?}");
            assert_eq!(
                parsed.unwrap(),
                element,
                "Parsed element doesn't match for symbol '{symbol}'",
            );
        }
    }

    #[test]
    fn test_invalid_symbols() {
        // Test various invalid inputs
        let invalid_symbols = alloc::vec![
            "",    // empty string
            "X",   // non-existent element
            "Zz",  // non-existent element
            "ABC", // too long
            "H2",  // valid element with number
            "He2", // valid element with number
            "123", // numbers only
            " ",   // space only
            "  ",  // multiple spaces
            "c",   // lowercase should not work
            "n",   // lowercase should not work
            "o",   // lowercase should not work
        ];

        for invalid in invalid_symbols {
            let result: Result<crate::Element, _> = invalid.parse();
            assert!(result.is_err(), "Should fail to parse invalid symbol '{invalid}'");
        }
    }

    #[test]
    fn test_roundtrip_via_string() {
        for element in crate::Element::iter() {
            let symbol: &str = element.as_ref();

            // Parse back to element
            let parsed: crate::Element = symbol.parse().unwrap();

            // Should be the same element
            assert_eq!(parsed, element, "Roundtrip failed for {element:?}");
        }
    }
}