1use core::fmt;
7
8pub type Result<T> = core::result::Result<T, Error>;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ErrorClass {
14 Io,
16 Usage,
18 InvalidContainer,
20 UnsupportedVersion,
22 UnsupportedFeature,
24 IntegrityMismatch,
26 ResourceLimit,
28 InvalidGraph,
30 InvalidModel,
32 EntropyDecode,
34 InvalidPdfStructure,
36 CodecReplay,
38 MissingExternalObject,
40 CoverageViolation,
42 ReconstructionMismatch,
44 Cancelled,
46 InvalidZipStructure,
50 InvalidXmlStructure,
53 InvalidPackageStructure,
57 InvalidJsonStructure,
61 InvalidYamlStructure,
65 InvalidCsvStructure,
69 InternalInvariant,
71}
72
73impl ErrorClass {
74 pub const fn exit_code(self) -> i32 {
76 match self {
77 ErrorClass::Io => 3,
78 ErrorClass::Usage => 2,
79 ErrorClass::InvalidContainer => 4,
80 ErrorClass::UnsupportedVersion => 5,
81 ErrorClass::UnsupportedFeature => 6,
82 ErrorClass::IntegrityMismatch => 7,
83 ErrorClass::ResourceLimit => 8,
84 ErrorClass::InvalidGraph => 9,
85 ErrorClass::InvalidModel => 10,
86 ErrorClass::EntropyDecode => 11,
87 ErrorClass::InvalidPdfStructure => 12,
88 ErrorClass::CodecReplay => 13,
89 ErrorClass::MissingExternalObject => 14,
90 ErrorClass::CoverageViolation => 15,
91 ErrorClass::ReconstructionMismatch => 16,
92 ErrorClass::Cancelled => 17,
93 ErrorClass::InvalidZipStructure => 18,
94 ErrorClass::InvalidXmlStructure => 19,
95 ErrorClass::InvalidPackageStructure => 20,
96 ErrorClass::InvalidJsonStructure => 21,
97 ErrorClass::InvalidYamlStructure => 22,
98 ErrorClass::InvalidCsvStructure => 23,
99 ErrorClass::InternalInvariant => 70,
100 }
101 }
102
103 pub const fn as_str(self) -> &'static str {
105 match self {
106 ErrorClass::Io => "Io",
107 ErrorClass::Usage => "Usage",
108 ErrorClass::InvalidContainer => "InvalidContainer",
109 ErrorClass::UnsupportedVersion => "UnsupportedVersion",
110 ErrorClass::UnsupportedFeature => "UnsupportedFeature",
111 ErrorClass::IntegrityMismatch => "IntegrityMismatch",
112 ErrorClass::ResourceLimit => "ResourceLimit",
113 ErrorClass::InvalidGraph => "InvalidGraph",
114 ErrorClass::InvalidModel => "InvalidModel",
115 ErrorClass::EntropyDecode => "EntropyDecode",
116 ErrorClass::InvalidPdfStructure => "InvalidPdfStructure",
117 ErrorClass::CodecReplay => "CodecReplay",
118 ErrorClass::MissingExternalObject => "MissingExternalObject",
119 ErrorClass::CoverageViolation => "CoverageViolation",
120 ErrorClass::ReconstructionMismatch => "ReconstructionMismatch",
121 ErrorClass::Cancelled => "Cancelled",
122 ErrorClass::InvalidZipStructure => "InvalidZipStructure",
123 ErrorClass::InvalidXmlStructure => "InvalidXmlStructure",
124 ErrorClass::InvalidPackageStructure => "InvalidPackageStructure",
125 ErrorClass::InvalidJsonStructure => "InvalidJsonStructure",
126 ErrorClass::InvalidYamlStructure => "InvalidYamlStructure",
127 ErrorClass::InvalidCsvStructure => "InvalidCsvStructure",
128 ErrorClass::InternalInvariant => "InternalInvariant",
129 }
130 }
131}
132
133#[derive(Debug, Clone)]
135pub struct Error {
136 class: ErrorClass,
137 message: String,
138}
139
140impl Error {
141 pub fn new(class: ErrorClass, message: impl Into<String>) -> Self {
143 Error {
144 class,
145 message: message.into(),
146 }
147 }
148
149 pub fn class(&self) -> ErrorClass {
151 self.class
152 }
153
154 pub fn exit_code(&self) -> i32 {
156 self.class.exit_code()
157 }
158
159 pub fn message(&self) -> &str {
161 &self.message
162 }
163}
164
165impl fmt::Display for Error {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 write!(f, "{}: {}", self.class.as_str(), self.message)
168 }
169}
170
171impl std::error::Error for Error {}
172
173impl From<std::io::Error> for Error {
174 fn from(e: std::io::Error) -> Self {
175 Error::new(ErrorClass::Io, e.to_string())
176 }
177}
178
179macro_rules! ctor {
180 ($name:ident, $class:ident) => {
181 pub fn $name(message: impl Into<String>) -> Self {
183 Error::new(ErrorClass::$class, message)
184 }
185 };
186}
187
188impl Error {
189 ctor!(io, Io);
190 ctor!(usage, Usage);
191 ctor!(invalid_container, InvalidContainer);
192 ctor!(unsupported_version, UnsupportedVersion);
193 ctor!(unsupported_feature, UnsupportedFeature);
194 ctor!(integrity_mismatch, IntegrityMismatch);
195 ctor!(resource_limit, ResourceLimit);
196 ctor!(invalid_graph, InvalidGraph);
197 ctor!(invalid_model, InvalidModel);
198 ctor!(entropy_decode, EntropyDecode);
199 ctor!(invalid_pdf_structure, InvalidPdfStructure);
200 ctor!(codec_replay, CodecReplay);
201 ctor!(missing_external_object, MissingExternalObject);
202 ctor!(coverage_violation, CoverageViolation);
203 ctor!(reconstruction_mismatch, ReconstructionMismatch);
204 ctor!(cancelled, Cancelled);
205 ctor!(invalid_zip_structure, InvalidZipStructure);
206 ctor!(invalid_xml_structure, InvalidXmlStructure);
207 ctor!(invalid_package_structure, InvalidPackageStructure);
208 ctor!(invalid_json_structure, InvalidJsonStructure);
209 ctor!(invalid_yaml_structure, InvalidYamlStructure);
210 ctor!(invalid_csv_structure, InvalidCsvStructure);
211 ctor!(internal_invariant, InternalInvariant);
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217
218 #[test]
219 fn exit_codes_are_distinct_and_stable() {
220 let classes = [
222 ErrorClass::Io,
223 ErrorClass::Usage,
224 ErrorClass::InvalidContainer,
225 ErrorClass::UnsupportedVersion,
226 ErrorClass::UnsupportedFeature,
227 ErrorClass::IntegrityMismatch,
228 ErrorClass::ResourceLimit,
229 ErrorClass::InvalidGraph,
230 ErrorClass::InvalidModel,
231 ErrorClass::EntropyDecode,
232 ErrorClass::InvalidPdfStructure,
233 ErrorClass::CodecReplay,
234 ErrorClass::MissingExternalObject,
235 ErrorClass::CoverageViolation,
236 ErrorClass::ReconstructionMismatch,
237 ErrorClass::Cancelled,
238 ErrorClass::InvalidZipStructure,
239 ErrorClass::InvalidXmlStructure,
240 ErrorClass::InvalidPackageStructure,
241 ErrorClass::InvalidJsonStructure,
242 ErrorClass::InvalidYamlStructure,
243 ErrorClass::InvalidCsvStructure,
244 ErrorClass::InternalInvariant,
245 ];
246 let mut codes: Vec<i32> = classes.iter().map(|c| c.exit_code()).collect();
247 let n = codes.len();
248 codes.sort_unstable();
249 codes.dedup();
250 assert_eq!(codes.len(), n, "exit codes must be unique");
251 assert_eq!(ErrorClass::InvalidContainer.exit_code(), 4);
252 }
253
254 #[test]
255 fn display_includes_class() {
256 let e = Error::invalid_container("bad magic");
257 assert_eq!(e.to_string(), "InvalidContainer: bad magic");
258 }
259}