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