Skip to main content

edgefirst_tensor/
error.rs

1// SPDX-FileCopyrightText: Copyright 2025 Au-Zone Technologies
2// SPDX-License-Identifier: Apache-2.0
3
4pub type Result<T, E = Error> = std::result::Result<T, E>;
5
6#[derive(Debug)]
7pub enum Error {
8    IoError(std::io::Error),
9    #[cfg(unix)]
10    NixError(nix::Error),
11    NotImplemented(String),
12    InvalidSize(usize),
13    ShapeMismatch(String),
14    #[cfg(target_os = "linux")]
15    UnknownDeviceType(u64, u64),
16    InvalidMemoryType(String),
17    /// The GL context backing a PBO tensor has been destroyed.
18    PboDisconnected,
19    /// The PBO buffer is currently mapped and cannot be used for GL operations.
20    PboMapped,
21    #[cfg(feature = "ndarray")]
22    NdArrayError(ndarray::ShapeError),
23    InvalidShape(String),
24    InvalidArgument(String),
25    InvalidOperation(String),
26    /// Structured quantization-invariant failure. Round-trippable through
27    /// the C and Python boundaries so callers can diagnose which field
28    /// failed without parsing strings.
29    QuantizationInvalid {
30        /// Which invariant failed: `"scale.len"`, `"zero_point.len"`,
31        /// `"axis"`, `"per_channel_requires_axis"`,
32        /// `"per_tensor_redundant_axis"`, `"dtype_is_integer"`.
33        field: &'static str,
34        /// What the validator expected, e.g. `"length matches scale (48)"`.
35        expected: String,
36        /// What was observed, e.g. `"length 32"`.
37        got: String,
38    },
39    /// A capacity-aware operation needs more bytes than the tensor's
40    /// underlying allocation provides.
41    InsufficientCapacity {
42        /// Bytes the requested layout needs.
43        needed: usize,
44        /// Bytes the allocation provides.
45        capacity: usize,
46    },
47    /// A [`crate::Tensor::view`]-style sub-region extends past the parent's
48    /// bounds. `view`/`batch` reject rather than clamp.
49    RegionOutOfBounds {
50        /// The offending region.
51        region: crate::Region,
52        /// The parent frame `(width, height)` in pixels.
53        bounds: (usize, usize),
54    },
55    /// A `batch(n)` index is `>=` the tensor's leading batch dimension `N`.
56    BatchIndexOutOfBounds {
57        /// The requested element index.
58        index: usize,
59        /// The tensor's leading dimension `N`.
60        batch: usize,
61    },
62}
63
64impl From<std::io::Error> for Error {
65    fn from(err: std::io::Error) -> Self {
66        Error::IoError(err)
67    }
68}
69#[cfg(unix)]
70impl From<nix::Error> for Error {
71    fn from(err: nix::Error) -> Self {
72        Error::NixError(err)
73    }
74}
75
76#[cfg(feature = "ndarray")]
77impl From<ndarray::ShapeError> for Error {
78    fn from(err: ndarray::ShapeError) -> Self {
79        Error::NdArrayError(err)
80    }
81}
82
83impl std::fmt::Display for Error {
84    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85        match self {
86            Error::InsufficientCapacity { needed, capacity } => write!(
87                f,
88                "insufficient tensor capacity: need {needed} bytes, have {capacity}"
89            ),
90            Error::RegionOutOfBounds { region, bounds } => write!(
91                f,
92                "region {region:?} out of bounds for {}x{} frame",
93                bounds.0, bounds.1
94            ),
95            Error::BatchIndexOutOfBounds { index, batch } => write!(
96                f,
97                "batch index {index} out of bounds for batch size {batch}"
98            ),
99            _ => write!(f, "{self:?}"),
100        }
101    }
102}
103
104impl std::error::Error for Error {}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[test]
111    fn test_error_display() {
112        let e = Error::InvalidSize(0);
113        let msg = e.to_string();
114        assert!(!msg.is_empty());
115        assert!(
116            msg.contains("InvalidSize"),
117            "unexpected InvalidSize message: {msg}"
118        );
119
120        let e = Error::NotImplemented("foo".to_string());
121        let msg = e.to_string();
122        assert!(!msg.is_empty());
123        assert!(
124            msg.contains("NotImplemented") && msg.contains("foo"),
125            "unexpected NotImplemented message: {msg}"
126        );
127
128        let e = Error::ShapeMismatch("expected 3, got 4".to_string());
129        let msg = e.to_string();
130        assert!(!msg.is_empty());
131        assert!(
132            msg.contains("ShapeMismatch") && msg.contains("expected 3"),
133            "unexpected ShapeMismatch message: {msg}"
134        );
135
136        let e = Error::InvalidMemoryType("dma".to_string());
137        let msg = e.to_string();
138        assert!(!msg.is_empty());
139        assert!(
140            msg.contains("InvalidMemoryType") && msg.contains("dma"),
141            "unexpected InvalidMemoryType message: {msg}"
142        );
143
144        let e = Error::PboDisconnected;
145        let msg = e.to_string();
146        assert!(!msg.is_empty());
147        assert!(
148            msg.contains("PboDisconnected"),
149            "unexpected PboDisconnected message: {msg}"
150        );
151
152        let e = Error::PboMapped;
153        let msg = e.to_string();
154        assert!(!msg.is_empty());
155        assert!(
156            msg.contains("PboMapped"),
157            "unexpected PboMapped message: {msg}"
158        );
159
160        let e = Error::InvalidShape("bad shape".to_string());
161        let msg = e.to_string();
162        assert!(!msg.is_empty());
163        assert!(
164            msg.contains("InvalidShape") && msg.contains("bad shape"),
165            "unexpected InvalidShape message: {msg}"
166        );
167
168        let e = Error::InvalidArgument("negative".to_string());
169        let msg = e.to_string();
170        assert!(!msg.is_empty());
171        assert!(
172            msg.contains("InvalidArgument") && msg.contains("negative"),
173            "unexpected InvalidArgument message: {msg}"
174        );
175
176        let e = Error::InvalidOperation("read-only".to_string());
177        let msg = e.to_string();
178        assert!(!msg.is_empty());
179        assert!(
180            msg.contains("InvalidOperation") && msg.contains("read-only"),
181            "unexpected InvalidOperation message: {msg}"
182        );
183
184        let e = Error::IoError(std::io::Error::new(
185            std::io::ErrorKind::NotFound,
186            "file missing",
187        ));
188        let msg = e.to_string();
189        assert!(!msg.is_empty());
190        assert!(
191            msg.contains("IoError") && msg.contains("file missing"),
192            "unexpected IoError message: {msg}"
193        );
194    }
195
196    #[test]
197    fn insufficient_capacity_message() {
198        let e = Error::InsufficientCapacity {
199            needed: 100,
200            capacity: 64,
201        };
202        let msg = format!("{e}");
203        assert!(
204            msg.contains("100") && msg.contains("64"),
205            "unexpected message: {msg}"
206        );
207    }
208}