Skip to main content

causal_hub/types/
error.rs

1use std::{panic::Location, sync::Arc};
2
3use thiserror::Error;
4
5/// The error kind type for this crate.
6#[derive(Error, Debug, Clone)]
7pub enum ErrorKind {
8    /// An error related to I/O operations.
9    #[error(transparent)]
10    Io(Arc<std::io::Error>),
11    /// An error related to CSV parsing.
12    #[error(transparent)]
13    Csv(Arc<csv::Error>),
14    /// An error related to JSON parsing.
15    #[error(transparent)]
16    Json(Arc<serde_json::Error>),
17    /// An error related to UTF-8 conversion.
18    #[error(transparent)]
19    Utf8(#[from] std::string::FromUtf8Error),
20    /// An error related to float parsing.
21    #[error(transparent)]
22    ParseFloat(#[from] std::num::ParseFloatError),
23    /// An error related to ndarray shape operations.
24    #[error(transparent)]
25    NdarrayShape(#[from] ndarray::ShapeError),
26    /// An error related to ndarray statistics.
27    #[error(transparent)]
28    NdarrayMinMax(#[from] ndarray_stats::errors::MinMaxError),
29    /// An error related to random distribution uniform sampling.
30    #[error(transparent)]
31    RandDistrUniform(#[from] rand_distr::uniform::Error),
32    /// An error related to linear algebra operations.
33    #[error("Linear Algebra error: {0}")]
34    Linalg(String),
35    /// An error related to probability calculations.
36    #[error("Probability error: {0}")]
37    Probability(String),
38    /// An error related to parsing.
39    #[error("Parsing error: {0}")]
40    Parsing(String),
41    /// An error related to missing data.
42    #[error("Missing data error: {0}")]
43    MissingData(String),
44    /// An error related to statistics.
45    #[error("Statistics error: {0}")]
46    Stats(String),
47    /// An error related to random distributions.
48    #[error("Random distribution error: {0}")]
49    RandDistr(String),
50    /// An error related to shape.
51    #[error("Shape error: {0}")]
52    Shape(String),
53    /// An error related to unreachable code.
54    #[error("Unreachable error: {0}")]
55    Unreachable(String),
56    /// An error related to lock poisoning.
57    #[error("Lock poisoning error: {0}")]
58    Poison(String),
59    /// Index is out of bounds.
60    #[error("Index `{0}` is out of bounds")]
61    IndexOutOfBounds(usize),
62    /// Labels must be unique.
63    #[error("Labels must be unique.")]
64    NonUniqueLabels,
65    /// An error indicating that a set cannot be empty.
66    #[error("Set {0} must not be empty")]
67    EmptySet(String),
68    /// An error indicating that two sets must be disjoint.
69    #[error("Sets {0} and {1} must be disjoint")]
70    SetsNotDisjoint(String, String),
71    /// An error indicating that one set must be a subset of another.
72    #[error("Set {0} must be a subset of set {1}")]
73    SubsetMismatch(String, String),
74    /// An error indicating that the graph must be a DAG.
75    #[error("Graph must be a DAG")]
76    NotADag,
77    /// An error indicating that a parameter is invalid.
78    #[error("Invalid parameter {0}: {1}")]
79    InvalidParameter(String, String),
80    /// An error indicating a conflict in prior knowledge.
81    #[error("Prior knowledge conflict: {0}")]
82    PriorKnowledgeConflict(String),
83    /// An error indicating that the labels of the graphs are incompatible.
84    #[error("Labels mismatch: {0} != {1}")]
85    LabelMismatch(String, String),
86    /// An error indicating that sufficient statistics are missing.
87    #[error("Missing sufficient statistics")]
88    MissingSufficientStatistics,
89    /// An error indicating that the log-likelihood is missing.
90    #[error("Missing log-likelihood")]
91    MissingLogLikelihood,
92    /// An error indicating that a CSV file is missing headers.
93    #[error("CSV file must have headers")]
94    MissingHeader,
95    /// An error indicating that the shape of the data is incompatible.
96    #[error("Incompatible shape: {0} != {1}")]
97    IncompatibleShape(String, String),
98    /// An error indicating that a state is missing.
99    #[error("State {0} not found")]
100    MissingState(String),
101    /// An error indicating that a label is missing.
102    #[error("Label {0} not found")]
103    MissingLabel(String),
104    /// An error indicating that a value is NaN.
105    #[error("Value is NaN")]
106    NanValue,
107    /// An error indicating that a value is missing.
108    #[error("Missing value at line {0}, column {1}")]
109    MissingValue(usize, usize),
110    /// An error indicating that an object construction failed.
111    #[error("Object construction failed: {0}")]
112    ConstructionError(String),
113    /// Other errors.
114    #[error(transparent)]
115    Other(Arc<Box<dyn std::error::Error + Send + Sync>>),
116}
117
118/// The error type for this crate.
119#[derive(Debug, Clone)]
120pub struct Error {
121    /// The error kind.
122    pub kind: ErrorKind,
123    /// The location of the error.
124    pub location: &'static Location<'static>,
125}
126
127impl std::fmt::Display for Error {
128    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129        write!(f, "{} at {}", self.kind, self.location)
130    }
131}
132
133impl std::error::Error for Error {
134    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
135        self.kind.source()
136    }
137}
138
139impl From<ErrorKind> for Error {
140    #[track_caller]
141    fn from(kind: ErrorKind) -> Self {
142        Self {
143            kind,
144            location: Location::caller(),
145        }
146    }
147}
148
149impl From<std::io::Error> for Error {
150    #[track_caller]
151    fn from(err: std::io::Error) -> Self {
152        ErrorKind::Io(Arc::new(err)).into()
153    }
154}
155
156impl From<csv::Error> for Error {
157    #[track_caller]
158    fn from(err: csv::Error) -> Self {
159        ErrorKind::Csv(Arc::new(err)).into()
160    }
161}
162
163impl From<serde_json::Error> for Error {
164    #[track_caller]
165    fn from(err: serde_json::Error) -> Self {
166        ErrorKind::Json(Arc::new(err)).into()
167    }
168}
169
170impl From<Box<dyn std::error::Error + Send + Sync>> for Error {
171    #[track_caller]
172    fn from(err: Box<dyn std::error::Error + Send + Sync>) -> Self {
173        ErrorKind::Other(Arc::new(err)).into()
174    }
175}
176
177impl From<std::string::FromUtf8Error> for Error {
178    #[track_caller]
179    fn from(err: std::string::FromUtf8Error) -> Self {
180        ErrorKind::Utf8(err).into()
181    }
182}
183
184impl From<std::num::ParseFloatError> for Error {
185    #[track_caller]
186    fn from(err: std::num::ParseFloatError) -> Self {
187        ErrorKind::ParseFloat(err).into()
188    }
189}
190
191impl From<ndarray::ShapeError> for Error {
192    #[track_caller]
193    fn from(err: ndarray::ShapeError) -> Self {
194        ErrorKind::NdarrayShape(err).into()
195    }
196}
197
198impl From<ndarray_stats::errors::MinMaxError> for Error {
199    #[track_caller]
200    fn from(err: ndarray_stats::errors::MinMaxError) -> Self {
201        ErrorKind::NdarrayMinMax(err).into()
202    }
203}
204
205impl From<rand_distr::uniform::Error> for Error {
206    #[track_caller]
207    fn from(err: rand_distr::uniform::Error) -> Self {
208        ErrorKind::RandDistrUniform(err).into()
209    }
210}
211
212/// A specialized [`Result`] type for this crate.
213pub type Result<T> = std::result::Result<T, Error>;
214
215impl serde::Serialize for Error {
216    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
217    where
218        S: serde::Serializer,
219    {
220        serializer.serialize_str(&self.to_string())
221    }
222}
223
224/// Helper to construct error with location.
225#[track_caller]
226pub fn err<T>(kind: ErrorKind) -> Result<T> {
227    Err(Error::from(kind))
228}
229
230// Backward compatibility constructors.
231impl Error {
232    /// An error related to linear algebra operations.
233    #[allow(non_snake_case)]
234    #[track_caller]
235    pub fn Linalg(stats: &str) -> Self {
236        ErrorKind::Linalg(stats.to_string()).into()
237    }
238
239    /// An error related to probability calculations.
240    #[allow(non_snake_case)]
241    #[track_caller]
242    pub fn Probability(stats: &str) -> Self {
243        ErrorKind::Probability(stats.to_string()).into()
244    }
245
246    /// An error related to parsing.
247    #[allow(non_snake_case)]
248    #[track_caller]
249    pub fn Parsing(stats: &str) -> Self {
250        ErrorKind::Parsing(stats.to_string()).into()
251    }
252
253    /// An error related to missing data.
254    #[allow(non_snake_case)]
255    #[track_caller]
256    pub fn MissingData(stats: &str) -> Self {
257        ErrorKind::MissingData(stats.to_string()).into()
258    }
259
260    /// An error related to statistics.
261    #[allow(non_snake_case)]
262    #[track_caller]
263    pub fn Stats(stats: &str) -> Self {
264        ErrorKind::Stats(stats.to_string()).into()
265    }
266
267    /// An error related to random distributions.
268    #[allow(non_snake_case)]
269    #[track_caller]
270    pub fn RandDistr(stats: &str) -> Self {
271        ErrorKind::RandDistr(stats.to_string()).into()
272    }
273
274    /// An error related to shape.
275    #[allow(non_snake_case)]
276    #[track_caller]
277    pub fn Shape(stats: &str) -> Self {
278        ErrorKind::Shape(stats.to_string()).into()
279    }
280
281    /// An error related to unreachable code.
282    #[allow(non_snake_case)]
283    #[track_caller]
284    pub fn Unreachable(stats: &str) -> Self {
285        ErrorKind::Unreachable(stats.to_string()).into()
286    }
287
288    /// An error related to lock poisoning.
289    #[allow(non_snake_case)]
290    #[track_caller]
291    pub fn Poison(stats: &str) -> Self {
292        ErrorKind::Poison(stats.to_string()).into()
293    }
294
295    /// Index is out of bounds.
296    #[allow(non_snake_case)]
297    #[track_caller]
298    pub fn IndexOutOfBounds(u: usize) -> Self {
299        ErrorKind::IndexOutOfBounds(u).into()
300    }
301
302    /// Labels must be unique.
303    #[allow(non_snake_case)]
304    #[track_caller]
305    pub fn NonUniqueLabels() -> Self {
306        ErrorKind::NonUniqueLabels.into()
307    }
308
309    /// An error indicating that a set cannot be empty.
310    #[allow(non_snake_case)]
311    #[track_caller]
312    pub fn EmptySet(stats: &str) -> Self {
313        ErrorKind::EmptySet(stats.to_string()).into()
314    }
315
316    /// An error indicating that two sets must be disjoint.
317    #[allow(non_snake_case)]
318    #[track_caller]
319    pub fn SetsNotDisjoint(s1: &str, s2: &str) -> Self {
320        ErrorKind::SetsNotDisjoint(s1.to_string(), s2.to_string()).into()
321    }
322
323    /// An error indicating that one set must be a subset of another.
324    #[allow(non_snake_case)]
325    #[track_caller]
326    pub fn SubsetMismatch(s1: &str, s2: &str) -> Self {
327        ErrorKind::SubsetMismatch(s1.to_string(), s2.to_string()).into()
328    }
329
330    /// An error indicating that the graph must be a DAG.
331    #[allow(non_snake_case)]
332    #[track_caller]
333    pub fn NotADag() -> Self {
334        ErrorKind::NotADag.into()
335    }
336
337    /// An error indicating that a parameter is invalid.
338    #[allow(non_snake_case)]
339    #[track_caller]
340    pub fn InvalidParameter(s1: &str, s2: &str) -> Self {
341        ErrorKind::InvalidParameter(s1.to_string(), s2.to_string()).into()
342    }
343
344    /// An error indicating a conflict in prior knowledge.
345    #[allow(non_snake_case)]
346    #[track_caller]
347    pub fn PriorKnowledgeConflict(stats: &str) -> Self {
348        ErrorKind::PriorKnowledgeConflict(stats.to_string()).into()
349    }
350
351    /// An error indicating that the labels of the graphs are incompatible.
352    #[allow(non_snake_case)]
353    #[track_caller]
354    pub fn LabelMismatch(s1: &str, s2: &str) -> Self {
355        ErrorKind::LabelMismatch(s1.to_string(), s2.to_string()).into()
356    }
357
358    /// An error indicating that sufficient statistics are missing.
359    #[allow(non_snake_case)]
360    #[track_caller]
361    pub fn MissingSufficientStatistics() -> Self {
362        ErrorKind::MissingSufficientStatistics.into()
363    }
364
365    /// An error indicating that the log-likelihood is missing.
366    #[allow(non_snake_case)]
367    #[track_caller]
368    pub fn MissingLogLikelihood() -> Self {
369        ErrorKind::MissingLogLikelihood.into()
370    }
371
372    /// An error indicating that a CSV file is missing headers.
373    #[allow(non_snake_case)]
374    #[track_caller]
375    pub fn MissingHeader() -> Self {
376        ErrorKind::MissingHeader.into()
377    }
378
379    /// An error indicating that the shape of the data is incompatible.
380    #[allow(non_snake_case)]
381    #[track_caller]
382    pub fn IncompatibleShape(s1: &str, s2: &str) -> Self {
383        ErrorKind::IncompatibleShape(s1.to_string(), s2.to_string()).into()
384    }
385
386    /// An error indicating that a state is missing.
387    #[allow(non_snake_case)]
388    #[track_caller]
389    pub fn MissingState(stats: &str) -> Self {
390        ErrorKind::MissingState(stats.to_string()).into()
391    }
392
393    /// An error indicating that a label is missing.
394    #[allow(non_snake_case)]
395    #[track_caller]
396    pub fn MissingLabel(stats: &str) -> Self {
397        ErrorKind::MissingLabel(stats.to_string()).into()
398    }
399
400    /// An error indicating that a value is NaN.
401    #[allow(non_snake_case)]
402    #[track_caller]
403    pub fn NanValue() -> Self {
404        ErrorKind::NanValue.into()
405    }
406
407    /// An error indicating that a value is missing.
408    #[allow(non_snake_case)]
409    #[track_caller]
410    pub fn MissingValue(u1: usize, u2: usize) -> Self {
411        ErrorKind::MissingValue(u1, u2).into()
412    }
413
414    /// An error indicating that an object construction failed.
415    #[allow(non_snake_case)]
416    #[track_caller]
417    pub fn ConstructionError(stats: &str) -> Self {
418        ErrorKind::ConstructionError(stats.to_string()).into()
419    }
420}
421
422// Additional compatibility constructors for transparent variants
423impl Error {
424    /// An error related to I/O operations.
425    #[allow(non_snake_case)]
426    #[track_caller]
427    pub fn Io(err: Arc<std::io::Error>) -> Self {
428        ErrorKind::Io(err).into()
429    }
430
431    /// An error related to CSV parsing.
432    #[allow(non_snake_case)]
433    #[track_caller]
434    pub fn Csv(err: Arc<csv::Error>) -> Self {
435        ErrorKind::Csv(err).into()
436    }
437
438    /// An error related to JSON parsing.
439    #[allow(non_snake_case)]
440    #[track_caller]
441    pub fn Json(err: Arc<serde_json::Error>) -> Self {
442        ErrorKind::Json(err).into()
443    }
444
445    /// An error related to UTF-8 conversion.
446    #[allow(non_snake_case)]
447    #[track_caller]
448    pub fn Utf8(err: std::string::FromUtf8Error) -> Self {
449        ErrorKind::Utf8(err).into()
450    }
451
452    /// An error related to float parsing.
453    #[allow(non_snake_case)]
454    #[track_caller]
455    pub fn ParseFloat(err: std::num::ParseFloatError) -> Self {
456        ErrorKind::ParseFloat(err).into()
457    }
458
459    /// An error related to ndarray shape operations.
460    #[allow(non_snake_case)]
461    #[track_caller]
462    pub fn NdarrayShape(err: ndarray::ShapeError) -> Self {
463        ErrorKind::NdarrayShape(err).into()
464    }
465
466    /// An error related to ndarray statistics.
467    #[allow(non_snake_case)]
468    #[track_caller]
469    pub fn NdarrayMinMax(err: ndarray_stats::errors::MinMaxError) -> Self {
470        ErrorKind::NdarrayMinMax(err).into()
471    }
472
473    /// An error related to random distribution uniform sampling.
474    #[allow(non_snake_case)]
475    #[track_caller]
476    pub fn RandDistrUniform(err: rand_distr::uniform::Error) -> Self {
477        ErrorKind::RandDistrUniform(err).into()
478    }
479
480    /// Other errors.
481    #[allow(non_snake_case)]
482    #[track_caller]
483    pub fn Other(err: Arc<Box<dyn std::error::Error + Send + Sync>>) -> Self {
484        ErrorKind::Other(err).into()
485    }
486}