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