bio_forge/ops/
error.rs

1//! Shared error types returned by the high-level operations modules.
2//!
3//! Every variant maps to a specific biological invariant violation (missing templates,
4//! failed alignments, protonation issues, etc.) so downstream callers can display precise
5//! remediation guidance.
6
7use thiserror::Error;
8
9/// Error conditions surfaced by the operations layer.
10#[derive(Debug, Error)]
11pub enum Error {
12    /// Internal template lookup failed for a standard residue.
13    #[error("internal template not found for standard residue '{res_name}'")]
14    MissingInternalTemplate { res_name: String },
15
16    /// Least-squares alignment between residue coordinates and template failed.
17    #[error("alignment failed for residue '{res_name}' ({res_id}): {reason}")]
18    AlignmentFailed {
19        res_name: String,
20        res_id: i32,
21        reason: String,
22    },
23
24    /// Hydrogen addition could not proceed because a required anchor atom is absent.
25    #[error(
26        "cannot add hydrogens to residue '{res_name}' ({res_id}): missing anchor atom '{atom_name}'"
27    )]
28    IncompleteResidueForHydro {
29        res_name: String,
30        res_id: i32,
31        atom_name: String,
32    },
33
34    /// Simulation bounding box cannot accommodate requested solvent parameters.
35    #[error("simulation box is too small for the requested solvation parameters")]
36    BoxTooSmall,
37
38    /// Replacement of waters with ions could not reach the requested charge balance.
39    #[error("ionization failed: {details}")]
40    IonizationFailed { details: String },
41
42    /// No heterogen template was available for the residue.
43    #[error("missing hetero topology template for residue '{res_name}'")]
44    MissingHeteroTemplate { res_name: String },
45
46    /// Residue is missing a heavy atom mandated by the template topology.
47    #[error(
48        "topology mismatch: Residue '{res_name}' ({res_id}) is missing atom '{atom_name}' required by template"
49    )]
50    TopologyAtomMissing {
51        res_name: String,
52        res_id: i32,
53        atom_name: String,
54    },
55}
56
57impl Error {
58    /// Helper for constructing an [`Error::AlignmentFailed`] variant.
59    ///
60    /// # Arguments
61    ///
62    /// * `res_name` - Residue name to include in the message.
63    /// * `res_id` - PDB/author residue identifier.
64    /// * `reason` - Free-form explanation of the failure.
65    pub fn alignment_failed(
66        res_name: impl Into<String>,
67        res_id: i32,
68        reason: impl Into<String>,
69    ) -> Self {
70        Self::AlignmentFailed {
71            res_name: res_name.into(),
72            res_id,
73            reason: reason.into(),
74        }
75    }
76
77    /// Helper for constructing an [`Error::IncompleteResidueForHydro`] variant.
78    ///
79    /// # Arguments
80    ///
81    /// * `res_name` - Residue label.
82    /// * `res_id` - Residue identifier.
83    /// * `atom_name` - Anchor atom that is missing.
84    pub fn incomplete_for_hydro(
85        res_name: impl Into<String>,
86        res_id: i32,
87        atom_name: impl Into<String>,
88    ) -> Self {
89        Self::IncompleteResidueForHydro {
90            res_name: res_name.into(),
91            res_id,
92            atom_name: atom_name.into(),
93        }
94    }
95
96    /// Helper for constructing an [`Error::TopologyAtomMissing`] variant.
97    ///
98    /// # Arguments
99    ///
100    /// * `res_name` - Residue name as reported to the user.
101    /// * `res_id` - Residue identifier.
102    /// * `atom_name` - The absent atom that triggered the mismatch.
103    pub fn topology_atom_missing(
104        res_name: impl Into<String>,
105        res_id: i32,
106        atom_name: impl Into<String>,
107    ) -> Self {
108        Self::TopologyAtomMissing {
109            res_name: res_name.into(),
110            res_id,
111            atom_name: atom_name.into(),
112        }
113    }
114}