Skip to main content

rave_core/
error.rs

1//! Typed error hierarchy for the engine.
2//!
3//! Uses `thiserror` for library-grade errors.  Application code should wrap
4//! these in `anyhow::Result` at call sites.
5//!
6//! # Error codes
7//!
8//! Each variant maps to a stable integer code via [`EngineError::error_code`]
9//! for structured telemetry without string parsing.
10
11use crate::types::PixelFormat;
12
13/// All errors originating from the RAVE engine.
14#[derive(Debug, thiserror::Error)]
15pub enum EngineError {
16    // ── CUDA ──────────────────────────────────────────────────────────
17    #[error("CUDA driver error: {0}")]
18    Cuda(#[from] cudarc::driver::DriverError),
19
20    #[error("CUDA kernel compilation error: {0}")]
21    NvrtcCompile(#[from] cudarc::nvrtc::CompileError),
22
23    // ── Inference ────────────────────────────────────────────────────
24    #[error("ORT inference error: {0}")]
25    Inference(#[from] ort::Error),
26
27    #[error("Model metadata error: {0}")]
28    ModelMetadata(String),
29
30    #[error("Backend not initialized — call initialize() first")]
31    NotInitialized,
32
33    // ── Codecs ────────────────────────────────────────────────────────
34    #[error("NVDEC decode error: {0}")]
35    Decode(String),
36
37    #[error("NVENC encode error: {0}")]
38    Encode(String),
39
40    #[error("Demux error: {0}")]
41    Demux(String),
42
43    #[error("Mux error: {0}")]
44    Mux(String),
45
46    #[error("Bitstream filter error: {0}")]
47    BitstreamFilter(String),
48
49    #[error("Probe error: {0}")]
50    Probe(String),
51
52    // ── Pipeline ─────────────────────────────────────────────────────
53    #[error("Pipeline error: {0}")]
54    Pipeline(String),
55
56    #[error("Pipeline channel closed unexpectedly")]
57    ChannelClosed,
58
59    #[error("Pipeline shutdown signal received")]
60    Shutdown,
61
62    // ── Type contracts ───────────────────────────────────────────────
63    #[error("Pixel format mismatch: expected {expected:?}, got {actual:?}")]
64    FormatMismatch {
65        expected: PixelFormat,
66        actual: PixelFormat,
67    },
68
69    #[error("Dimension mismatch: {0}")]
70    DimensionMismatch(String),
71
72    #[error("Buffer too small: need {need} bytes, have {have}")]
73    BufferTooSmall { need: usize, have: usize },
74
75    // ── Audit invariants ─────────────────────────────────────────────
76    #[error("Invariant violation: {0}")]
77    InvariantViolation(String),
78
79    // ── Production hardening ─────────────────────────────────────────
80    #[error("Panic recovered in {stage}: {message}")]
81    PanicRecovered {
82        stage: &'static str,
83        message: String,
84    },
85
86    #[error(
87        "VRAM limit exceeded: limit={limit_bytes}B current={current_bytes}B \
88         requested={requested_bytes}B would_be={would_be_bytes}B"
89    )]
90    VramLimitExceeded {
91        limit_bytes: usize,
92        current_bytes: usize,
93        requested_bytes: usize,
94        would_be_bytes: usize,
95    },
96
97    #[error("Backpressure timeout: {stage} blocked for {elapsed_ms} ms")]
98    BackpressureTimeout {
99        stage: &'static str,
100        elapsed_ms: u64,
101    },
102
103    #[error("Drop order violation: {0}")]
104    DropOrderViolation(String),
105}
106
107impl EngineError {
108    /// Stable integer error code for structured telemetry.
109    ///
110    /// Codes are grouped by category:
111    /// - 1xx: CUDA/driver
112    /// - 2xx: Inference
113    /// - 3xx: Codecs
114    /// - 4xx: Pipeline
115    /// - 5xx: Type contracts
116    /// - 6xx: Audit/invariant
117    /// - 7xx: Production hardening
118    pub fn error_code(&self) -> u32 {
119        match self {
120            Self::Cuda(_) => 100,
121            Self::NvrtcCompile(_) => 101,
122            Self::Inference(_) => 200,
123            Self::ModelMetadata(_) => 201,
124            Self::NotInitialized => 202,
125            Self::Decode(_) => 300,
126            Self::Encode(_) => 301,
127            Self::Demux(_) => 302,
128            Self::Mux(_) => 303,
129            Self::BitstreamFilter(_) => 304,
130            Self::Probe(_) => 305,
131            Self::ChannelClosed => 400,
132            Self::Shutdown => 401,
133            Self::Pipeline(_) => 402,
134            Self::FormatMismatch { .. } => 500,
135            Self::DimensionMismatch(_) => 501,
136            Self::BufferTooSmall { .. } => 502,
137            Self::InvariantViolation(_) => 600,
138            Self::PanicRecovered { .. } => 700,
139            Self::VramLimitExceeded { .. } => 701,
140            Self::BackpressureTimeout { .. } => 702,
141            Self::DropOrderViolation(_) => 703,
142        }
143    }
144
145    /// Whether this error is recoverable (pipeline can continue after logging).
146    pub fn is_recoverable(&self) -> bool {
147        matches!(
148            self,
149            Self::BackpressureTimeout { .. } | Self::PanicRecovered { .. }
150        )
151    }
152}
153
154/// Convenience alias used throughout the engine crate.
155pub type Result<T> = std::result::Result<T, EngineError>;