Skip to main content

use_molecule/
molecule_builder.rs

1use use_chemical_formula::ChemicalFormula;
2
3use crate::{
4    AtomConnection, MolecularAtom, Molecule, MoleculeCharge, MoleculeKind, MoleculeValidationError,
5};
6
7/// Builder for assembling a molecule with optional explicit atom data.
8#[derive(Clone, Debug, Default, Eq, PartialEq)]
9pub struct MoleculeBuilder {
10    name: String,
11    formula: Option<ChemicalFormula>,
12    atoms: Vec<MolecularAtom>,
13    connections: Vec<AtomConnection>,
14    charge: MoleculeCharge,
15    kinds: Vec<MoleculeKind>,
16}
17
18impl MoleculeBuilder {
19    /// Creates a molecule builder.
20    #[must_use]
21    pub fn new(name: &str) -> Self {
22        Self {
23            name: name.to_owned(),
24            formula: None,
25            atoms: Vec::new(),
26            connections: Vec::new(),
27            charge: MoleculeCharge::NEUTRAL,
28            kinds: Vec::new(),
29        }
30    }
31
32    /// Sets the molecule formula.
33    #[must_use]
34    pub fn formula(mut self, formula: ChemicalFormula) -> Self {
35        self.formula = Some(formula);
36        self
37    }
38
39    /// Adds an explicit atom.
40    #[must_use]
41    pub fn atom(mut self, atom: MolecularAtom) -> Self {
42        self.atoms.push(atom);
43        self
44    }
45
46    /// Adds an atom connection to validate during build.
47    #[must_use]
48    pub fn connection(mut self, connection: AtomConnection) -> Self {
49        self.connections.push(connection);
50        self
51    }
52
53    /// Sets the formal molecule charge.
54    #[must_use]
55    pub const fn charge(mut self, charge: MoleculeCharge) -> Self {
56        self.charge = charge;
57        self
58    }
59
60    /// Adds a kind label if it is not already present.
61    #[must_use]
62    pub fn kind(mut self, kind: MoleculeKind) -> Self {
63        if !self.kinds.contains(&kind) {
64            self.kinds.push(kind);
65        }
66        self
67    }
68
69    /// Builds the molecule.
70    ///
71    /// # Errors
72    ///
73    /// Returns [`MoleculeValidationError::EmptyName`] for an empty name,
74    /// [`MoleculeValidationError::MissingFormula`] if no formula was assigned, or
75    /// [`MoleculeValidationError::InvalidConnectionIndex`] when a connection references an atom
76    /// index outside the explicit atom list.
77    pub fn build(self) -> Result<Molecule, MoleculeValidationError> {
78        let Some(formula) = self.formula else {
79            return Err(MoleculeValidationError::MissingFormula);
80        };
81
82        let mut molecule = Molecule::new(&self.name, formula)?.with_charge(self.charge);
83        for kind in self.kinds {
84            molecule = molecule.with_kind(kind);
85        }
86        for atom in self.atoms {
87            molecule = molecule.with_atom(atom);
88        }
89        for connection in self.connections {
90            molecule = molecule.try_with_connection(connection)?;
91        }
92
93        Ok(molecule)
94    }
95}