bio_forge/io/error.rs
1//! Canonical error type for all structure and template IO operations.
2//!
3//! This module wraps parser, serializer, and filesystem failures into a single
4//! `Error` enum that higher-level operations can bubble up or convert into
5//! user-facing diagnostics with uniform wording.
6
7use std::fmt;
8use std::path::PathBuf;
9use thiserror::Error;
10
11/// Errors that can occur while reading or writing biomolecular data.
12///
13/// The enum captures I/O failures, structured parser issues, unknown residues, and
14/// integrity mismatches so callers can inspect the variant and react accordingly.
15#[derive(Debug, Error)]
16pub enum Error {
17 /// Wrapper around operating-system level I/O failures.
18 ///
19 /// Includes both filesystem and stream sources, optionally carrying the file path for
20 /// richer error messages.
21 #[error(
22 "I/O error for {path_desc}: {source}",
23 path_desc = PathDisplay(path)
24 )]
25 Io {
26 /// Path to the file involved in the failed operation, if any.
27 path: Option<PathBuf>,
28 /// Underlying error emitted by the standard library.
29 #[source]
30 source: std::io::Error,
31 },
32
33 /// Indicates that an input line could not be parsed into the expected record.
34 ///
35 /// Exposes the textual format, source path, failing line number, and an explanatory
36 /// detail string to assist with debugging malformed files.
37 #[error(
38 "failed to parse {format} {path_desc}: {details} (line {line_number})",
39 path_desc = PathDisplay(path)
40 )]
41 Parse {
42 /// Name of the textual format (e.g., `"PDB"`, `"mmCIF"`).
43 format: &'static str,
44 /// Path to the offending file, if known.
45 path: Option<PathBuf>,
46 /// One-based line number where parsing failed.
47 line_number: usize,
48 /// Human-readable description of what went wrong.
49 details: String,
50 },
51
52 /// Raised when an atom record references a residue not present in template libraries.
53 #[error(
54 "unknown standard residue name '{name}' in {path_desc} could not be resolved",
55 path_desc = PathDisplay(path)
56 )]
57 UnknownStandardResidue { name: String, path: Option<PathBuf> },
58
59 /// Reports logical inconsistencies such as mismatched atom counts or invalid records.
60 #[error(
61 "inconsistent data in {format} {path_desc}: {details}",
62 path_desc = PathDisplay(path)
63 )]
64 InconsistentData {
65 /// Name of the textual format being processed.
66 format: &'static str,
67 /// Related file path when available.
68 path: Option<PathBuf>,
69 /// Summary of the detected inconsistency.
70 details: String,
71 },
72}
73
74impl Error {
75 /// Constructs an [`Error::Io`] variant from a standard I/O error.
76 ///
77 /// # Arguments
78 ///
79 /// * `source` - The original `std::io::Error` emitted by the OS or runtime.
80 /// * `path` - Optional file path associated with the operation.
81 ///
82 /// # Returns
83 ///
84 /// A ready-to-use `Error` that preserves the source error for chaining.
85 pub fn from_io(source: std::io::Error, path: Option<PathBuf>) -> Self {
86 Self::Io { path, source }
87 }
88
89 /// Builds a [`Error::Parse`] variant with consistent messaging.
90 ///
91 /// # Arguments
92 ///
93 /// * `format` - Name of the textual format being parsed.
94 /// * `path` - Optional path pointing to the input file.
95 /// * `line_number` - Line where the failure occurred (1-indexed).
96 /// * `details` - Additional context about the parsing problem.
97 ///
98 /// # Returns
99 ///
100 /// An `Error::Parse` carrying the supplied metadata.
101 pub fn parse(
102 format: &'static str,
103 path: Option<PathBuf>,
104 line_number: usize,
105 details: impl Into<String>,
106 ) -> Self {
107 Self::Parse {
108 format,
109 path,
110 line_number,
111 details: details.into(),
112 }
113 }
114
115 /// Generates an [`Error::UnknownStandardResidue`] for unresolved template names.
116 ///
117 /// # Arguments
118 ///
119 /// * `name` - Residue identifier that failed to look up.
120 /// * `path` - Optional path to the source file.
121 ///
122 /// # Returns
123 ///
124 /// An error variant signalling the missing residue definition.
125 pub fn unknown_standard_residue(name: impl Into<String>, path: Option<PathBuf>) -> Self {
126 Self::UnknownStandardResidue {
127 name: name.into(),
128 path,
129 }
130 }
131
132 /// Creates an [`Error::InconsistentData`] describing logical mismatches.
133 ///
134 /// # Arguments
135 ///
136 /// * `format` - Name of the textual format being processed.
137 /// * `path` - Optional file path, if applicable.
138 /// * `details` - Explanation of the inconsistency.
139 ///
140 /// # Returns
141 ///
142 /// An error variant pointing to structural or semantic discrepancies.
143 pub fn inconsistent_data(
144 format: &'static str,
145 path: Option<PathBuf>,
146 details: impl Into<String>,
147 ) -> Self {
148 Self::InconsistentData {
149 format,
150 path,
151 details: details.into(),
152 }
153 }
154}
155
156/// Lightweight formatter for optional paths used in error messages.
157///
158/// When a path is present it prints `file '<path>'`; otherwise it emits `stream source` so
159/// error messages remain grammatically consistent.
160struct PathDisplay<'a>(&'a Option<PathBuf>);
161
162impl<'a> fmt::Display for PathDisplay<'a> {
163 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164 match self.0 {
165 Some(p) => write!(f, "file '{}'", p.display()),
166 None => write!(f, "stream source"),
167 }
168 }
169}