vole_document/error.rs
1//! Typed errors with stable classes and documented CLI exit codes.
2//!
3//! Malformed data is never reported as an "internal invariant"; each failure
4//! class is distinct so callers and the CLI can react precisely.
5
6use core::fmt;
7
8/// Convenience alias for the crate's fallible operations.
9pub type Result<T> = core::result::Result<T, Error>;
10
11/// Stable error classes. These distinguish failure *kinds* rather than prose.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ErrorClass {
14 /// Host I/O failure (read/write/rename/fsync).
15 Io,
16 /// Command-line or API misuse.
17 Usage,
18 /// The container framing/header is malformed.
19 InvalidContainer,
20 /// The container declares a version this build does not implement.
21 UnsupportedVersion,
22 /// The container declares a mandatory feature this build does not support.
23 UnsupportedFeature,
24 /// A digest, length, or checksum disagreed with the declared value.
25 IntegrityMismatch,
26 /// A declared or configured resource bound was exceeded.
27 ResourceLimit,
28 /// The reconstruction graph is malformed (cycles, bad references, gaps).
29 InvalidGraph,
30 /// An entropy model descriptor is malformed.
31 InvalidModel,
32 /// An entropy channel failed to decode.
33 EntropyDecode,
34 /// A PDF physical/lexical structure error (Phase 3+).
35 InvalidPdfStructure,
36 /// An exact codec-replay candidate failed (Phase 6+).
37 CodecReplay,
38 /// A referenced external store object is missing (Phase 9+).
39 MissingExternalObject,
40 /// The coverage certificate is invalid (gaps or overlapping authorities).
41 CoverageViolation,
42 /// Materialization completed but did not equal the source.
43 ReconstructionMismatch,
44 /// The operation was cooperatively cancelled.
45 Cancelled,
46 /// A ZIP container structure error (Phase 12+): malformed or contradictory
47 /// local/central/ZIP64 records, an ambiguous byte cover, a broken descriptor,
48 /// multi-disk layout, or a hostile member name.
49 InvalidZipStructure,
50 /// An XML part structure error (Phase 12+, reserved): malformed or forbidden
51 /// constructs (DOCTYPE/XXE, encoding tricks) in a package part.
52 InvalidXmlStructure,
53 /// An OPC/OCF package structure error (Phase 12+, reserved): a missing or
54 /// ambiguous main part, content-type abuse, `mimetype` trick, or an ambiguous
55 /// part-name identity.
56 InvalidPackageStructure,
57 /// A JSON structured-tree structure error (Phase 21.5+): a malformed token,
58 /// an unterminated string/container, a bad escape, or a trailing byte after
59 /// the single top-level value.
60 InvalidJsonStructure,
61 /// A YAML structured-tree structure error (Phase 21.6+): a malformed or
62 /// unsupported construct (a bad indentation, an unterminated quoted scalar, a
63 /// declined directive/explicit key/flow-context block scalar).
64 InvalidYamlStructure,
65 /// A CSV/TSV tabular structure error (Phase 21.7+): an unterminated quoted
66 /// field, a byte after a closing quote that is neither a delimiter nor an end
67 /// of record, or an input that is not a table under either delimiter.
68 InvalidCsvStructure,
69 /// A Markdown prose structure error (Phase 21.8+): a malformed or declined
70 /// construct (an unterminated fenced code block close, a bad reference
71 /// definition, or an input that carries no structural mark).
72 InvalidMarkdownStructure,
73 /// An HTML document structure error (Phase 21.10+): a forbidden construct
74 /// (a DOCTYPE with an internal subset), a non-UTF-8/UTF-16/NUL byte string, or
75 /// an input that carries no HTML structure. Malformed HTML is otherwise
76 /// **recovered** by the adapter, never a panic.
77 InvalidHtmlStructure,
78 /// A TOML structure error (Phase 21.11+): a malformed token, an unterminated
79 /// string/array/inline table, an invalid number or escape, or a violation of
80 /// TOML's duplicate-key/redefinition rules (which this adapter **enforces**).
81 InvalidTomlStructure,
82 /// A JSONL/NDJSON structure error (Phase 21.12+): a non-blank line that is not
83 /// exactly one JSON value, too few record lines to distinguish the source from a
84 /// single JSON value, or a non-newline-separated bag of JSON values.
85 InvalidJsonlStructure,
86 /// An EML/MIME message structure error (Phase 21.13+): no RFC 5322 header block,
87 /// a malformed header line, a `multipart/*` without a boundary, or an invalid
88 /// quoted-printable/base64 escape.
89 InvalidEmlStructure,
90 /// An Apache Parquet structure error (Phase 21.14+): missing/inconsistent `PAR1`
91 /// magic, an inconsistent footer length, a truncated Thrift-Compact footer, a
92 /// page or column-chunk span outside the file, or a value count that disagrees
93 /// with the page layout.
94 InvalidParquetStructure,
95 /// An Apache Arrow IPC structure error (Phase 21.16+): missing/inconsistent
96 /// `ARROW1` magic, a truncated or out-of-bounds Flatbuffers metadata table, an
97 /// invalid message framing, or a buffer span outside the message body.
98 InvalidArrowStructure,
99 /// A JSON5/JSONC structure error (Phase 21.17.1+): a malformed token, an
100 /// unterminated string/comment/container, a bad escape or identifier, or a
101 /// trailing byte after the single top-level value.
102 InvalidJson5Structure,
103 /// A CBOR structure error (Phase 21.18+): a malformed head (reserved additional
104 /// information, a non-minimal simple value), a truncated item, trailing bytes
105 /// after the single top-level item, an unterminated indefinite-length item, a
106 /// map key with no value, or a text string that is not valid UTF-8.
107 InvalidCborStructure,
108 /// A MessagePack structure error (Phase 21.19+): a malformed head (the never-used
109 /// `0xc1` byte), a truncated item, trailing bytes after the single top-level item,
110 /// a map key with no value, an over-long declared length, or a `str` that is not
111 /// valid UTF-8.
112 InvalidMsgpackStructure,
113 /// A config-family structure error (Phase 21.20+): a line that is not a valid
114 /// INI/`.env`/`.properties` construct under the detected dialect, an unterminated
115 /// quoted value, or a source that carries no config-family dialect signal at all
116 /// (so it stays `Opaque`).
117 InvalidConfigStructure,
118 /// An RSS/Atom feed structure error (Phase 21.21+): a source that is not
119 /// well-formed XML, an `<rss>` root with no `<channel>`, an Atom `<feed>` whose
120 /// namespace is not the Atom URI or which carries no `<entry>`, or a root that is
121 /// not an RSS/Atom feed (so it stays `Xml`/`Opaque`).
122 InvalidFeedStructure,
123 /// A GeoJSON structure error (Phase 21.22+): a source that is not a single JSON
124 /// value, a root that is not an object with a string `"type"` member, a `"type"`
125 /// that is not one of the nine GeoJSON type names, or a type-consistent object
126 /// whose shape is not consistent (a geometry with no array `coordinates`/`geometries`,
127 /// a `Feature` with no `geometry`/`properties`, a `FeatureCollection` with no array
128 /// `features`) — so it stays `Json`/`Opaque` rather than being claimed.
129 InvalidGeojsonStructure,
130 /// A KML/GPX geospatial structure error (Phase 21.23+): a source that is not
131 /// well-formed XML, a `<kml>` root not in the KML namespace or with no
132 /// `Document`/`Folder`/`Placemark` child, a `<gpx>` root not in the GPX namespace
133 /// or with no `metadata`/`wpt`/`rte`/`trk` child, or a root that is not a KML/GPX
134 /// document (so it stays `Xml`/`Opaque`).
135 InvalidGisStructure,
136 /// A Jupyter notebook structure error (Phase 21.24+): a source that is not a single
137 /// JSON value, a root that is not an object with a plain non-negative integer-literal
138 /// `nbformat` (≥ 1) and an array `cells`, a cell that is not an object with a string
139 /// `cell_type`, a present-but-malformed recognized cell field (a `source` that is not
140 /// a string or array of strings, an `execution_count` that is neither a number nor
141 /// `null`, a non-object `metadata`/`attachments`, an `outputs` that is not an array of
142 /// objects with a string `output_type`), or a non-object root `metadata` — so it stays
143 /// `Json`/`Opaque` rather than being claimed.
144 InvalidNotebookStructure,
145 /// A fixed-width structure error (Phase 21.25+): a source that carries no strong
146 /// fixed-width signal (fewer than the minimum sampled records, records that do not
147 /// share an identical byte width, fewer than two non-empty columns, or an interior
148 /// column gap narrower than the minimum), or a document whose later records
149 /// disagree with the sampled layout — so it stays `Opaque` rather than being
150 /// claimed.
151 InvalidFixedWidthStructure,
152 /// A reStructuredText structure error (Phase 21.26.1+): a source that carries no
153 /// reST-specific signal (an explicit markup start, a grid/simple table, a field
154 /// list, or an admissible section adornment), a malformed directive/marker, or an
155 /// input that exceeds a cap — so it stays `Opaque` rather than being claimed.
156 InvalidRstStructure,
157 /// An AsciiDoc structure error (Phase 21.26.2+): a source that carries no
158 /// AsciiDoc-specific signal (a document title followed by a block, a `==`+ section,
159 /// a `|===` table, a complete delimited block, or a block attribute line followed by
160 /// a block), a malformed attribute/list/macro marker, or an input that exceeds a cap
161 /// — so it stays `Opaque` rather than being claimed.
162 InvalidAsciidocStructure,
163 /// An MDX structure error (Phase 21.26.3+): a source that carries no MDX-specific
164 /// signal (a top-level ESM statement, a JSX component/fragment/attribute, or a
165 /// whole-line block expression), an unbalanced/ambiguous brace in a non-code
166 /// position, a malformed JSX/ESM construct, or an input that exceeds a cap — so it
167 /// stays `Opaque` rather than being claimed.
168 InvalidMdxStructure,
169 /// An MHTML structure error (Phase 21.27): a source that is not a well-formed MIME
170 /// message, a root that is not `multipart/related`, a `multipart/related` document
171 /// that carries no MHTML-specific signal (`Snapshot-Content-Location`/`Content-Base`,
172 /// or a `From`+`Subject` envelope with a `text/html` part), a root part that cannot
173 /// be decoded/parsed as HTML, or an input that exceeds a cap — so it stays
174 /// `Eml`/`Html`/`Opaque` rather than being claimed.
175 InvalidMhtmlStructure,
176 /// A syslog / log-stream structure error (Phase 21.28): a source that is not a log
177 /// stream (fewer than two non-blank record lines, or a non-blank line that matches
178 /// no recognized dialect — RFC 5424 syslog, RFC 3164 (BSD) syslog, or a generic
179 /// application log line), or an input that exceeds a cap — so it stays `Opaque`
180 /// rather than being claimed.
181 InvalidLogstreamStructure,
182 /// A package-metadata structure error (Phase 21.29): a JSON/TOML source that is not
183 /// a recognized package-manifest shape (an npm `package.json`/`package-lock.json`, a
184 /// Cargo `Cargo.toml`/`Cargo.lock`, or a Python `pyproject.toml`), a malformed
185 /// manifest, or an input that exceeds a cap — so it stays `Json`/`Toml`/`Opaque`
186 /// rather than being claimed.
187 InvalidPkgmetaStructure,
188 /// An API-specification structure error (Phase 21.30): a JSON source that is not a
189 /// recognized API spec (a JSON Schema, an OpenAPI 3.x, a Swagger 2.0, or an
190 /// AsyncAPI document), a root object carrying no strong marker, a malformed source,
191 /// or an input that exceeds a cap — so it stays `Json`/`Opaque` rather than being
192 /// claimed.
193 InvalidApispecStructure,
194 /// A bibliography structure error (Phase 21.31): a source that is not a recognized
195 /// bibliography (a BibTeX entry sequence, an RIS `TY`/`ER` entry sequence, or a
196 /// CSL-JSON item array/object carrying a string `id` and a string `type`), a
197 /// malformed entry/field, or an input that exceeds a cap — so it stays
198 /// `Json`/`Opaque` rather than being claimed.
199 InvalidBibStructure,
200 /// An RTF (Rich Text Format) structure error (Phase 21.32): a source that begins
201 /// with `{\rtf<digit>` but is not well-formed under the bounded group-brace
202 /// grammar (an unbalanced brace, a trailing backslash, an over-long control word,
203 /// or trailing bytes after the root group) — so it stays `Opaque` rather than being
204 /// claimed as a derived model. Detection and the exact leaf are unaffected.
205 InvalidRtfStructure,
206 /// A generic archive-container structure error (Phase 21.33): a source that is
207 /// neither a valid ZIP (magic + EOCD + coherent central directory) nor a valid
208 /// TAR (a strong header whose checksum matches), or a container whose TAR
209 /// member is truncated / has a bad checksum / has a malformed PAX record — so
210 /// it stays `Opaque` rather than being claimed as a derived model. Detection
211 /// and the whole-source exact leaf are unaffected.
212 InvalidContainerStructure,
213 /// A scientific-container structure error (Phase 21.34): a NetCDF classic
214 /// (CDF-1/CDF-2) or HDF5 source that is structurally malformed — a bad tag, an
215 /// out-of-bounds declared offset, a truncated header/message, an unsatisfied
216 /// object-header message count, or a bad local heap / B-tree / symbol table — so
217 /// it stays `Opaque` rather than being claimed as a derived model. Detection and
218 /// the whole-source exact leaf are unaffected.
219 InvalidHdfStructure,
220 /// A CFBF/OLE2 structure error (Phase 21.35): a compound-file source that is
221 /// structurally malformed — an inconsistent header, a bad sector shift, an
222 /// out-of-range sector ID, a cyclic/overlapping FAT or MiniFAT chain, an invalid
223 /// directory object type, a truncated stream, or a header/sector-size mismatch —
224 /// so it stays `Opaque` rather than being claimed as a derived model. Detection
225 /// and the whole-source exact leaf are unaffected.
226 InvalidCfbfStructure,
227 /// A legacy Word 97–2003 (`.doc`, MS-DOC) structure error (Phase 26.1): a
228 /// compound file whose `WordDocument` stream begins with the Word FIB magic but
229 /// whose semantic body is malformed or outside the documented subset — a bad
230 /// `wIdent`, an implausible `nFib`, an out-of-range `fcClx`/`lcbClx`, a CLX past
231 /// the table stream, a `Pcdt` with no piece table, a CP array that is not
232 /// monotonic (or does not start at 0), a PCD `fc` outside the `WordDocument`
233 /// stream, or a declared section total that disagrees with the piece table — so
234 /// it declines typed rather than being claimed as a derived model. Detection and
235 /// the whole-source exact leaf are unaffected.
236 InvalidDocStructure,
237 /// A legacy Excel 97–2003 (`.xls`, BIFF8) structure error (Phase 26.2): a compound
238 /// file whose `Workbook`/`Book` stream begins with a BIFF8 `BOF` but whose semantic
239 /// body is malformed or outside the documented subset — a bad `BOF` version, an
240 /// unrecognized substream type, a leading substream that is not workbook globals, a
241 /// truncated record, an out-of-range `BOUNDSHEET` position, an `SST` count mismatch
242 /// or a malformed `CONTINUE`, or a `FORMULA` string result with no `STRING` record —
243 /// so it declines typed rather than being claimed as a derived model. Detection and
244 /// the whole-source exact leaf are unaffected.
245 InvalidXlsStructure,
246 /// A legacy PowerPoint 97–2003 (`.ppt`, MS-PPT) structure error (Phase 26.3): a
247 /// compound file whose `PowerPoint Document` stream begins with a `Document`
248 /// record but whose semantic body is malformed or outside the documented subset —
249 /// a truncated record header, a `recLen` that overruns the stream, a record that
250 /// overruns its enclosing container, an odd-length `TextCharsAtom`, a text atom
251 /// with no preceding `SlidePersistAtom`, or a `Current User` stream whose declared
252 /// `UserEditAtom` offset does not resolve — so it declines typed rather than being
253 /// claimed as a derived model. Detection and the whole-source exact leaf are
254 /// unaffected.
255 InvalidPptStructure,
256 /// A legacy Outlook message (`.msg`, MS-OXMSG) structure error (Phase 26.4): a
257 /// compound file whose root has a `__properties_version1.0` stream but whose
258 /// semantic body is malformed or outside the documented subset — a
259 /// `__properties_version1.0` shorter than its header, a fixed property entry that
260 /// overruns the stream, a `__substg1.0_*` name that is not `PPPPTTTT` hex, a
261 /// `__substg1.0_*` stream with an unsupported property type or that is not
262 /// referenced by any fixed entry, a sub-storage without its own
263 /// `__properties_version1.0`, or a declared `RecipientCount`/`AttachmentCount`
264 /// that disagrees with the sub-storages — so it declines typed rather than being
265 /// claimed as a derived model. Detection and the whole-source exact leaf are
266 /// unaffected.
267 InvalidMsgStructure,
268 /// A bug in this implementation; never a description of malformed input.
269 InternalInvariant,
270}
271
272impl ErrorClass {
273 /// Stable, documented CLI exit code for this class.
274 pub const fn exit_code(self) -> i32 {
275 match self {
276 ErrorClass::Io => 3,
277 ErrorClass::Usage => 2,
278 ErrorClass::InvalidContainer => 4,
279 ErrorClass::UnsupportedVersion => 5,
280 ErrorClass::UnsupportedFeature => 6,
281 ErrorClass::IntegrityMismatch => 7,
282 ErrorClass::ResourceLimit => 8,
283 ErrorClass::InvalidGraph => 9,
284 ErrorClass::InvalidModel => 10,
285 ErrorClass::EntropyDecode => 11,
286 ErrorClass::InvalidPdfStructure => 12,
287 ErrorClass::CodecReplay => 13,
288 ErrorClass::MissingExternalObject => 14,
289 ErrorClass::CoverageViolation => 15,
290 ErrorClass::ReconstructionMismatch => 16,
291 ErrorClass::Cancelled => 17,
292 ErrorClass::InvalidZipStructure => 18,
293 ErrorClass::InvalidXmlStructure => 19,
294 ErrorClass::InvalidPackageStructure => 20,
295 ErrorClass::InvalidJsonStructure => 21,
296 ErrorClass::InvalidYamlStructure => 22,
297 ErrorClass::InvalidCsvStructure => 23,
298 ErrorClass::InvalidMarkdownStructure => 24,
299 ErrorClass::InvalidHtmlStructure => 25,
300 ErrorClass::InvalidTomlStructure => 26,
301 ErrorClass::InvalidJsonlStructure => 27,
302 ErrorClass::InvalidEmlStructure => 28,
303 ErrorClass::InvalidParquetStructure => 29,
304 ErrorClass::InvalidArrowStructure => 30,
305 ErrorClass::InvalidJson5Structure => 31,
306 ErrorClass::InvalidCborStructure => 32,
307 ErrorClass::InvalidMsgpackStructure => 33,
308 ErrorClass::InvalidConfigStructure => 34,
309 ErrorClass::InvalidFeedStructure => 35,
310 ErrorClass::InvalidGeojsonStructure => 36,
311 ErrorClass::InvalidGisStructure => 37,
312 ErrorClass::InvalidNotebookStructure => 38,
313 ErrorClass::InvalidFixedWidthStructure => 39,
314 ErrorClass::InvalidRstStructure => 40,
315 ErrorClass::InvalidAsciidocStructure => 41,
316 ErrorClass::InvalidMdxStructure => 42,
317 ErrorClass::InvalidMhtmlStructure => 43,
318 ErrorClass::InvalidLogstreamStructure => 44,
319 ErrorClass::InvalidPkgmetaStructure => 45,
320 ErrorClass::InvalidApispecStructure => 46,
321 ErrorClass::InvalidBibStructure => 47,
322 ErrorClass::InvalidRtfStructure => 48,
323 ErrorClass::InvalidContainerStructure => 49,
324 ErrorClass::InvalidHdfStructure => 50,
325 ErrorClass::InvalidCfbfStructure => 51,
326 ErrorClass::InvalidDocStructure => 52,
327 ErrorClass::InvalidXlsStructure => 53,
328 ErrorClass::InvalidPptStructure => 54,
329 ErrorClass::InvalidMsgStructure => 55,
330 ErrorClass::InternalInvariant => 70,
331 }
332 }
333
334 /// Short stable identifier used in receipts and machine-readable output.
335 pub const fn as_str(self) -> &'static str {
336 match self {
337 ErrorClass::Io => "Io",
338 ErrorClass::Usage => "Usage",
339 ErrorClass::InvalidContainer => "InvalidContainer",
340 ErrorClass::UnsupportedVersion => "UnsupportedVersion",
341 ErrorClass::UnsupportedFeature => "UnsupportedFeature",
342 ErrorClass::IntegrityMismatch => "IntegrityMismatch",
343 ErrorClass::ResourceLimit => "ResourceLimit",
344 ErrorClass::InvalidGraph => "InvalidGraph",
345 ErrorClass::InvalidModel => "InvalidModel",
346 ErrorClass::EntropyDecode => "EntropyDecode",
347 ErrorClass::InvalidPdfStructure => "InvalidPdfStructure",
348 ErrorClass::CodecReplay => "CodecReplay",
349 ErrorClass::MissingExternalObject => "MissingExternalObject",
350 ErrorClass::CoverageViolation => "CoverageViolation",
351 ErrorClass::ReconstructionMismatch => "ReconstructionMismatch",
352 ErrorClass::Cancelled => "Cancelled",
353 ErrorClass::InvalidZipStructure => "InvalidZipStructure",
354 ErrorClass::InvalidXmlStructure => "InvalidXmlStructure",
355 ErrorClass::InvalidPackageStructure => "InvalidPackageStructure",
356 ErrorClass::InvalidJsonStructure => "InvalidJsonStructure",
357 ErrorClass::InvalidYamlStructure => "InvalidYamlStructure",
358 ErrorClass::InvalidCsvStructure => "InvalidCsvStructure",
359 ErrorClass::InvalidMarkdownStructure => "InvalidMarkdownStructure",
360 ErrorClass::InvalidHtmlStructure => "InvalidHtmlStructure",
361 ErrorClass::InvalidTomlStructure => "InvalidTomlStructure",
362 ErrorClass::InvalidJsonlStructure => "InvalidJsonlStructure",
363 ErrorClass::InvalidEmlStructure => "InvalidEmlStructure",
364 ErrorClass::InvalidParquetStructure => "InvalidParquetStructure",
365 ErrorClass::InvalidArrowStructure => "InvalidArrowStructure",
366 ErrorClass::InvalidJson5Structure => "InvalidJson5Structure",
367 ErrorClass::InvalidCborStructure => "InvalidCborStructure",
368 ErrorClass::InvalidMsgpackStructure => "InvalidMsgpackStructure",
369 ErrorClass::InvalidConfigStructure => "InvalidConfigStructure",
370 ErrorClass::InvalidFeedStructure => "InvalidFeedStructure",
371 ErrorClass::InvalidGeojsonStructure => "InvalidGeojsonStructure",
372 ErrorClass::InvalidGisStructure => "InvalidGisStructure",
373 ErrorClass::InvalidNotebookStructure => "InvalidNotebookStructure",
374 ErrorClass::InvalidFixedWidthStructure => "InvalidFixedWidthStructure",
375 ErrorClass::InvalidRstStructure => "InvalidRstStructure",
376 ErrorClass::InvalidAsciidocStructure => "InvalidAsciidocStructure",
377 ErrorClass::InvalidMdxStructure => "InvalidMdxStructure",
378 ErrorClass::InvalidMhtmlStructure => "InvalidMhtmlStructure",
379 ErrorClass::InvalidLogstreamStructure => "InvalidLogstreamStructure",
380 ErrorClass::InvalidPkgmetaStructure => "InvalidPkgmetaStructure",
381 ErrorClass::InvalidApispecStructure => "InvalidApispecStructure",
382 ErrorClass::InvalidBibStructure => "InvalidBibStructure",
383 ErrorClass::InvalidRtfStructure => "InvalidRtfStructure",
384 ErrorClass::InvalidContainerStructure => "InvalidContainerStructure",
385 ErrorClass::InvalidHdfStructure => "InvalidHdfStructure",
386 ErrorClass::InvalidCfbfStructure => "InvalidCfbfStructure",
387 ErrorClass::InvalidDocStructure => "InvalidDocStructure",
388 ErrorClass::InvalidXlsStructure => "InvalidXlsStructure",
389 ErrorClass::InvalidPptStructure => "InvalidPptStructure",
390 ErrorClass::InvalidMsgStructure => "InvalidMsgStructure",
391 ErrorClass::InternalInvariant => "InternalInvariant",
392 }
393 }
394}
395
396/// A typed, classified error.
397#[derive(Debug, Clone)]
398pub struct Error {
399 class: ErrorClass,
400 message: String,
401}
402
403impl Error {
404 /// Construct an error of the given class with a human-readable message.
405 pub fn new(class: ErrorClass, message: impl Into<String>) -> Self {
406 Error {
407 class,
408 message: message.into(),
409 }
410 }
411
412 /// The failure class.
413 pub fn class(&self) -> ErrorClass {
414 self.class
415 }
416
417 /// The stable CLI exit code for this error's class.
418 pub fn exit_code(&self) -> i32 {
419 self.class.exit_code()
420 }
421
422 /// The human-readable message.
423 pub fn message(&self) -> &str {
424 &self.message
425 }
426}
427
428impl fmt::Display for Error {
429 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
430 write!(f, "{}: {}", self.class.as_str(), self.message)
431 }
432}
433
434impl std::error::Error for Error {}
435
436impl From<std::io::Error> for Error {
437 fn from(e: std::io::Error) -> Self {
438 Error::new(ErrorClass::Io, e.to_string())
439 }
440}
441
442macro_rules! ctor {
443 ($name:ident, $class:ident) => {
444 /// Construct an error of this class.
445 pub fn $name(message: impl Into<String>) -> Self {
446 Error::new(ErrorClass::$class, message)
447 }
448 };
449}
450
451impl Error {
452 ctor!(io, Io);
453 ctor!(usage, Usage);
454 ctor!(invalid_container, InvalidContainer);
455 ctor!(unsupported_version, UnsupportedVersion);
456 ctor!(unsupported_feature, UnsupportedFeature);
457 ctor!(integrity_mismatch, IntegrityMismatch);
458 ctor!(resource_limit, ResourceLimit);
459 ctor!(invalid_graph, InvalidGraph);
460 ctor!(invalid_model, InvalidModel);
461 ctor!(entropy_decode, EntropyDecode);
462 ctor!(invalid_pdf_structure, InvalidPdfStructure);
463 ctor!(codec_replay, CodecReplay);
464 ctor!(missing_external_object, MissingExternalObject);
465 ctor!(coverage_violation, CoverageViolation);
466 ctor!(reconstruction_mismatch, ReconstructionMismatch);
467 ctor!(cancelled, Cancelled);
468 ctor!(invalid_zip_structure, InvalidZipStructure);
469 ctor!(invalid_xml_structure, InvalidXmlStructure);
470 ctor!(invalid_package_structure, InvalidPackageStructure);
471 ctor!(invalid_json_structure, InvalidJsonStructure);
472 ctor!(invalid_yaml_structure, InvalidYamlStructure);
473 ctor!(invalid_csv_structure, InvalidCsvStructure);
474 ctor!(invalid_markdown_structure, InvalidMarkdownStructure);
475 ctor!(invalid_html_structure, InvalidHtmlStructure);
476 ctor!(invalid_toml_structure, InvalidTomlStructure);
477 ctor!(invalid_jsonl_structure, InvalidJsonlStructure);
478 ctor!(invalid_eml_structure, InvalidEmlStructure);
479 ctor!(invalid_parquet_structure, InvalidParquetStructure);
480 ctor!(invalid_arrow_structure, InvalidArrowStructure);
481 ctor!(invalid_json5_structure, InvalidJson5Structure);
482 ctor!(invalid_cbor_structure, InvalidCborStructure);
483 ctor!(invalid_msgpack_structure, InvalidMsgpackStructure);
484 ctor!(invalid_config_structure, InvalidConfigStructure);
485 ctor!(invalid_feed_structure, InvalidFeedStructure);
486 ctor!(invalid_geojson_structure, InvalidGeojsonStructure);
487 ctor!(invalid_gis_structure, InvalidGisStructure);
488 ctor!(invalid_notebook_structure, InvalidNotebookStructure);
489 ctor!(invalid_fixedwidth_structure, InvalidFixedWidthStructure);
490 ctor!(invalid_rst_structure, InvalidRstStructure);
491 ctor!(invalid_asciidoc_structure, InvalidAsciidocStructure);
492 ctor!(invalid_mdx_structure, InvalidMdxStructure);
493 ctor!(invalid_mhtml_structure, InvalidMhtmlStructure);
494 ctor!(invalid_logstream_structure, InvalidLogstreamStructure);
495 ctor!(invalid_pkgmeta_structure, InvalidPkgmetaStructure);
496 ctor!(invalid_apispec_structure, InvalidApispecStructure);
497 ctor!(invalid_bib_structure, InvalidBibStructure);
498 ctor!(invalid_rtf_structure, InvalidRtfStructure);
499 ctor!(invalid_container_structure, InvalidContainerStructure);
500 ctor!(invalid_hdf_structure, InvalidHdfStructure);
501 ctor!(invalid_cfbf_structure, InvalidCfbfStructure);
502 ctor!(invalid_doc_structure, InvalidDocStructure);
503 ctor!(invalid_xls_structure, InvalidXlsStructure);
504 ctor!(invalid_ppt_structure, InvalidPptStructure);
505 ctor!(invalid_msg_structure, InvalidMsgStructure);
506 ctor!(internal_invariant, InternalInvariant);
507}
508
509#[cfg(test)]
510mod tests {
511 use super::*;
512
513 #[test]
514 fn exit_codes_are_distinct_and_stable() {
515 // A representative set; guards accidental collisions.
516 let classes = [
517 ErrorClass::Io,
518 ErrorClass::Usage,
519 ErrorClass::InvalidContainer,
520 ErrorClass::UnsupportedVersion,
521 ErrorClass::UnsupportedFeature,
522 ErrorClass::IntegrityMismatch,
523 ErrorClass::ResourceLimit,
524 ErrorClass::InvalidGraph,
525 ErrorClass::InvalidModel,
526 ErrorClass::EntropyDecode,
527 ErrorClass::InvalidPdfStructure,
528 ErrorClass::CodecReplay,
529 ErrorClass::MissingExternalObject,
530 ErrorClass::CoverageViolation,
531 ErrorClass::ReconstructionMismatch,
532 ErrorClass::Cancelled,
533 ErrorClass::InvalidZipStructure,
534 ErrorClass::InvalidXmlStructure,
535 ErrorClass::InvalidPackageStructure,
536 ErrorClass::InvalidJsonStructure,
537 ErrorClass::InvalidYamlStructure,
538 ErrorClass::InvalidCsvStructure,
539 ErrorClass::InvalidMarkdownStructure,
540 ErrorClass::InvalidHtmlStructure,
541 ErrorClass::InvalidTomlStructure,
542 ErrorClass::InternalInvariant,
543 ];
544 let mut codes: Vec<i32> = classes.iter().map(|c| c.exit_code()).collect();
545 let n = codes.len();
546 codes.sort_unstable();
547 codes.dedup();
548 assert_eq!(codes.len(), n, "exit codes must be unique");
549 assert_eq!(ErrorClass::InvalidContainer.exit_code(), 4);
550 }
551
552 #[test]
553 fn display_includes_class() {
554 let e = Error::invalid_container("bad magic");
555 assert_eq!(e.to_string(), "InvalidContainer: bad magic");
556 }
557}