Skip to main content

laddu_expr/
error.rs

1use thiserror::Error;
2
3/// Result type for parameter construction and lookup operations.
4pub type ParamResult<T> = Result<T, ParamError>;
5/// Result type for expression construction and validation operations.
6pub type ExprResult<T> = Result<T, ExprError>;
7
8/// Errors produced while defining, laying out, or assigning parameters.
9#[derive(Clone, Debug, Error, PartialEq)]
10pub enum ParamError {
11    /// A parameter name was empty.
12    #[error("parameter name cannot be empty")]
13    EmptyName,
14    #[error("duplicate parameter name: {0}")]
15    /// A parameter name was registered more than once.
16    DuplicateName(String),
17    #[error("unknown parameter: {0}")]
18    /// A parameter name was not present in the layout.
19    UnknownName(String),
20    #[error("parameter conflict for {name}: {reason}")]
21    /// Two definitions of the same parameter were incompatible.
22    ParameterConflict {
23        /// Conflicting parameter name.
24        name: String,
25        /// Description of the incompatibility.
26        reason: String,
27    },
28    /// A parameter identifier was outside the layout.
29    #[error("invalid parameter id #{id} for layout of size {len}")]
30    InvalidParamId {
31        /// Invalid identifier index.
32        id: usize,
33        /// Number of parameters in the layout.
34        len: usize,
35    },
36    /// A free-parameter identifier was outside the layout.
37    #[error("invalid free parameter id #{id} for layout with {len} free parameters")]
38    InvalidFreeParamId {
39        /// Invalid free-parameter index.
40        id: usize,
41        /// Number of free parameters in the layout.
42        len: usize,
43    },
44    /// A free-parameter value vector had the wrong length.
45    #[error("expected {expected} free parameters, got {actual}")]
46    FreeLengthMismatch {
47        /// Required number of values.
48        expected: usize,
49        /// Supplied number of values.
50        actual: usize,
51    },
52    /// A parameter's lower bound exceeded its upper bound.
53    #[error("invalid bounds for {name}: min {min} is greater than max {max}")]
54    InvalidBounds {
55        /// Parameter name.
56        name: String,
57        /// Invalid lower bound.
58        min: f64,
59        /// Invalid upper bound.
60        max: f64,
61    },
62    /// A parameter bound was NaN.
63    #[error("invalid NaN bound {value} for {name}")]
64    InvalidBoundValue {
65        /// Parameter name.
66        name: String,
67        /// Invalid bound value.
68        value: f64,
69    },
70    /// A uniform initial range had its endpoints reversed.
71    #[error("invalid uniform initial range for {name}: min {min} is greater than max {max}")]
72    InvalidInitialRange {
73        /// Parameter name.
74        name: String,
75        /// Invalid range minimum.
76        min: f64,
77        /// Invalid range maximum.
78        max: f64,
79    },
80    /// A parameter's initial value fell outside its bounds.
81    #[error("initial value {value} for {name} is outside bounds")]
82    InitialOutOfBounds {
83        /// Parameter name.
84        name: String,
85        /// Invalid initial value.
86        value: f64,
87    },
88    /// A parameter's initial range extended outside its bounds.
89    #[error("initial range [{min}, {max}] for {name} is outside parameter bounds")]
90    InitialRangeOutOfBounds {
91        /// Parameter name.
92        name: String,
93        /// Initial range minimum.
94        min: f64,
95        /// Initial range maximum.
96        max: f64,
97    },
98    /// A scalar initial value was not finite.
99    #[error("initial value {value} for {name} must be finite")]
100    NonFiniteInitialValue {
101        /// Parameter name.
102        name: String,
103        /// Invalid initial value.
104        value: f64,
105    },
106    /// An initial range endpoint was not finite.
107    #[error("initial range [{min}, {max}] for {name} must be finite")]
108    NonFiniteInitialRange {
109        /// Parameter name.
110        name: String,
111        /// Range minimum.
112        min: f64,
113        /// Range maximum.
114        max: f64,
115    },
116    /// A fixed parameter value fell outside its bounds.
117    #[error("fixed value {value} for {name} is outside bounds")]
118    FixedValueOutOfBounds {
119        /// Parameter name.
120        name: String,
121        /// Invalid fixed value.
122        value: f64,
123    },
124    /// A fixed parameter value was not finite.
125    #[error("fixed value {value} for {name} must be finite")]
126    NonFiniteFixedValue {
127        /// Parameter name.
128        name: String,
129        /// Invalid fixed value.
130        value: f64,
131    },
132    /// An assigned parameter value fell outside its bounds.
133    #[error("value {value} for {name} is outside bounds")]
134    ValueOutOfBounds {
135        /// Parameter name.
136        name: String,
137        /// Invalid assigned value.
138        value: f64,
139    },
140    /// A periodic parameter did not have finite, ordered bounds.
141    #[error("periodic parameter {name} requires finite two-sided bounds with min < max")]
142    PeriodicRequiresFiniteBounds {
143        /// Parameter name.
144        name: String,
145    },
146    /// A parameter scale was not finite and positive.
147    #[error("invalid scale for {name}: expected a finite positive value, got {scale}")]
148    InvalidScale {
149        /// Parameter name.
150        name: String,
151        /// Invalid scale.
152        scale: f64,
153    },
154    /// A fixed value in a parameter update was not finite.
155    #[error("parameter update fixed value must be finite, got {value}")]
156    InvalidUpdateFixedValue {
157        /// Invalid fixed value.
158        value: f64,
159    },
160    /// A scalar initial value in a parameter update was not finite.
161    #[error("parameter update initial value must be finite, got {value}")]
162    InvalidUpdateInitialValue {
163        /// Invalid initial value.
164        value: f64,
165    },
166    /// A uniform initial range in a parameter update was not finite or ordered.
167    #[error("parameter update initial range must be finite and ordered, got [{min}, {max}]")]
168    InvalidUpdateInitialRange {
169        /// Invalid range minimum.
170        min: f64,
171        /// Invalid range maximum.
172        max: f64,
173    },
174    /// Bounds in a parameter update were unordered or contained NaN.
175    #[error(
176        "parameter update bounds must be ordered and cannot contain NaN, got [{min:?}, {max:?}]"
177    )]
178    InvalidUpdateBounds {
179        /// Invalid lower bound.
180        min: Option<f64>,
181        /// Invalid upper bound.
182        max: Option<f64>,
183    },
184    /// A scale in a parameter update was not finite and positive.
185    #[error("parameter update scale must be finite and positive, got {scale}")]
186    InvalidUpdateScale {
187        /// Invalid scale.
188        scale: f64,
189    },
190    /// A periodic value was outside its canonical half-open domain.
191    #[error(
192        "value {value} for periodic parameter {name} is outside canonical domain [{min}, {max})"
193    )]
194    ValueOutsidePeriodicDomain {
195        /// Parameter name.
196        name: String,
197        /// Invalid value.
198        value: f64,
199        /// Inclusive domain minimum.
200        min: f64,
201        /// Exclusive domain maximum.
202        max: f64,
203    },
204}
205
206/// Structural validation errors for serialized expression graphs.
207#[derive(Clone, Debug, Error, PartialEq, Eq)]
208pub enum ExprGraphError {
209    /// The node and metadata arrays had different lengths.
210    #[error("graph metadata length {metadata_len} does not match node length {node_len}")]
211    MetadataLength {
212        /// Number of graph nodes.
213        node_len: usize,
214        /// Number of metadata entries.
215        metadata_len: usize,
216    },
217    /// The root identifier was outside the node array.
218    #[error("graph root node #{root} is out of bounds for graph with {node_len} nodes")]
219    InvalidRoot {
220        /// Invalid root index.
221        root: usize,
222        /// Number of graph nodes.
223        node_len: usize,
224    },
225    /// A node referenced a child outside the node array.
226    #[error("graph node #{node} references missing child #{child}")]
227    InvalidChild {
228        /// Parent node index.
229        node: usize,
230        /// Invalid child index.
231        child: usize,
232    },
233    /// A node referenced a child stored after its parent.
234    #[error(
235        "graph node #{node} references child #{child}, but children must appear before parents"
236    )]
237    InvalidChildOrder {
238        /// Parent node index.
239        node: usize,
240        /// Out-of-order child index.
241        child: usize,
242    },
243    /// The graph contained no nodes.
244    #[error("graph is empty")]
245    Empty,
246}
247
248/// Describes an operation applied to an expression with an incompatible shape.
249#[derive(Clone, Debug, Error, PartialEq, Eq)]
250#[error("invalid expression shape for {operation}: {message}")]
251pub struct ExprShapeError {
252    operation: &'static str,
253    message: String,
254}
255
256impl ExprShapeError {
257    pub(crate) fn new(operation: &'static str, message: impl Into<String>) -> Self {
258        Self {
259            operation,
260            message: message.into(),
261        }
262    }
263}
264
265/// Errors produced while constructing or rebuilding expressions.
266#[derive(Clone, Debug, Error, PartialEq)]
267pub enum ExprError {
268    /// A parameter operation failed.
269    #[error(transparent)]
270    Params(#[from] ParamError),
271    /// A serialized graph was structurally invalid.
272    #[error(transparent)]
273    Graph(#[from] ExprGraphError),
274    /// An operation received incompatible expression shapes.
275    #[error(transparent)]
276    Shape(#[from] ExprShapeError),
277}