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}
87
88impl Error {
89    pub fn code(&self) -> &'static str {
90        match self {
91            Error::UnsupportedVersion { .. } => "E001",
92            Error::DuplicateName { .. } => "E002",
93            Error::UnknownReference { .. } => "E003",
94            Error::UnknownOutput { .. } => "E004",
95            Error::Cycle { .. } => "E005",
96            Error::Arity { .. } => "E006",
97            Error::UnsupportedOp { .. } => "E007",
98            Error::DataLength { .. } => "E008",
99            Error::InvalidShape { .. } => "E009",
100            Error::LimitExceeded { .. } => "E010",
101            Error::NoOutputs => "E011",
102            Error::NonFinite { .. } => "E012",
103            Error::InputCount { .. } => "E013",
104            Error::InputShape { .. } => "E014",
105            Error::Verify(_) => "E015",
106            Error::TensorLimit { .. } => "E016",
107            Error::ShapeMismatch { .. } => "E017",
108            Error::MissingAttribute { .. } => "E018",
109            Error::UnexpectedAttribute { .. } => "E019",
110            Error::InvalidAttribute { .. } => "E020",
111        }
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118    use std::collections::HashSet;
119
120    fn one_of_each() -> Vec<Error> {
121        vec![
122            Error::UnsupportedVersion {
123                found: 2,
124                supported: 1,
125            },
126            Error::DuplicateName { name: "a".into() },
127            Error::UnknownReference {
128                node: "n".into(),
129                name: "a".into(),
130            },
131            Error::UnknownOutput { name: "a".into() },
132            Error::Cycle { node: "a".into() },
133            Error::Arity {
134                node: "n".into(),
135                op: "Relu",
136                expected: 1,
137                found: 2,
138            },
139            Error::UnsupportedOp {
140                node: "n".into(),
141                op: "Gelu".into(),
142            },
143            Error::DataLength {
144                name: "c".into(),
145                shape: vec![2],
146                expected: 2,
147                found: 1,
148            },
149            Error::InvalidShape {
150                name: "x".into(),
151                shape: vec![0],
152                reason: "dimensions must be at least 1",
153            },
154            Error::LimitExceeded {
155                what: "values",
156                found: 3,
157                limit: 2,
158            },
159            Error::NoOutputs,
160            Error::NonFinite { name: "c".into() },
161            Error::InputCount {
162                expected: 1,
163                found: 0,
164            },
165            Error::InputShape {
166                name: "x".into(),
167                expected: vec![2],
168                found: vec![3],
169            },
170            Error::TensorLimit {
171                name: "x".into(),
172                what: "tensor rank",
173                found: 9,
174                limit: 8,
175            },
176            Error::ShapeMismatch {
177                node: "n".into(),
178                reason: "r".into(),
179            },
180            Error::MissingAttribute {
181                node: "n".into(),
182                op: "Reshape",
183                attr: "shape",
184            },
185            Error::UnexpectedAttribute {
186                node: "n".into(),
187                op: "Relu",
188                attr: "shape",
189            },
190            Error::InvalidAttribute {
191                node: "n".into(),
192                op: "Reshape",
193                attr: "shape",
194                reason: "r".into(),
195            },
196            Error::Verify("bad".into()),
197        ]
198    }
199
200    #[test]
201    fn codes_are_unique_and_stable_format() {
202        let errors = one_of_each();
203        let codes: HashSet<&str> = errors.iter().map(Error::code).collect();
204        assert_eq!(codes.len(), errors.len());
205        assert!(codes.iter().all(|c| c.len() == 4 && c.starts_with('E')));
206    }
207
208    #[test]
209    fn messages_name_the_offender() {
210        assert_eq!(
211            Error::Cycle { node: "a".into() }.to_string(),
212            "graph contains a cycle through node `a`"
213        );
214        assert_eq!(
215            Error::UnknownReference {
216                node: "n".into(),
217                name: "q".into()
218            }
219            .to_string(),
220            "node `n` references unknown value `q`"
221        );
222    }
223}