1#[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}