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