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