Skip to main content

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}