Skip to main content

limen_core/
errors.rs

1//! Error families used across Limen Core.
2//!
3//! Errors are designed to stay lightweight and `no_std` friendly by default.
4//! Implementations of `std::error::Error` are only provided when the `std`
5//! feature is enabled.
6
7use core::fmt;
8
9#[cfg(feature = "std")]
10use std::error::Error;
11
12use crate::types::EdgeIndex;
13
14// **** Edge Errors *****
15
16/// Errors originating from queue operations.
17#[non_exhaustive]
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum QueueError {
20    /// The queue is at or above the hard watermark capacity.
21    AtOrAboveHardCap,
22    /// The queue is backpressured but not full; caller may retry later.
23    Backpressured,
24    /// The queue is empty when a pop operation was requested.
25    Empty,
26    /// The operation is not supported by this queue/backend (e.g., reference peek in concurrent mode).
27    Unsupported,
28    /// The queue lock has been poisoned (concurrent mode only).
29    Poisoned,
30}
31
32impl fmt::Display for QueueError {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        match self {
35            QueueError::AtOrAboveHardCap => {
36                f.write_str("queue is at or above the hard watermark capacity")
37            }
38            QueueError::Backpressured => {
39                f.write_str("queue is backpressured but not full; caller may retry later")
40            }
41            QueueError::Empty => f.write_str("queue is empty"),
42            QueueError::Unsupported => f.write_str("operation unsupported by this queue/backend"),
43            QueueError::Poisoned => f.write_str("queue lock is poisoned"),
44        }
45    }
46}
47
48#[cfg(feature = "std")]
49impl Error for QueueError {}
50
51// ***** Node Errors *****
52
53/// Errors from node execution.
54#[non_exhaustive]
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum NodeErrorKind {
57    /// Inputs were not available to progress this node.
58    NoInput,
59    /// Outputs could not be enqueued due to backpressure.
60    Backpressured,
61    /// An execution budget or deadline was exceeded.
62    OverBudget,
63    /// External dependency (device, transport) was unavailable or timed out.
64    ExternalUnavailable,
65    /// A generic failure in node logic.
66    ExecutionFailed,
67}
68
69impl fmt::Display for NodeErrorKind {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        match self {
72            NodeErrorKind::NoInput => {
73                f.write_str("inputs were not available to progress this node")
74            }
75            NodeErrorKind::Backpressured => {
76                f.write_str("outputs could not be enqueued due to backpressure")
77            }
78            NodeErrorKind::OverBudget => {
79                f.write_str("an execution budget or deadline was exceeded")
80            }
81            NodeErrorKind::ExternalUnavailable => {
82                f.write_str("an external dependency was unavailable or timed out")
83            }
84            NodeErrorKind::ExecutionFailed => {
85                f.write_str("a generic failure occurred in node logic")
86            }
87        }
88    }
89}
90
91/// A unified error used by node lifecycle methods.
92#[non_exhaustive]
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub struct NodeError {
95    /// The error kind.
96    kind: NodeErrorKind,
97    /// Optional numeric code for platform/backend-specific mapping.
98    code: u32,
99}
100
101impl NodeError {
102    /// Construct a new node error with the given kind and optional code.
103    pub const fn new(kind: NodeErrorKind, code: u32) -> Self {
104        Self { kind, code }
105    }
106}
107
108impl fmt::Display for NodeError {
109    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110        write!(f, "node error: {} (code: {})", self.kind, self.code)
111    }
112}
113
114#[cfg(feature = "std")]
115impl Error for NodeError {}
116
117impl NodeError {
118    /// Creates a `NoInput` error.
119    #[inline]
120    pub const fn no_input() -> Self {
121        Self::new(NodeErrorKind::NoInput, 0)
122    }
123    /// Creates a `Backpressured` error.
124    #[inline]
125    pub const fn backpressured() -> Self {
126        Self::new(NodeErrorKind::Backpressured, 0)
127    }
128    /// Creates an `OverBudget` error.
129    #[inline]
130    pub const fn over_budget() -> Self {
131        Self::new(NodeErrorKind::OverBudget, 0)
132    }
133    /// Creates an `ExternalUnavailable` error.
134    #[inline]
135    pub const fn external_unavailable() -> Self {
136        Self::new(NodeErrorKind::ExternalUnavailable, 0)
137    }
138    /// Creates an `ExecutionFailed` error.
139    #[inline]
140    pub const fn execution_failed() -> Self {
141        Self::new(NodeErrorKind::ExecutionFailed, 0)
142    }
143
144    /// Same as the above but lets you tack on a backend/platform error code.
145    #[inline]
146    pub const fn with_code(mut self, code: u32) -> Self {
147        self.code = code;
148        self
149    }
150
151    /// Return the error kind.
152    #[inline]
153    pub const fn kind(&self) -> &NodeErrorKind {
154        &self.kind
155    }
156
157    /// Return the numeric code associated with this error.
158    #[inline]
159    pub const fn code(&self) -> &u32 {
160        &self.code
161    }
162}
163
164impl From<NodeErrorKind> for NodeError {
165    #[inline]
166    fn from(kind: NodeErrorKind) -> Self {
167        NodeError::new(kind, 0)
168    }
169}
170
171/// Errors related to model loading and inference execution.
172#[non_exhaustive]
173#[derive(Debug, Clone, Copy, PartialEq, Eq)]
174pub enum InferenceErrorKind {
175    /// A model artifact is invalid or unsupported.
176    InvalidArtifact,
177    /// The input or output payload is incompatible with the model.
178    ShapeOrTypeMismatch,
179    /// Execution failed inside the backend.
180    ExecutionFailed,
181    /// Backend resource not available (e.g., device).
182    ResourceUnavailable,
183}
184
185impl fmt::Display for InferenceErrorKind {
186    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187        match self {
188            InferenceErrorKind::InvalidArtifact => {
189                f.write_str("model artifact is invalid or unsupported")
190            }
191            InferenceErrorKind::ShapeOrTypeMismatch => {
192                f.write_str("input or output payload is incompatible with the model")
193            }
194            InferenceErrorKind::ExecutionFailed => {
195                f.write_str("execution failed inside the backend")
196            }
197            InferenceErrorKind::ResourceUnavailable => {
198                f.write_str("backend resource is unavailable")
199            }
200        }
201    }
202}
203
204/// Inference error including a kind and optional code.
205#[non_exhaustive]
206#[derive(Debug, Clone, Copy, PartialEq, Eq)]
207pub struct InferenceError {
208    /// Error kind.
209    kind: InferenceErrorKind,
210    /// Optional numeric code.
211    code: u32,
212}
213
214impl InferenceError {
215    /// Construct a new inference error.
216    pub const fn new(kind: InferenceErrorKind, code: u32) -> Self {
217        Self { kind, code }
218    }
219
220    /// Return the inference error kind.
221    #[inline]
222    pub const fn kind(&self) -> &InferenceErrorKind {
223        &self.kind
224    }
225
226    /// Return the numeric code associated with this inference error.
227    #[inline]
228    pub const fn code(&self) -> &u32 {
229        &self.code
230    }
231}
232
233impl fmt::Display for InferenceError {
234    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235        write!(f, "inference error: {} (code: {})", self.kind, self.code)
236    }
237}
238
239#[cfg(feature = "std")]
240impl Error for InferenceError {}
241
242// ***** Graph Errors *****
243
244/// Graph validation and wiring errors.
245#[non_exhaustive]
246#[derive(Debug, Clone, Copy, PartialEq, Eq)]
247pub enum GraphError {
248    /// The graph contains a cycle.
249    // TODO: ENABLE?
250    Cyclic,
251    /// Port schema or memory placement is incompatible across an edge.
252    IncompatiblePorts,
253    /// Queue capacity or watermark configuration is invalid.
254    InvalidCapacity,
255    /// Invalid graph index used.
256    InvalidEdgeIndex,
257    /// Failed to sample occupancy for the given edge (e.g., poisoned lock or device error).
258    OccupancySampleFailed(EdgeIndex),
259}
260
261impl fmt::Display for GraphError {
262    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263        match self {
264            GraphError::Cyclic => f.write_str("graph contains a cycle"),
265            GraphError::IncompatiblePorts => {
266                f.write_str("port schema or memory placement is incompatible across an edge")
267            }
268            GraphError::InvalidCapacity => {
269                f.write_str("queue capacity or watermark configuration is invalid")
270            }
271            GraphError::InvalidEdgeIndex => f.write_str("edge index is invalid"),
272            GraphError::OccupancySampleFailed(ei) => {
273                write!(f, "failed to sample occupancy for edge {}", ei.as_usize())
274            }
275        }
276    }
277}
278
279#[cfg(feature = "std")]
280impl Error for GraphError {}
281
282// ***** Runtime Errors *****
283
284/// Generic runtime error kinds.
285#[non_exhaustive]
286#[derive(Debug, Clone, Copy, PartialEq, Eq)]
287pub enum RuntimeErrorKind {
288    /// An invariant has been violated (e.g., cyclic graph or type mismatch).
289    InvariantViolation,
290    /// A platform service was requested but is unavailable.
291    PlatformUnavailable,
292    /// The operation is unsupported in this profile or configuration.
293    Unsupported,
294    /// An unspecified failure occurred.
295    Unknown,
296}
297
298impl fmt::Display for RuntimeErrorKind {
299    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300        match self {
301            RuntimeErrorKind::InvariantViolation => f.write_str("an invariant has been violated"),
302            RuntimeErrorKind::PlatformUnavailable => {
303                f.write_str("a requested platform service is unavailable")
304            }
305            RuntimeErrorKind::Unsupported => {
306                f.write_str("the operation is unsupported in this profile or configuration")
307            }
308            RuntimeErrorKind::Unknown => f.write_str("an unspecified failure occurred"),
309        }
310    }
311}
312
313#[cfg(feature = "std")]
314impl Error for RuntimeErrorKind {}
315
316/// Scheduler-related errors.
317#[non_exhaustive]
318#[derive(Debug, Clone, Copy, PartialEq, Eq)]
319pub enum SchedulerError {
320    /// The scheduler cannot proceed due to an invariant violation.
321    InvariantViolation,
322    /// An internal error occurred.
323    Internal,
324}
325
326impl fmt::Display for SchedulerError {
327    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
328        match self {
329            SchedulerError::InvariantViolation => {
330                f.write_str("the scheduler cannot proceed due to an invariant violation")
331            }
332            SchedulerError::Internal => f.write_str("an internal scheduler error occurred"),
333        }
334    }
335}
336
337#[cfg(feature = "std")]
338impl Error for SchedulerError {}
339
340// ***** Source Errors *****
341
342/// Source / sensor related errors.
343#[non_exhaustive]
344#[derive(Debug, Clone, Copy, PartialEq, Eq)]
345pub enum SensorError {
346    /// Sensor open failed.
347    OpenFailed,
348    /// Sensor read failed.
349    ReadFailed,
350    /// Sensor stream ended.
351    EndOfStream,
352    /// Sensor reset failed.
353    ResetFailed,
354    /// Invalid sensor configuration
355    ConfigurationInvalid,
356}
357
358impl fmt::Display for SensorError {
359    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360        match self {
361            SensorError::OpenFailed => f.write_str("sensor open failed"),
362            SensorError::ReadFailed => f.write_str("sensor read failed"),
363            SensorError::EndOfStream => f.write_str("sensor stream ended"),
364            SensorError::ResetFailed => f.write_str("sensor reset failed"),
365            SensorError::ConfigurationInvalid => f.write_str("invalid sensor configuration"),
366        }
367    }
368}
369
370#[cfg(feature = "std")]
371impl Error for SensorError {}
372
373// ***** Sink Errors *****
374
375/// Output / sink related errors.
376#[non_exhaustive]
377#[derive(Debug, Clone, Copy, PartialEq, Eq)]
378pub enum OutputError {
379    /// Sink write failed.
380    WriteFailed,
381    /// Sink flush failed.
382    FlushFailed,
383}
384
385impl fmt::Display for OutputError {
386    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
387        match self {
388            OutputError::WriteFailed => f.write_str("sink write failed"),
389            OutputError::FlushFailed => f.write_str("sink flush failed"),
390        }
391    }
392}
393
394#[cfg(feature = "std")]
395impl Error for OutputError {}
396
397// ***** Memory Errors *****
398
399/// Errors originating from memory manager operations.
400#[non_exhaustive]
401#[derive(Debug, Clone, Copy, PartialEq, Eq)]
402pub enum MemoryError {
403    /// No free slots available in the manager.
404    NoFreeSlots,
405    /// Token index is out of range (invalid token) or the slot is not allocated.
406    BadToken,
407    /// Attempted to free a slot that is not currently allocated.
408    NotAllocated,
409    /// Attempted to borrow (read or write) but slot is already borrowed
410    /// in an incompatible way.
411    AlreadyBorrowed,
412    /// Attempted to free a slot while borrows are still active.
413    BorrowActive,
414    /// A synchronization primitive (lock) was poisoned.
415    Poisoned,
416}
417
418impl fmt::Display for MemoryError {
419    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
420        match self {
421            MemoryError::NoFreeSlots => f.write_str("no free slots in memory manager"),
422            MemoryError::BadToken => f.write_str("token index out of range or slot not allocated"),
423            MemoryError::NotAllocated => {
424                f.write_str("attempted to free a slot that is not allocated")
425            }
426            MemoryError::AlreadyBorrowed => f.write_str("slot already borrowed incompatibly"),
427            MemoryError::BorrowActive => f.write_str("cannot free slot while borrows are active"),
428            MemoryError::Poisoned => f.write_str("synchronization primitive is poisoned"),
429        }
430    }
431}
432
433#[cfg(feature = "std")]
434impl Error for MemoryError {}
435
436// ***** Runtime Invariant Errors *****
437
438/// Runtime invariants that were violated (programmer errors).
439#[non_exhaustive]
440#[derive(Debug, Clone, Copy, PartialEq, Eq)]
441pub enum RuntimeInvariantError {
442    /// `step` was called before `init` installed a clock.
443    UninitializedClock,
444    /// `step` was called before `init` installed telemetry.
445    UninitializedTelemetry,
446}
447
448impl fmt::Display for RuntimeInvariantError {
449    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
450        match self {
451            RuntimeInvariantError::UninitializedClock => f.write_str("clock not present"),
452            RuntimeInvariantError::UninitializedTelemetry => f.write_str("telemetry not present"),
453        }
454    }
455}
456
457/// Error surface for runtimes: can wrap graph- and node-level errors.
458#[non_exhaustive]
459#[derive(Debug, Clone, Copy, PartialEq, Eq)]
460pub enum RuntimeError {
461    /// Graph errors
462    Graph(GraphError),
463    /// Node errors
464    Node(NodeError),
465    /// Internal runtime invariants that were violated.
466    RuntimeInvariant(RuntimeInvariantError),
467}
468
469impl From<GraphError> for RuntimeError {
470    #[inline]
471    fn from(e: GraphError) -> Self {
472        RuntimeError::Graph(e)
473    }
474}
475
476impl From<NodeError> for RuntimeError {
477    #[inline]
478    fn from(e: NodeError) -> Self {
479        RuntimeError::Node(e)
480    }
481}
482
483impl From<RuntimeInvariantError> for RuntimeError {
484    #[inline]
485    fn from(e: RuntimeInvariantError) -> Self {
486        RuntimeError::RuntimeInvariant(e)
487    }
488}
489
490impl fmt::Display for RuntimeError {
491    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
492        match self {
493            RuntimeError::Graph(e) => write!(f, "runtime graph error: {e}"),
494            RuntimeError::Node(e) => write!(f, "runtime node error: {e}"),
495            RuntimeError::RuntimeInvariant(e) => write!(f, "runtime invariant error: {e}"),
496        }
497    }
498}
499
500#[cfg(feature = "std")]
501impl std::error::Error for RuntimeError {}