1use rust_decimal::Decimal;
2use std::{borrow::Cow, error::Error, fmt, str::FromStr};
3
4#[derive(Debug, Clone)]
6pub enum AtomParseError<'a> {
7 InvalidCoordinate(Cow<'a, str>, rust_decimal::Error),
8 NoSymbol,
9 InvalidNumberOfCoordinates(usize),
10}
11
12impl<'a> AtomParseError<'a> {
13 pub fn into_owned(self) -> AtomParseError<'static> {
14 match self {
15 Self::InvalidCoordinate(input, err) => {
16 AtomParseError::InvalidCoordinate(Cow::Owned(input.into_owned()), err)
17 }
18 Self::NoSymbol => AtomParseError::NoSymbol,
19 Self::InvalidNumberOfCoordinates(num) => {
20 AtomParseError::InvalidNumberOfCoordinates(num)
21 }
22 }
23 }
24}
25
26impl<'a> fmt::Display for AtomParseError<'a> {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 match self {
29 Self::InvalidCoordinate(input, err) => write!(f, "Invalid coordinate '{input}': {err}"),
30 Self::NoSymbol => write!(f, "No symbol found"),
31 Self::InvalidNumberOfCoordinates(num) => {
32 write!(f, "Invalid number of coordinates. Found {num}, expected 3")
33 }
34 }
35 }
36}
37
38impl Error for AtomParseError<'static> {}
39
40#[derive(Debug, Clone, Default, PartialEq, Eq)]
42pub struct Atom<'a> {
43 pub symbol: Cow<'a, str>,
44 pub x: Decimal,
45 pub y: Decimal,
46 pub z: Decimal,
47}
48
49impl<'a> Atom<'a> {
50 pub fn parse(string: &'a str) -> Result<Self, AtomParseError> {
51 let mut parts = string.split_whitespace();
52 let symbol = parts
53 .next()
54 .map(Cow::Borrowed)
55 .ok_or(AtomParseError::NoSymbol)?;
56 let mut coordinates: [Decimal; 3] = Default::default();
57 coordinates
58 .iter_mut()
59 .enumerate()
60 .try_for_each(|(i, coord)| {
61 let part = parts
62 .next()
63 .ok_or(AtomParseError::InvalidNumberOfCoordinates(i))?;
64 *coord = Decimal::from_str_exact(part)
65 .map_err(|e| AtomParseError::InvalidCoordinate(Cow::Borrowed(part), e))?;
66 Ok(())
67 })?;
68 let remaining = parts.count();
69 if remaining > 0 {
70 return Err(AtomParseError::InvalidNumberOfCoordinates(3 + remaining));
71 }
72 Ok(Self {
73 symbol,
74 x: coordinates[0],
75 y: coordinates[1],
76 z: coordinates[2],
77 })
78 }
79
80 pub fn into_owned(self) -> Atom<'static> {
81 Atom {
82 symbol: Cow::Owned(self.symbol.into_owned()),
83 ..self
84 }
85 }
86
87 pub fn coordinates(&self) -> [Decimal; 3] {
88 [self.x, self.y, self.z]
89 }
90}
91
92impl<'a> fmt::Display for Atom<'a> {
93 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94 write!(f, "{} {} {} {}", self.symbol, self.x, self.y, self.z)
95 }
96}
97
98impl FromStr for Atom<'static> {
99 type Err = AtomParseError<'static>;
100 fn from_str(s: &str) -> Result<Self, Self::Err> {
101 Atom::parse(s)
102 .map(|res| res.into_owned())
103 .map_err(|err| err.into_owned())
104 }
105}