Skip to main content

r2l_core/
error.rs

1use std::{error::Error as StdError, fmt, path::PathBuf};
2
3/// A thread-safe, type-erased error used for failures originating outside
4/// `r2l-core`.
5pub type BoxedError = Box<dyn StdError + Send + Sync + 'static>;
6
7/// Result type returned by fallible `r2l` operations.
8pub type Result<T> = std::result::Result<T, Error>;
9
10impl fmt::Debug for Error {
11    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
12        write!(f, "{self}")
13    }
14}
15
16/// Data type of a parameter value.
17#[derive(Debug)]
18pub enum DType {
19    /// A 32-bit floating-point value.
20    F32,
21    /// An unsigned pointer-sized integer value.
22    Usize,
23    /// Another data type.
24    Other(String),
25}
26
27/// A formatted parameter value and its data type.
28#[derive(Debug)]
29pub struct ValueWithDtype {
30    /// Data type of the value.
31    pub r#type: DType,
32    /// Formatted value.
33    pub value: String,
34}
35
36/// Reason a parameter is invalid.
37#[derive(Debug, thiserror::Error)]
38pub enum InvalidParameterError {
39    /// A value does not satisfy the parameter's requirements.
40    #[error("invalid value for `{name}`: expected {expected}, got {value}")]
41    InvalidValue {
42        /// Parameter name.
43        name: String,
44        /// Description of accepted values.
45        expected: String,
46        /// Supplied value.
47        value: String,
48    },
49    /// The value is outside the accepted inclusive range.
50    #[error("value {current_value:?} is outside the range {min:?}..={max:?}")]
51    InvalidRange {
52        /// Smallest accepted value.
53        min: ValueWithDtype,
54        /// Largest accepted value.
55        max: ValueWithDtype,
56        /// Supplied value.
57        current_value: ValueWithDtype,
58    },
59    /// The supplied path is not valid for the parameter.
60    #[error("invalid path: {path}", path = .path.display())]
61    InvalidPath {
62        /// Supplied path.
63        path: PathBuf,
64    },
65}
66
67/// Reason an artifact cannot be used.
68#[derive(Debug, thiserror::Error)]
69pub enum BrokenArtifact {
70    /// A required artifact does not exist.
71    #[error("missing {artifact_type} artifact at {path}", path = .path.display())]
72    Missing {
73        /// Expected artifact path.
74        path: PathBuf,
75        /// Kind of artifact that was expected.
76        artifact_type: String,
77    },
78    /// An artifact could not be decoded.
79    #[error("failed to decode {artifact_type} artifact at {path}: {source}", path = .path.display())]
80    Decode {
81        /// Artifact path.
82        path: PathBuf,
83        /// Kind of artifact being decoded.
84        artifact_type: String,
85        /// Underlying decoder error.
86        #[source]
87        source: BoxedError,
88    },
89}
90
91/// One or more artifacts that cannot be used.
92#[derive(Debug)]
93pub struct BrokenArtifacts {
94    /// Broken artifacts discovered during validation or loading.
95    pub broken: Vec<BrokenArtifact>,
96}
97
98impl fmt::Display for BrokenArtifacts {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        for (index, artifact) in self.broken.iter().enumerate() {
101            if index > 0 {
102                f.write_str("; ")?;
103            }
104            write!(f, "{artifact}")?;
105        }
106        Ok(())
107    }
108}
109
110impl StdError for BrokenArtifacts {
111    fn source(&self) -> Option<&(dyn StdError + 'static)> {
112        self.broken
113            .first()
114            .map(|artifact| artifact as &(dyn StdError + 'static))
115    }
116}
117
118impl From<BrokenArtifact> for Error {
119    fn from(artifact: BrokenArtifact) -> Self {
120        Self::BrokenArtifacts(BrokenArtifacts {
121            broken: vec![artifact],
122        })
123    }
124}
125
126/// A dependency required for an operation is unavailable.
127#[derive(Debug, thiserror::Error)]
128#[error("missing {dependency_type} dependency `{name}`")]
129pub struct MissingDependency {
130    /// Dependency name.
131    pub name: String,
132    /// Kind of dependency, such as a library, feature, or executable.
133    pub dependency_type: String,
134}
135
136/// An environment operation failed.
137#[derive(Debug, thiserror::Error)]
138#[error("environment operation `{operation}` failed: {source}")]
139pub struct EnvironmentError {
140    /// Operation that failed, such as building, resetting, or stepping.
141    pub operation: String,
142    /// Underlying environment error.
143    #[source]
144    pub source: BoxedError,
145}
146
147/// Reason a tensor operation failed.
148#[derive(Debug, thiserror::Error)]
149pub enum TensorError {
150    /// Two operands do not have compatible shapes.
151    #[error("shape mismatch for tensor operation `{operation}`: left {left:?}, right {right:?}")]
152    ShapeMismatch {
153        /// Operation requiring compatible shapes.
154        operation: String,
155        /// Left-hand tensor shape.
156        left: Vec<usize>,
157        /// Right-hand tensor shape.
158        right: Vec<usize>,
159    },
160    /// Flat data does not contain the number of elements required by its shape.
161    #[error("invalid tensor shape {shape:?}: expected {expected} values, got {actual}")]
162    InvalidShape {
163        /// Requested tensor shape.
164        shape: Vec<usize>,
165        /// Number of values required by the shape.
166        expected: usize,
167        /// Number of supplied values.
168        actual: usize,
169    },
170    /// An operation requires at least one input element or tensor.
171    #[error("tensor operation `{operation}` requires non-empty input")]
172    EmptyInput {
173        /// Operation that received empty input.
174        operation: String,
175    },
176    /// A tensor backend could not execute an operation.
177    #[error("tensor operation `{operation}` failed: {source}")]
178    Operation {
179        /// Operation attempted by the backend.
180        operation: String,
181        /// Underlying backend error.
182        #[source]
183        source: BoxedError,
184    },
185}
186
187impl TensorError {
188    /// Wraps a backend failure with the operation being attempted.
189    pub fn operation(
190        operation: impl Into<String>,
191        source: impl StdError + Send + Sync + 'static,
192    ) -> Self {
193        Self::Operation {
194            operation: operation.into(),
195            source: Box::new(source),
196        }
197    }
198}
199
200/// An external resource stopped before completing an operation.
201#[derive(Debug, thiserror::Error)]
202#[error("{resource} was interrupted: {details}")]
203pub struct ResourceInterrupted {
204    /// Resource that was interrupted.
205    pub resource: String,
206    /// Additional failure details.
207    pub details: String,
208}
209
210/// Errors shared by the `r2l` workspace.
211#[derive(thiserror::Error)]
212pub enum Error {
213    /// A supplied parameter is invalid.
214    #[error("invalid parameter: {0}")]
215    InvalidParameter(#[source] Box<InvalidParameterError>),
216
217    /// An operation cannot run in the current state.
218    #[error("invalid state for `{operation}`: {details}")]
219    InvalidState {
220        /// Operation that was requested.
221        operation: String,
222        /// Why the current state does not permit the operation.
223        details: String,
224    },
225
226    /// The requested operation or capability is not supported.
227    #[error("unsupported operation `{operation}`: {details}")]
228    Unsupported {
229        /// Unsupported operation or capability.
230        operation: String,
231        /// Additional context about the limitation.
232        details: String,
233    },
234
235    /// One or more required artifacts are missing or cannot be decoded.
236    #[error("broken artifacts: {0}")]
237    BrokenArtifacts(#[source] BrokenArtifacts),
238
239    /// A required dependency is unavailable.
240    #[error(transparent)]
241    MissingDependency(#[from] MissingDependency),
242
243    /// An environment operation failed.
244    #[error(transparent)]
245    Environment(#[from] EnvironmentError),
246
247    /// A tensor operation or tensor value is invalid.
248    #[error(transparent)]
249    Tensor(#[from] TensorError),
250
251    /// An external resource was interrupted.
252    #[error(transparent)]
253    ResourceInterrupted(#[from] ResourceInterrupted),
254
255    /// A lower-level failure without a dedicated semantic category.
256    #[error(transparent)]
257    Wrapped(#[from] BoxedError),
258}
259
260impl Error {
261    /// Wraps a lower-level error without discarding its source chain.
262    pub fn wrap(error: impl StdError + Send + Sync + 'static) -> Self {
263        Self::Wrapped(Box::new(error))
264    }
265}