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}