Skip to main content

edgefirst_decoder/
error.rs

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