Skip to main content

edgefirst_decoder/
error.rs

1// SPDX-FileCopyrightText: Copyright 2025 Au-Zone Technologies
2// SPDX-License-Identifier: Apache-2.0
3
4use core::fmt;
5
6pub type DecoderResult<T, E = DecoderError> = std::result::Result<T, E>;
7
8#[derive(Debug)]
9pub enum DecoderError {
10    /// An internal error occurred
11    Internal(String),
12    /// An operation was requested that is not supported
13    NotSupported(String),
14    /// An invalid tensor shape was given
15    InvalidShape(String),
16    /// An error occurred while parsing YAML
17    Yaml(serde_yaml::Error),
18    /// An error occurred while parsing YAML
19    Json(serde_json::Error),
20    /// Attmpted to use build a decoder without configuration
21    NoConfig,
22    /// The provide decoder configuration was invalid
23    InvalidConfig(String),
24    /// An error occurred with ndarray shape operations
25    NDArrayShape(ndarray::ShapeError),
26    /// Schema-declared per-scale child dtype != bound tensor dtype at run time.
27    DtypeMismatch {
28        expected: edgefirst_tensor::DType,
29        actual: edgefirst_tensor::DType,
30        role: &'static str,
31        level: usize,
32    },
33    /// Integer tensor bound to a role that requires dequantization, but the
34    /// tensor carries no `Quantization` metadata. The upstream inference layer
35    /// must call `tensor.set_quantization(...)` before invoking the decoder,
36    /// or callers may use `per_scale::apply_schema_quant()` as a fallback.
37    QuantMissing {
38        dtype: edgefirst_tensor::DType,
39        role: &'static str,
40        level: usize,
41    },
42    /// Internal logic bug — a dispatch variant was constructed but the
43    /// concrete kernel wasn't matched. Indicates a missing arm in the
44    /// dispatch enum's `run()` impl.
45    KernelDispatchUnreachable(String),
46    /// `EDGEFIRST_DECODER_FORCE_KERNEL` requested a tier whose features
47    /// the running CPU doesn't support.
48    ForcedKernelUnavailable {
49        tier: &'static str,
50        missing_feature: &'static str,
51    },
52}
53
54impl fmt::Display for DecoderError {
55    /// Formats the error for display
56    /// # Arguments
57    /// * `f` - The formatter to write to
58    /// # Returns
59    /// A result indicating success or failure
60    /// # Examples
61    /// ```rust
62    /// use edgefirst_decoder::DecoderError;
63    /// let err = DecoderError::InvalidConfig("The config was invalid".to_string());
64    /// println!("{}", err);
65    /// ```
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        write!(f, "{self:?}")
68    }
69}
70
71impl std::error::Error for DecoderError {}
72
73impl From<serde_yaml::Error> for DecoderError {
74    fn from(err: serde_yaml::Error) -> Self {
75        DecoderError::Yaml(err)
76    }
77}
78
79impl From<serde_json::Error> for DecoderError {
80    fn from(err: serde_json::Error) -> Self {
81        DecoderError::Json(err)
82    }
83}
84
85impl From<ndarray::ShapeError> for DecoderError {
86    fn from(err: ndarray::ShapeError) -> Self {
87        DecoderError::NDArrayShape(err)
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn test_decoder_error_display() {
97        let e = DecoderError::Internal("something broke".to_string());
98        let msg = e.to_string();
99        assert!(!msg.is_empty());
100        assert!(
101            msg.contains("Internal") && msg.contains("something broke"),
102            "unexpected Internal message: {msg}"
103        );
104
105        let e = DecoderError::NotSupported("yolov99".to_string());
106        let msg = e.to_string();
107        assert!(!msg.is_empty());
108        assert!(
109            msg.contains("NotSupported") && msg.contains("yolov99"),
110            "unexpected NotSupported message: {msg}"
111        );
112
113        let e = DecoderError::InvalidShape("expected 3D".to_string());
114        let msg = e.to_string();
115        assert!(!msg.is_empty());
116        assert!(
117            msg.contains("InvalidShape") && msg.contains("expected 3D"),
118            "unexpected InvalidShape message: {msg}"
119        );
120
121        let e = DecoderError::NoConfig;
122        let msg = e.to_string();
123        assert!(!msg.is_empty());
124        assert!(
125            msg.contains("NoConfig"),
126            "unexpected NoConfig message: {msg}"
127        );
128
129        let e = DecoderError::InvalidConfig("missing field".to_string());
130        let msg = e.to_string();
131        assert!(!msg.is_empty());
132        assert!(
133            msg.contains("InvalidConfig") && msg.contains("missing field"),
134            "unexpected InvalidConfig message: {msg}"
135        );
136    }
137}