Skip to main content

nir_rs/
error.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! Structured errors for `nir-rs`.
4//!
5//! Covers graph construction/validation, tensor shape checks, and HDF5 `.nir`
6//! I/O.
7
8use thiserror::Error;
9
10/// Result type used across the crate.
11pub type Result<T> = std::result::Result<T, NirError>;
12
13/// Public error type for NIR operations.
14#[derive(Debug, Clone, PartialEq, Eq, Error)]
15#[non_exhaustive]
16pub enum NirError {
17    /// Feature not yet implemented.
18    ///
19    /// Returned by HDF5 I/O function stubs when the `hdf5` feature is not enabled.
20    #[error("not implemented: {0}")]
21    Unimplemented(&'static str),
22
23    /// HDF5 / wire `type` string is not a known NIR node.
24    #[error("unknown node type: {0}")]
25    UnknownNodeType(String),
26
27    /// A node name was inserted twice into the same graph.
28    #[error("duplicate node: {0}")]
29    DuplicateNode(String),
30
31    /// An edge or lookup referenced a node name that is not in the graph.
32    #[error("missing node: {0}")]
33    MissingNode(String),
34
35    /// The same directed edge `(src, dst)` appears more than once.
36    #[error("duplicate edge: ({0}, {1})")]
37    DuplicateEdge(String, String),
38
39    /// Structural or semantic problem with a graph that is not covered above.
40    #[error("invalid graph: {0}")]
41    InvalidGraph(String),
42
43    /// NIR file / graph version is not supported by this crate.
44    #[error("unsupported version: {0}")]
45    UnsupportedVersion(String),
46
47    /// A required wire field was absent when decoding a node or graph.
48    #[error("missing field: {0}")]
49    MissingField(String),
50
51    /// Tensor shape / data length mismatch or other tensor invariant failure.
52    #[error("invalid tensor: {0}")]
53    InvalidTensor(String),
54
55    /// A bounded read would exceed its decoded-allocation budget.
56    #[error(
57        "read allocation limit exceeded at {context}: limit {limit} bytes, used {used} bytes, requested {requested} bytes"
58    )]
59    ReadLimitExceeded {
60        /// Dataset or synthesized field being charged.
61        context: String,
62        /// Configured decoded-allocation limit in bytes.
63        limit: usize,
64        /// Bytes already charged by earlier allocations.
65        used: usize,
66        /// Bytes requested by the allocation that was rejected.
67        requested: usize,
68    },
69
70    /// Filesystem or HDF5 library failure while reading or writing a `.nir` file.
71    ///
72    /// The underlying `hdf5::Error` is rendered into the message rather than
73    /// carried, so [`NirError`] stays `Clone + Eq`.
74    #[error("io error: {0}")]
75    Io(String),
76}
77
78/// Render an HDF5 library failure into [`NirError::Io`].
79///
80/// The message is flattened into a `String` because `hdf5::Error` is neither
81/// `Clone` nor `Eq`, and this enum is both.
82#[cfg(feature = "hdf5")]
83impl From<hdf5::Error> for NirError {
84    fn from(err: hdf5::Error) -> Self {
85        Self::Io(err.to_string())
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn unimplemented_display() {
95        let err = NirError::Unimplemented("hdf5 read");
96        assert_eq!(err.to_string(), "not implemented: hdf5 read");
97    }
98
99    #[test]
100    fn unknown_node_type_display() {
101        let err = NirError::UnknownNodeType("CurrLIF".into());
102        assert_eq!(err.to_string(), "unknown node type: CurrLIF");
103    }
104
105    #[test]
106    fn duplicate_node_display() {
107        let err = NirError::DuplicateNode("lif".into());
108        assert_eq!(err.to_string(), "duplicate node: lif");
109    }
110
111    #[test]
112    fn missing_node_display() {
113        let err = NirError::MissingNode("missing".into());
114        assert_eq!(err.to_string(), "missing node: missing");
115    }
116
117    #[test]
118    fn duplicate_edge_display() {
119        let err = NirError::DuplicateEdge("a".into(), "b".into());
120        assert_eq!(err.to_string(), "duplicate edge: (a, b)");
121    }
122
123    #[test]
124    fn invalid_graph_display() {
125        let err = NirError::InvalidGraph("empty subgraph".into());
126        assert_eq!(err.to_string(), "invalid graph: empty subgraph");
127    }
128
129    #[test]
130    fn unsupported_version_display() {
131        let err = NirError::UnsupportedVersion("99.0".into());
132        assert_eq!(err.to_string(), "unsupported version: 99.0");
133    }
134
135    #[test]
136    fn missing_field_display() {
137        let err = NirError::MissingField("weight".into());
138        assert_eq!(err.to_string(), "missing field: weight");
139    }
140
141    #[test]
142    fn invalid_tensor_display() {
143        let err = NirError::InvalidTensor("shape product 4 != data len 3".into());
144        assert_eq!(
145            err.to_string(),
146            "invalid tensor: shape product 4 != data len 3"
147        );
148    }
149
150    #[test]
151    fn read_limit_display() {
152        let err = NirError::ReadLimitExceeded {
153            context: "lif.tau".into(),
154            limit: 1024,
155            used: 768,
156            requested: 512,
157        };
158        assert_eq!(
159            err.to_string(),
160            "read allocation limit exceeded at lif.tau: limit 1024 bytes, used 768 bytes, requested 512 bytes"
161        );
162    }
163
164    #[test]
165    fn io_display() {
166        let err = NirError::Io("unable to open file: model.nir".into());
167        assert_eq!(err.to_string(), "io error: unable to open file: model.nir");
168    }
169
170    #[test]
171    fn error_trait_implemented() {
172        let err: Box<dyn std::error::Error> = Box::new(NirError::Unimplemented("x"));
173        assert!(err.to_string().contains("not implemented"));
174    }
175}