Skip to main content

ryg_rans_rs_cli/
error.rs

1//! # Typed errors for the ryg-rans CLI
2//!
3//! Every error carries structured context (path, block index, declared length,
4//! limit, hash, etc.) without exposing untrusted payloads in error messages.
5//!
6//! Errors never include entire untrusted inputs.  They include identifying
7//! metadata sufficient for debugging and triage.
8
9// Re-export core error types for convenience.
10pub use ryg_rans_rs_core::{DecodeError, EncodeError, ModelError};
11
12use std::fmt;
13use std::path::PathBuf;
14use std::sync::Arc;
15
16// ---------------------------------------------------------------------------
17// AppError — top-level error enum
18// ---------------------------------------------------------------------------
19
20/// Top-level error type for all CLI operations.
21///
22/// Each variant carries structured context relevant to the error kind.
23/// The `Display` implementation produces user-facing messages.  The
24/// `AppError::kind()` method returns a stable string identifier for
25/// JSON serialization.
26#[derive(Debug, Clone)]
27pub enum AppError {
28    /// I/O operation failed.
29    Io(IoError),
30    /// Container or stream format violation.
31    Format(FormatError),
32    /// Model construction or validation error.
33    Model(ModelError),
34    /// Codec operation failed.
35    Codec(CodecError),
36    /// SHA-256 integrity check failure.
37    Integrity(IntegrityError),
38    /// Resource limit exceeded.
39    ResourceLimit(ResourceLimitError),
40    /// Requested backend not available.
41    Backend(BackendError),
42    /// Comparison mismatch.
43    Comparison(ComparisonError),
44    /// External oracle failure.
45    ExternalOracle(OracleError),
46    /// Internal invariant violation (bug).
47    InternalInvariant(InternalInvariantError),
48}
49
50impl fmt::Display for AppError {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        match self {
53            AppError::Io(e) => write!(f, "I/O error: {}", e),
54            AppError::Format(e) => write!(f, "format error: {}", e),
55            AppError::Model(e) => write!(f, "model error: {}", e),
56            AppError::Codec(e) => write!(f, "codec error: {}", e),
57            AppError::Integrity(e) => write!(f, "integrity error: {}", e),
58            AppError::ResourceLimit(e) => write!(f, "resource limit: {}", e),
59            AppError::Backend(e) => write!(f, "backend error: {}", e),
60            AppError::Comparison(e) => write!(f, "comparison error: {}", e),
61            AppError::ExternalOracle(e) => write!(f, "oracle error: {}", e),
62            AppError::InternalInvariant(e) => write!(f, "internal error: {}", e),
63        }
64    }
65}
66
67impl std::error::Error for AppError {
68    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
69        match self {
70            AppError::Io(e) => Some(e),
71            AppError::Format(e) => Some(e),
72            AppError::Model(e) => Some(e),
73            AppError::Codec(e) => Some(e),
74            AppError::Integrity(e) => Some(e),
75            AppError::ResourceLimit(e) => Some(e),
76            AppError::Backend(e) => Some(e),
77            AppError::Comparison(e) => Some(e),
78            AppError::ExternalOracle(e) => Some(e),
79            AppError::InternalInvariant(e) => Some(e),
80        }
81    }
82}
83
84impl AppError {
85    /// Stable string identifier for JSON serialization.
86    pub fn kind(&self) -> &'static str {
87        match self {
88            AppError::Io(_) => "io_error",
89            AppError::Format(_) => "format_error",
90            AppError::Model(_) => "model_error",
91            AppError::Codec(_) => "codec_error",
92            AppError::Integrity(_) => "integrity_failure",
93            AppError::ResourceLimit(_) => "resource_limit",
94            AppError::Backend(_) => "backend_unavailable",
95            AppError::Comparison(_) => "comparison_mismatch",
96            AppError::ExternalOracle(_) => "oracle_error",
97            AppError::InternalInvariant(_) => "internal_invariant",
98        }
99    }
100
101    /// Return the stable exit code for this error.
102    pub fn exit_code(&self) -> i32 {
103        match self {
104            AppError::Io(_) => 3,
105            AppError::Format(_) => 4,
106            AppError::Model(_) => 4,
107            AppError::Codec(_) => 4,
108            AppError::Integrity(_) => 5,
109            AppError::ResourceLimit(_) => 7,
110            AppError::Backend(_) => 9,
111            AppError::Comparison(_) => 8,
112            AppError::ExternalOracle(_) => 3,
113            AppError::InternalInvariant(_) => 10,
114        }
115    }
116
117    /// Convert to a JSON-compatible value.
118    pub fn to_json_value(&self) -> serde_json::Value {
119        serde_json::json!({
120            "kind": self.kind(),
121            "message": self.to_string(),
122        })
123    }
124}
125
126// ---------------------------------------------------------------------------
127// Concrete error types
128// ---------------------------------------------------------------------------
129
130#[derive(Debug, Clone)]
131pub struct IoError {
132    pub path: Option<PathBuf>,
133    pub detail: String,
134}
135
136impl fmt::Display for IoError {
137    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138        if let Some(path) = &self.path {
139            write!(f, "{} (path: {})", self.detail, path.display())
140        } else {
141            write!(f, "{}", self.detail)
142        }
143    }
144}
145
146impl std::error::Error for IoError {}
147
148#[derive(Debug, Clone)]
149pub struct FormatError {
150    pub detail: String,
151    pub block_index: Option<u64>,
152    pub offset: Option<u64>,
153}
154
155impl fmt::Display for FormatError {
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        write!(f, "{}", self.detail)?;
158        if let Some(block) = self.block_index {
159            write!(f, " (block {})", block)?;
160        }
161        if let Some(off) = self.offset {
162            write!(f, " (offset {})", off)?;
163        }
164        Ok(())
165    }
166}
167
168impl std::error::Error for FormatError {}
169
170#[derive(Debug, Clone)]
171pub struct CodecError {
172    pub detail: String,
173    pub codec_id: Option<u16>,
174}
175
176impl fmt::Display for CodecError {
177    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178        write!(f, "{}", self.detail)?;
179        if let Some(id) = self.codec_id {
180            write!(f, " (codec {})", id)?;
181        }
182        Ok(())
183    }
184}
185
186impl std::error::Error for CodecError {}
187
188#[derive(Debug, Clone)]
189pub struct IntegrityError {
190    pub detail: String,
191    pub block_index: Option<u64>,
192}
193
194impl fmt::Display for IntegrityError {
195    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196        write!(f, "{}", self.detail)?;
197        if let Some(block) = self.block_index {
198            write!(f, " (block {})", block)?;
199        }
200        Ok(())
201    }
202}
203
204impl std::error::Error for IntegrityError {}
205
206#[derive(Debug, Clone)]
207pub struct ResourceLimitError {
208    pub detail: String,
209    pub limit: u64,
210    pub requested: u64,
211}
212
213impl fmt::Display for ResourceLimitError {
214    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215        write!(
216            f,
217            "{} (limit: {}, requested: {})",
218            self.detail, self.limit, self.requested
219        )
220    }
221}
222
223impl std::error::Error for ResourceLimitError {}
224
225#[derive(Debug, Clone)]
226pub struct BackendError {
227    pub detail: String,
228    pub backend: String,
229}
230
231impl fmt::Display for BackendError {
232    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233        write!(f, "{} (backend: {})", self.detail, self.backend)
234    }
235}
236
237impl std::error::Error for BackendError {}
238
239#[derive(Debug, Clone)]
240pub struct ComparisonError {
241    pub detail: String,
242}
243
244impl fmt::Display for ComparisonError {
245    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
246        write!(f, "{}", self.detail)
247    }
248}
249
250impl std::error::Error for ComparisonError {}
251
252#[derive(Debug, Clone)]
253pub struct OracleError {
254    pub detail: String,
255    pub exit_code: Option<i32>,
256}
257
258impl fmt::Display for OracleError {
259    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260        write!(f, "{}", self.detail)?;
261        if let Some(code) = self.exit_code {
262            write!(f, " (exit: {})", code)?;
263        }
264        Ok(())
265    }
266}
267
268impl std::error::Error for OracleError {}
269
270#[derive(Debug, Clone)]
271pub struct InternalInvariantError {
272    pub detail: String,
273}
274
275impl fmt::Display for InternalInvariantError {
276    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277        write!(f, "internal invariant: {}", self.detail)
278    }
279}
280
281impl std::error::Error for InternalInvariantError {}
282
283// ---------------------------------------------------------------------------
284// From impls for core errors
285// ---------------------------------------------------------------------------
286
287impl From<std::io::Error> for AppError {
288    fn from(e: std::io::Error) -> Self {
289        AppError::Io(IoError {
290            path: None,
291            detail: e.to_string(),
292        })
293    }
294}
295
296impl From<ryg_rans_rs_core::ModelError> for AppError {
297    fn from(e: ryg_rans_rs_core::ModelError) -> Self {
298        AppError::Model(e)
299    }
300}
301
302impl From<ryg_rans_rs_core::EncodeError> for AppError {
303    fn from(_e: ryg_rans_rs_core::EncodeError) -> Self {
304        AppError::Codec(CodecError {
305            detail: "encode buffer too small".into(),
306            codec_id: None,
307        })
308    }
309}
310
311impl From<ryg_rans_rs_core::DecodeError> for AppError {
312    fn from(_e: ryg_rans_rs_core::DecodeError) -> Self {
313        AppError::Format(FormatError {
314            detail: "truncated stream".into(),
315            block_index: None,
316            offset: None,
317        })
318    }
319}
320
321impl From<String> for AppError {
322    fn from(s: String) -> Self {
323        AppError::InternalInvariant(InternalInvariantError { detail: s })
324    }
325}