Skip to main content

oxfuse_core/
error.rs

1//! One error type for the whole compiler core. Every variant has a stable
2//! code so tests and users can match on it without parsing messages.
3
4#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
5pub enum Error {
6    #[error("unsupported format_version {found} (supported: {supported})")]
7    UnsupportedVersion { found: u32, supported: u32 },
8    #[error("duplicate name `{name}`")]
9    DuplicateName { name: String },
10    #[error("node `{node}` references unknown value `{name}`")]
11    UnknownReference { node: String, name: String },
12    #[error("output `{name}` does not exist")]
13    UnknownOutput { name: String },
14    #[error("graph contains a cycle through node `{node}`")]
15    Cycle { node: String },
16    #[error("node `{node}`: {op} takes {expected} input(s), got {found}")]
17    Arity {
18        node: String,
19        op: &'static str,
20        expected: usize,
21        found: usize,
22    },
23    #[error("node `{node}`: unsupported op `{op}`")]
24    UnsupportedOp { node: String, op: String },
25    #[error("`{name}`: shape {shape:?} needs {expected} value(s), got {found}")]
26    DataLength {
27        name: String,
28        shape: Vec<usize>,
29        expected: usize,
30        found: usize,
31    },
32    #[error("`{name}`: invalid shape {shape:?}: {reason}")]
33    InvalidShape {
34        name: String,
35        shape: Vec<usize>,
36        reason: &'static str,
37    },
38    #[error("limit exceeded: {what} is {found}, maximum is {limit}")]
39    LimitExceeded {
40        what: &'static str,
41        found: usize,
42        limit: usize,
43    },
44    #[error("`{name}`: {what} is {found}, maximum is {limit}")]
45    TensorLimit {
46        name: String,
47        what: &'static str,
48        found: usize,
49        limit: usize,
50    },
51    #[error("graph declares no outputs")]
52    NoOutputs,
53    #[error("`{name}` contains a non-finite value (NaN or infinity), which JSON cannot represent")]
54    NonFinite { name: String },
55    #[error("expected {expected} input tensor(s), got {found}")]
56    InputCount { expected: usize, found: usize },
57    #[error("input `{name}`: expected shape {expected:?}, got {found:?}")]
58    InputShape {
59        name: String,
60        expected: Vec<usize>,
61        found: Vec<usize>,
62    },
63    #[error("node `{node}`: {reason}")]
64    ShapeMismatch { node: String, reason: String },
65    #[error("node `{node}`: {op} requires attribute `{attr}`")]
66    MissingAttribute {
67        node: String,
68        op: &'static str,
69        attr: &'static str,
70    },
71    #[error("node `{node}`: {op} does not take attribute `{attr}`")]
72    UnexpectedAttribute {
73        node: String,
74        op: &'static str,
75        attr: &'static str,
76    },
77    #[error("node `{node}`: {op} attribute `{attr}`: {reason}")]
78    InvalidAttribute {
79        node: String,
80        op: &'static str,
81        attr: &'static str,
82        reason: String,
83    },
84    #[error("invalid graph: {0}")]
85    Verify(String),
86    #[error("backend error: {0}")]
87    Backend(String),
88}
89
90impl Error {
91    pub fn code(&self) -> &'static str {
92        match self {
93            Error::UnsupportedVersion { .. } => "E001",
94            Error::DuplicateName { .. } => "E002",
95            Error::UnknownReference { .. } => "E003",
96            Error::UnknownOutput { .. } => "E004",
97            Error::Cycle { .. } => "E005",
98            Error::Arity { .. } => "E006",
99            Error::UnsupportedOp { .. } => "E007",
100            Error::DataLength { .. } => "E008",
101            Error::InvalidShape { .. } => "E009",
102            Error::LimitExceeded { .. } => "E010",
103            Error::NoOutputs => "E011",
104            Error::NonFinite { .. } => "E012",
105            Error::InputCount { .. } => "E013",
106            Error::InputShape { .. } => "E014",
107            Error::Verify(_) => "E015",
108            Error::TensorLimit { .. } => "E016",
109            Error::ShapeMismatch { .. } => "E017",
110            Error::MissingAttribute { .. } => "E018",
111            Error::UnexpectedAttribute { .. } => "E019",
112            Error::InvalidAttribute { .. } => "E020",
113            Error::Backend(_) => "E021",
114        }
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121    use std::collections::HashSet;
122
123    fn one_of_each() -> Vec<Error> {
124        vec![
125            Error::UnsupportedVersion {
126                found: 2,
127                supported: 1,
128            },
129            Error::DuplicateName { name: "a".into() },
130            Error::UnknownReference {
131                node: "n".into(),
132                name: "a".into(),
133            },
134            Error::UnknownOutput { name: "a".into() },
135            Error::Cycle { node: "a".into() },
136            Error::Arity {
137                node: "n".into(),
138                op: "Relu",
139                expected: 1,
140                found: 2,
141            },
142            Error::UnsupportedOp {
143                node: "n".into(),
144                op: "Gelu".into(),
145            },
146            Error::DataLength {
147                name: "c".into(),
148                shape: vec![2],
149                expected: 2,
150                found: 1,
151            },
152            Error::InvalidShape {
153                name: "x".into(),
154                shape: vec![0],
155                reason: "dimensions must be at least 1",
156            },
157            Error::LimitExceeded {
158                what: "values",
159                found: 3,
160                limit: 2,
161            },
162            Error::NoOutputs,
163            Error::NonFinite { name: "c".into() },
164            Error::InputCount {
165                expected: 1,
166                found: 0,
167            },
168            Error::InputShape {
169                name: "x".into(),
170                expected: vec![2],
171                found: vec![3],
172            },
173            Error::TensorLimit {
174                name: "x".into(),
175                what: "tensor rank",
176                found: 9,
177                limit: 8,
178            },
179            Error::ShapeMismatch {
180                node: "n".into(),
181                reason: "r".into(),
182            },
183            Error::MissingAttribute {
184                node: "n".into(),
185                op: "Reshape",
186                attr: "shape",
187            },
188            Error::UnexpectedAttribute {
189                node: "n".into(),
190                op: "Relu",
191                attr: "shape",
192            },
193            Error::InvalidAttribute {
194                node: "n".into(),
195                op: "Reshape",
196                attr: "shape",
197                reason: "r".into(),
198            },
199            Error::Verify("bad".into()),
200            Error::Backend("no adapter".into()),
201        ]
202    }
203
204    #[test]
205    fn codes_are_unique_and_stable_format() {
206        let errors = one_of_each();
207        let codes: HashSet<&str> = errors.iter().map(Error::code).collect();
208        assert_eq!(codes.len(), errors.len());
209        assert!(codes.iter().all(|c| c.len() == 4 && c.starts_with('E')));
210    }
211
212    #[test]
213    fn backend_errors_carry_their_message_and_a_stable_code() {
214        let e = Error::Backend("no adapter found".into());
215        assert_eq!(e.to_string(), "backend error: no adapter found");
216        assert_eq!(e.code(), "E021");
217    }
218
219    #[test]
220    fn messages_name_the_offender() {
221        assert_eq!(
222            Error::Cycle { node: "a".into() }.to_string(),
223            "graph contains a cycle through node `a`"
224        );
225        assert_eq!(
226            Error::UnknownReference {
227                node: "n".into(),
228                name: "q".into()
229            }
230            .to_string(),
231            "node `n` references unknown value `q`"
232        );
233    }
234}