Skip to main content

hdf5_pure/
error.rs

1//! Error types for HDF5 format parsing.
2
3#[cfg(not(feature = "std"))]
4extern crate alloc;
5
6#[cfg(not(feature = "std"))]
7use alloc::string::String;
8
9#[cfg(feature = "std")]
10use std::string::String;
11
12use core::fmt;
13use core::num::NonZeroUsize;
14
15/// Errors that can occur when parsing HDF5 binary format structures.
16#[derive(Debug, Clone, PartialEq, Eq)]
17#[non_exhaustive]
18pub enum FormatError {
19    /// The HDF5 magic signature was not found at any valid offset.
20    SignatureNotFound,
21    /// The superblock version is not supported.
22    UnsupportedVersion(u8),
23    /// Unexpected end of data.
24    UnexpectedEof {
25        /// Number of bytes expected.
26        expected: usize,
27        /// Number of bytes actually available.
28        available: usize,
29    },
30    /// Invalid offset size (must be 2, 4, or 8).
31    InvalidOffsetSize(u8),
32    /// Invalid length size (must be 2, 4, or 8).
33    InvalidLengthSize(u8),
34    /// Invalid object header signature.
35    InvalidObjectHeaderSignature,
36    /// Invalid object header version.
37    InvalidObjectHeaderVersion(u8),
38    /// Unknown message type that is marked as must-understand.
39    UnsupportedMessage(u16),
40    /// Invalid datatype class.
41    InvalidDatatypeClass(u8),
42    /// Invalid datatype version for a given class.
43    InvalidDatatypeVersion {
44        /// The type class.
45        class: u8,
46        /// The version found.
47        version: u8,
48    },
49    /// A datatype message in a file declares an element size of zero, which no
50    /// HDF5 type has. Raised when the message is parsed, so the size every reader
51    /// divides raw bytes by is never zero.
52    ZeroSizedDatatype {
53        /// The type class that declared it.
54        class: u8,
55    },
56    /// Invalid string padding type.
57    InvalidStringPadding(u8),
58    /// Invalid character set.
59    InvalidCharacterSet(u8),
60    /// Invalid byte order.
61    InvalidByteOrder(u8),
62    /// Invalid reference type.
63    InvalidReferenceType(u8),
64    /// Invalid file-space management strategy code in a File Space Info message.
65    InvalidFileSpaceStrategy(u8),
66    /// Unsupported File Space Info message version (only version 1 is handled).
67    UnsupportedFileSpaceInfoVersion(u8),
68    /// A paged file-space strategy was requested with a page size the writer
69    /// cannot use: it must be a power of two of at least 512 bytes.
70    InvalidFileSpacePageSize(u64),
71    /// A paged file-space strategy was requested alongside a userblock that is
72    /// not a whole number of pages. File-space pages are measured from the file
73    /// base, so the two boundaries coincide only when the userblock divides by
74    /// the page size: `(userblock bytes, page size)`.
75    UserblockNotPageAligned(u64, u64),
76    /// A userblock size the format does not define: it must be zero, or a power
77    /// of two of at least 512 bytes. A reader looks for the superblock at 0, 512,
78    /// 1024, and so on doubling, so any other size produces a file nothing can
79    /// open.
80    InvalidUserblockSize(u64),
81    /// More userblock content was supplied than the userblock region holds. The
82    /// overflow would displace the superblock, so it is refused rather than
83    /// truncated.
84    UserblockContentTooLarge {
85        /// Bytes supplied.
86        content: u64,
87        /// Bytes the userblock region holds.
88        userblock: u64,
89    },
90    /// A free-space manager block (`FSHD`/`FSSE`) is malformed.
91    InvalidFreeSpaceManager,
92    /// An enumeration datatype was built over a base type that is not an
93    /// integer. HDF5 enumerations must have a fixed-point base.
94    EnumBaseNotInteger,
95    /// An enumeration member's value does not occupy exactly the base type's
96    /// size: `(member name, expected bytes, actual bytes)`.
97    EnumMemberValueSize(String, u32, usize),
98    /// An enumeration member's integer value does not fit in the base type:
99    /// `(member name, value, base size in bytes)`.
100    EnumMemberValueRange(String, i64, u32),
101    /// A compound datatype has a zero total size.
102    InvalidCompoundSize,
103    /// A compound datatype contains no fields.
104    EmptyCompoundType,
105    /// A compound datatype contains the same field name more than once.
106    DuplicateCompoundField(String),
107    /// A compound field extends past the declared compound size.
108    CompoundFieldOutOfBounds {
109        /// Field name.
110        name: String,
111        /// Field byte offset.
112        offset: u64,
113        /// Field size in bytes.
114        field_size: u32,
115        /// Declared compound size in bytes.
116        compound_size: u32,
117    },
118    /// Two compound fields overlap.
119    CompoundFieldOverlap {
120        /// Earlier field in byte order.
121        first: String,
122        /// Later field in byte order.
123        second: String,
124    },
125    /// A named compound field was not present.
126    CompoundFieldMissing(String),
127    /// A compound field has an incompatible datatype.
128    CompoundFieldTypeMismatch(String),
129    /// Invalid dataspace version.
130    InvalidDataspaceVersion(u8),
131    /// Invalid dataspace type.
132    InvalidDataspaceType(u8),
133    /// Invalid data layout version.
134    InvalidLayoutVersion(u8),
135    /// Invalid data layout class.
136    InvalidLayoutClass(u8),
137    /// The dataset's Fill Value message could not be parsed, and a read needed
138    /// it: part of the dataset's storage was never allocated, so what those
139    /// elements read as is undetermined. A dataset whose storage is fully
140    /// allocated reads normally regardless of this message.
141    UnreadableFillValue,
142    /// Type mismatch when reading data.
143    TypeMismatch {
144        /// Expected type description.
145        expected: &'static str,
146        /// Actual type description.
147        actual: &'static str,
148    },
149    /// Data size mismatch.
150    DataSizeMismatch {
151        /// Expected size in bytes.
152        expected: usize,
153        /// Actual size in bytes.
154        actual: usize,
155    },
156    /// Invalid local heap signature.
157    InvalidLocalHeapSignature,
158    /// Invalid local heap version.
159    InvalidLocalHeapVersion(u8),
160    /// Invalid B-tree v1 signature.
161    InvalidBTreeSignature,
162    /// Invalid B-tree node type.
163    InvalidBTreeNodeType(u8),
164    /// Invalid symbol table node signature.
165    InvalidSymbolTableNodeSignature,
166    /// Invalid symbol table node version.
167    InvalidSymbolTableNodeVersion(u8),
168    /// Path not found during group traversal.
169    PathNotFound(String),
170    /// Invalid Link message version.
171    InvalidLinkVersion(u8),
172    /// Invalid link type code.
173    InvalidLinkType(u8),
174    /// Invalid Link Info message version.
175    InvalidLinkInfoVersion(u8),
176    /// Invalid B-tree v2 signature.
177    InvalidBTreeV2Signature,
178    /// Invalid B-tree v2 version.
179    InvalidBTreeV2Version(u8),
180    /// Invalid fractal heap signature.
181    InvalidFractalHeapSignature,
182    /// Invalid fractal heap version.
183    InvalidFractalHeapVersion(u8),
184    /// Invalid heap ID type.
185    InvalidHeapIdType(u8),
186    /// A fractal-heap "huge" object's heap ID referenced a B-tree key that is
187    /// not present in the heap's huge-objects v2 B-tree.
188    HugeObjectNotFound(u64),
189    /// A fractal heap's huge-objects v2 B-tree is not the indirectly accessed,
190    /// non-filtered layout (record type 1) this reader decodes: either the tree
191    /// declares a different record type, or its records are too short to hold
192    /// that one. Reading its records as that layout would decode an object ID
193    /// out of another field's bytes.
194    UnexpectedHugeObjectBTree {
195        /// The record type the B-tree declares.
196        tree_type: u8,
197        /// The record size the B-tree declares, in bytes.
198        record_size: usize,
199        /// The bytes a type-1 record needs: address + length + object ID.
200        required: usize,
201    },
202    /// A fractal-heap object lives in an I/O-filter-encoded heap (filtered
203    /// managed or huge storage), whose filtered bytes this reader does not
204    /// decode. Link and attribute heaps are never filtered, so this does not
205    /// arise for them.
206    UnsupportedFilteredHeapObject,
207    /// A dataset uses the Virtual (VDS) data layout, which maps its elements to
208    /// regions of other datasets, possibly in other files. This reader does not
209    /// yet resolve virtual mappings, so such a dataset is refused rather than
210    /// read as empty or wrong.
211    UnsupportedVirtualLayout,
212    /// A dataset's element bytes live in files outside this one
213    /// (`H5Pset_external`, the External Data Files header message). Its layout
214    /// message is contiguous with the data address undefined — the same encoding
215    /// a never-written dataset carries — so reading the layout alone would answer
216    /// the fill value for every element of a dataset that holds data. This reader
217    /// does not follow the external files, so such a dataset is refused rather
218    /// than read as empty.
219    UnsupportedExternalStorage,
220    /// Invalid attribute message version.
221    InvalidAttributeVersion(u8),
222    /// Invalid Attribute Info message version.
223    InvalidAttributeInfoVersion(u8),
224    /// Invalid shared message version.
225    InvalidSharedMessageVersion(u8),
226    /// An attribute message's flags byte set a bit the format does not define.
227    /// Only bit 0 (shared datatype) and bit 1 (shared dataspace) exist, so any
228    /// other bit means the message is not what it claims to be.
229    InvalidAttributeFlags(u8),
230    /// A message body is a reference to the file's shared object header message
231    /// (SOHM) heap, and the parse that met it holds no shared-message table to
232    /// find it in: the file's superblock extension carries no Shared Message
233    /// Table message, the table it names could not be read, or the parse was
234    /// handed a message body without the file it came from. A committed
235    /// (`H5Tcommit`) datatype is *not* stored in the heap — it references another
236    /// object header, and is resolved.
237    UnsupportedSohmReference,
238    /// A file's shared-message table, or a Shared Message Table message naming
239    /// it, declares a version this format does not define.
240    InvalidSohmTableVersion(u8),
241    /// A Shared Message Table message declares no indexes, or more than the
242    /// eight the format allows. The count sizes the table's own read, so a
243    /// wrong one reads neighbouring bytes as index headers.
244    InvalidSohmIndexCount(u8),
245    /// The shared-message table does not start with its `SMTB` signature.
246    InvalidSohmTableSignature,
247    /// A shared-message list index does not start with its `SMLI` signature.
248    InvalidSohmListSignature,
249    /// A shared-message index header declares a storage kind that is neither a
250    /// list (0) nor a version 2 B-tree (1).
251    InvalidSohmIndexKind(u8),
252    /// A shared-message index names a version 2 B-tree that is not a
253    /// shared-message index (type 7). Carries the type it is.
254    InvalidSohmBTreeType(u8),
255    /// A shared-message index record names a storage location that is neither
256    /// the heap (0) nor an object header (1).
257    InvalidSohmRecordLocation(u8),
258    /// A message body references the shared-message heap, and no index of the
259    /// file's shared-message table covers that message type or has allocated a
260    /// heap. Carries the raw type ID of the referenced message.
261    SohmIndexMissing(u16),
262    /// A shared message reference named an object header that holds no message of
263    /// the referenced type.
264    SharedMessageMissing {
265        /// Address of the object header the reference named.
266        object_header_address: u64,
267        /// Raw type ID of the message the reference stood in for.
268        message_type: u16,
269    },
270    /// A message body holds a reference to a shared message, and the parse that
271    /// met it had no access to the file the reference addresses. Carries the raw
272    /// type ID of the referenced message.
273    UnresolvedSharedMessage(u16),
274    /// A dataset or attribute names a committed datatype at a path where the file
275    /// being written places no such object.
276    UnknownCommittedDatatype(String),
277    /// A dataset or attribute names a committed datatype whose encoding differs
278    /// from its own. The two would disagree about how to read the element bytes,
279    /// and the committed one is what every reader would believe.
280    CommittedDatatypeMismatch {
281        /// Path of the committed datatype object that was named.
282        path: String,
283        /// Name of the dataset or attribute that named it.
284        user: String,
285    },
286    /// Invalid global heap collection signature.
287    InvalidGlobalHeapSignature,
288    /// Invalid global heap version.
289    InvalidGlobalHeapVersion(u8),
290    /// Global heap object not found.
291    GlobalHeapObjectNotFound {
292        /// Address of the collection.
293        collection_address: u64,
294        /// Index that was not found.
295        index: u16,
296    },
297    /// Variable-length data error.
298    VlDataError(String),
299    /// A variable-length read exceeded its configured element limit.
300    VariableLengthElementLimitExceeded {
301        /// Maximum number of elements permitted by the caller.
302        limit: usize,
303        /// Number of elements present in the selected data.
304        actual: u64,
305    },
306    /// A variable-length read exceeded its configured payload-byte limit.
307    VariableLengthByteLimitExceeded {
308        /// Maximum number of payload bytes permitted by the caller.
309        limit: usize,
310        /// Number of payload bytes required by the selected data.
311        required: u64,
312    },
313    /// Serialization error.
314    SerializationError(String),
315    /// Dataset is missing data.
316    DatasetMissingData,
317    /// Dataset is missing shape.
318    DatasetMissingShape,
319    /// The dataset's element count implied by its shape does not match the
320    /// amount of data supplied (`shape.product() * element_size != data.len()`).
321    ShapeDataMismatch {
322        /// Number of data bytes the shape requires (`product(shape) * element_size`).
323        expected: usize,
324        /// Number of data bytes actually supplied.
325        actual: usize,
326        /// Size in bytes of one element (the dataset's datatype size), used to
327        /// report the mismatch in elements as well as bytes. Non-zero by type,
328        /// so the division in the message is well defined.
329        element_size: NonZeroUsize,
330    },
331    /// A chunked/filtered/extensible dataset's chunk geometry is invalid — for
332    /// example chunk dimensions whose rank disagrees with the shape, a zero chunk
333    /// dimension, a maximum shape whose rank disagrees with the shape or that is
334    /// smaller than the current shape, or chunking requested on a scalar dataset.
335    /// Reported up front so a malformed request is refused instead of panicking
336    /// in the chunk splitter or producing an unreadable dataset. The payload is a
337    /// human-readable reason.
338    InvalidChunkGeometry(&'static str),
339    /// Invalid filter pipeline version.
340    InvalidFilterPipelineVersion(u8),
341    /// Unsupported filter ID.
342    UnsupportedFilter(u16),
343    /// Filter processing error, including a stream that failed to decode. Every
344    /// filter in the pipeline — shuffle, scale-offset, LZF, deflate — reports a
345    /// bad chunk with this variant, so "this chunk did not decode" is one match
346    /// arm rather than one per compressor. The payload names the filter.
347    FilterError(String),
348    /// Fletcher32 checksum mismatch.
349    Fletcher32Mismatch {
350        /// Expected checksum.
351        expected: u32,
352        /// Computed checksum.
353        computed: u32,
354    },
355    /// Chunked dataset read error.
356    ChunkedReadError(String),
357    /// CRC32C checksum mismatch.
358    ChecksumMismatch {
359        /// The checksum stored in the file.
360        expected: u32,
361        /// The checksum we computed.
362        computed: u32,
363    },
364    /// Maximum nesting/continuation depth exceeded (malformed data protection).
365    NestingDepthExceeded,
366    /// ZFP filter configuration is invalid (e.g. missing element type, rank out of range).
367    UnsupportedZfp(String),
368    /// A file-derived 64-bit value (an offset, length, size, or element count)
369    /// does not fit in the target integer type on this platform. This is the
370    /// guard that replaces silent `as usize` / `as u32` truncation: on a 32-bit
371    /// host, `usize` is 32 bits, so an HDF5 offset or length above `usize::MAX`
372    /// would otherwise wrap and read the wrong bytes. The original value is
373    /// preserved for diagnostics, and `target` names the type we tried to
374    /// narrow to (e.g. `"usize"`, `"u32"`).
375    ValueTooLargeForPlatform {
376        /// The original 64-bit value read from the file.
377        value: u64,
378        /// The platform integer type the value could not fit into.
379        target: &'static str,
380    },
381    /// Two file-derived values (typically an offset and a length) overflow `u64`
382    /// when added to form a slice bound. Reported instead of wrapping so a
383    /// malformed file cannot produce a wrapped or out-of-range index.
384    OffsetOverflow {
385        /// First operand (typically the base offset/address).
386        offset: u64,
387        /// Second operand (typically the length/size).
388        length: u64,
389    },
390    /// An absolute file position lies below the superblock's base address, so it
391    /// has no stored (base-relative) form: the bytes below the base are the
392    /// userblock, where no HDF5 structure can live. Reported instead of wrapping
393    /// the subtraction into a near-`u64::MAX` address.
394    AddressBelowBase {
395        /// The absolute file position that could not be made base-relative.
396        address: u64,
397        /// The superblock base address it was below.
398        base: u64,
399    },
400    /// A random-access byte source failed to
401    /// supply the requested bytes. The string carries a backend-specific reason
402    /// (e.g. an underlying `std::io::Error` rendered to text), so this stays
403    /// `no_std`/`alloc`-friendly and free of an `std::io` dependency.
404    Source(String),
405    /// The library-version bounds requested via
406    /// [`FileBuilder::with_libver_bounds`](crate::FileBuilder::with_libver_bounds)
407    /// admit no format this crate writes. It writes the 1.8 and 1.10 formats
408    /// ([`LibVer::WRITER_OLDEST`](crate::LibVer::WRITER_OLDEST) through
409    /// [`LibVer::WRITER_DEFAULT`](crate::LibVer::WRITER_DEFAULT)), so an upper
410    /// bound older than 1.8, or one below the lower bound, is unsatisfiable. A
411    /// *lower* bound newer than 1.10 is not: it licenses newer encodings without
412    /// requiring them, and the 1.10 format satisfies it.
413    /// The fields carry the default format and the bounds asked for, as
414    /// [`LibVer::name`](crate::LibVer::name) labels.
415    LibverBoundsUnsatisfiable {
416        /// The library-version label of the format this crate writes by default.
417        writes: &'static str,
418        /// The requested lower bound.
419        requested_low: &'static str,
420        /// The requested upper bound.
421        requested_high: &'static str,
422    },
423    /// The file's content needs a newer on-disk format than the requested
424    /// library-version bounds allow, so writing it would silently produce a file
425    /// the caller asked not to receive.
426    ///
427    /// Reported rather than upgraded, because the bound exists to be relied on:
428    /// a MAT v7.3 file bounded to 1.8 so MATLAB can load it is worth less than
429    /// nothing if compressing it quietly restores the 1.10 format. `content`
430    /// names what forced the newer format and `needs` the format it forces, both
431    /// as [`LibVer::name`](crate::LibVer::name) labels.
432    LibverTooOldForContent {
433        /// What in the file requires a newer format, e.g. `"a chunked dataset"`.
434        content: &'static str,
435        /// The library-version label of the format that content requires.
436        needs: &'static str,
437        /// The library-version label of the format the bounds resolved to.
438        writing: &'static str,
439    },
440    /// An HDF5 object reference (`H5R_OBJECT`) could not be resolved to an
441    /// object: the stored address is null or undefined (`HADDR_UNDEF`), or it
442    /// does not point at a group or dataset object header. The payload is the
443    /// stored (base-relative) address, preserved for diagnostics.
444    InvalidObjectReference(u64),
445    /// A Fill Value message (`0x0005`) has an on-disk version this crate does
446    /// not recognize (only 1, 2, and 3 are defined). The payload is the version
447    /// byte found.
448    UnsupportedFillValueVersion(u8),
449    /// A user-supplied fill value's byte width does not match the dataset's
450    /// datatype element size (for example a `u8` fill value on an `i32`
451    /// dataset). The fields carry the datatype element size and the fill value
452    /// size, both in bytes.
453    FillValueSizeMismatch {
454        /// The dataset datatype's element size in bytes.
455        expected: usize,
456        /// The supplied fill value's size in bytes.
457        actual: usize,
458    },
459    /// An attribute's serialized message is larger than the version 2 object
460    /// header's 2-byte message-size field can describe, so it cannot be stored
461    /// as a compact (in-header) attribute. Refused rather than written, because
462    /// a truncated size field would desynchronize every message after it. The
463    /// fields carry the attribute name and its serialized message size in
464    /// bytes; the limit is [`OBJECT_HEADER_MESSAGE_MAX`].
465    ///
466    /// A backstop rather than an outcome you should expect to see: the
467    /// whole-file writer selects dense (fractal-heap) storage for exactly the
468    /// attributes that would trip this, so no input reaches it today. It stays
469    /// because the limit it describes is a real property of the object header,
470    /// and any future path that must keep an attribute compact needs it.
471    AttributeMessageTooLarge {
472        /// The attribute's name.
473        name: String,
474        /// The attribute message's serialized size in bytes.
475        size: usize,
476    },
477    /// An object-header message is larger than the version 2 object header's
478    /// 2-byte message-size field can describe. This is the whole-file writer's
479    /// backstop against emitting a truncated size field, covering every message
480    /// it builds; callers that can name the offending object (for example an
481    /// attribute) report a more specific error first. The in-place editor
482    /// encodes headers separately and refuses the same condition with
483    /// [`Error::EditUnsupported`](crate::Error::EditUnsupported). The fields
484    /// carry the message type code and the message's serialized size in bytes;
485    /// the limit is [`OBJECT_HEADER_MESSAGE_MAX`].
486    ObjectHeaderMessageTooLarge {
487        /// The header message's type code (see the HDF5 message type table).
488        message_type: u16,
489        /// The message's serialized size in bytes.
490        size: usize,
491    },
492    /// An attribute's name, datatype or dataspace is longer than the attribute
493    /// message can describe: each of the three has a 2-byte length field, so a
494    /// longer one would truncate and produce a message that decodes as something
495    /// else. Refused rather than written.
496    ///
497    /// This bounds the attribute's *description*, not its data. An attribute
498    /// whose data is arbitrarily large is written to dense storage as a
499    /// fractal-heap huge object.
500    ///
501    /// Reported by the whole-file writer (including through `repack`), which
502    /// sends any attribute too large for an object-header message to dense
503    /// storage and so reaches this check.
504    ///
505    /// In practice `field` is always `"name"`. Every attribute this crate writes
506    /// is built from an [`AttrValue`](crate::AttrValue) — including the ones
507    /// `repack` carries over, which it refuses outright if it cannot represent
508    /// them — and no variant of it produces a datatype past 20 bytes or a
509    /// dataspace past 12, whatever the data. The other two are still checked,
510    /// because the message encodes all three lengths the same way, and named
511    /// rather than merged so that a datatype which does one day reach the limit
512    /// is diagnosable from the error rather than mistaken for a long name.
513    AttributeFieldTooLong {
514        /// The attribute's name.
515        name: String,
516        /// Which of the message's three 2-byte length fields overflowed:
517        /// `"name"`, `"datatype"` or `"dataspace"`. Diagnostic detail rather than
518        /// a discriminator to branch on, since only `"name"` is reachable today.
519        field: &'static str,
520        /// That field's encoded length in bytes.
521        size: usize,
522        /// The largest length the message can describe, in bytes.
523        limit: usize,
524    },
525    /// An object's attributes need more space than a dense attribute heap can
526    /// address: its offsets are 40 bits wide, so the blocks holding the
527    /// attributes cannot span more than `limit` bytes between them. Reaching this
528    /// takes about a terabyte of attributes on a single object.
529    ///
530    /// A set that fits the heap but not the host reports
531    /// [`FormatError::ValueTooLargeForPlatform`] instead, so the limit named here
532    /// is always the one that actually applied.
533    DenseAttributeHeapTooLarge {
534        /// The heap address space, in bytes.
535        limit: u64,
536    },
537    /// A fixed-width string was asked for a declared width of zero. No HDF5
538    /// string datatype may be zero bytes wide: libhdf5 refuses one with
539    /// "invalid datatype size", and it does so while *iterating* the object's
540    /// members, so one such type takes its neighbours down with it.
541    ///
542    /// Only an explicitly requested width raises this — a dataset's through
543    /// [`DatasetBuilder::with_ascii_strings_sized`](crate::DatasetBuilder::with_ascii_strings_sized)
544    /// or its siblings, an attribute's through
545    /// [`AttrValue::ascii_string_sized`](crate::AttrValue::ascii_string_sized)
546    /// or its siblings. A width *derived* from the values — every one of them
547    /// empty — is one byte rather than zero, since the empty string is
548    /// representable and it was only the datatype that was not.
549    ZeroFixedStringWidth,
550    /// An element handed to a fixed-width string dataset or attribute is longer
551    /// than the width declared for it. Storing a prefix would read back later as
552    /// a value the caller never wrote, and with no error to say so, which is why
553    /// this refuses rather than truncates.
554    FixedStringTooLong {
555        /// Position of the offending element among the values passed. Zero for
556        /// a scalar attribute, which holds one value.
557        index: usize,
558        /// That element's length in bytes.
559        len: usize,
560        /// The declared width, in bytes.
561        width: u32,
562    },
563    /// A numeric element in a file is wider than the 64-bit word the typed
564    /// numeric readers model an element as.
565    ///
566    /// Those readers assemble one element from its leading eight bytes, so a
567    /// wider element would decode from part of itself: a 9-byte integer holding
568    /// 2^64 read back as zero, indistinguishable from one that really holds
569    /// zero. *Which* part survives depends on the byte order — under big-endian
570    /// those leading bytes are the element's **most** significant — so the whole
571    /// class is refused rather than decoded for the orders that happen to work
572    /// out (issue #361).
573    ///
574    /// The bytes are still readable: `Dataset::read_raw` is unaffected, and an
575    /// attribute this refuses is omitted from `attrs` while `attr_datatypes`
576    /// still reports its type.
577    NumericElementTooWide {
578        /// Storage width of one element, in bytes.
579        size: usize,
580    },
581}
582
583/// The largest message a version 2 object header can describe: its per-message
584/// size field is 2 bytes wide. A message past this must be refused rather than
585/// written with a truncated length (see
586/// [`FormatError::ObjectHeaderMessageTooLarge`]).
587pub const OBJECT_HEADER_MESSAGE_MAX: usize = u16::MAX as usize;
588
589impl fmt::Display for FormatError {
590    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
591        match self {
592            FormatError::SignatureNotFound => {
593                write!(f, "HDF5 signature not found at any valid offset")
594            }
595            FormatError::UnsupportedVersion(v) => {
596                write!(f, "unsupported superblock version: {v}")
597            }
598            FormatError::UnexpectedEof {
599                expected,
600                available,
601            } => {
602                write!(f, "unexpected EOF: need {expected} bytes, have {available}")
603            }
604            FormatError::InvalidOffsetSize(s) => {
605                write!(f, "invalid offset size: {s} (must be 2, 4, or 8)")
606            }
607            FormatError::InvalidLengthSize(s) => {
608                write!(f, "invalid length size: {s} (must be 2, 4, or 8)")
609            }
610            FormatError::InvalidObjectHeaderSignature => {
611                write!(f, "invalid object header signature")
612            }
613            FormatError::InvalidObjectHeaderVersion(v) => {
614                write!(f, "invalid object header version: {v}")
615            }
616            FormatError::UnsupportedMessage(id) => {
617                write!(
618                    f,
619                    "unsupported message type {id:#06x} marked as must-understand"
620                )
621            }
622            FormatError::InvalidDatatypeClass(c) => {
623                write!(f, "invalid datatype class: {c}")
624            }
625            FormatError::InvalidDatatypeVersion { class, version } => {
626                write!(f, "invalid datatype version {version} for class {class}")
627            }
628            FormatError::ZeroSizedDatatype { class } => {
629                write!(
630                    f,
631                    "datatype class {class} declares a zero-byte element size"
632                )
633            }
634            FormatError::InvalidStringPadding(p) => {
635                write!(f, "invalid string padding type: {p}")
636            }
637            FormatError::InvalidCharacterSet(c) => {
638                write!(f, "invalid character set: {c}")
639            }
640            FormatError::InvalidByteOrder(b) => {
641                write!(f, "invalid byte order: {b}")
642            }
643            FormatError::InvalidReferenceType(r) => {
644                write!(f, "invalid reference type: {r}")
645            }
646            FormatError::InvalidFileSpaceStrategy(s) => {
647                write!(f, "invalid file-space strategy code: {s}")
648            }
649            FormatError::UnsupportedFileSpaceInfoVersion(v) => {
650                write!(f, "unsupported File Space Info message version: {v}")
651            }
652            FormatError::InvalidFileSpacePageSize(p) => {
653                write!(
654                    f,
655                    "invalid file-space page size {p}: must be a power of two >= 512"
656                )
657            }
658            FormatError::UserblockNotPageAligned(userblock, page_size) => {
659                write!(
660                    f,
661                    "userblock of {userblock} bytes is not a whole number of {page_size}-byte \
662                     file-space pages: a paged file measures its pages from the file base, so \
663                     the userblock must be a multiple of the page size (or zero)"
664                )
665            }
666            FormatError::InvalidUserblockSize(size) => {
667                write!(
668                    f,
669                    "invalid userblock size {size}: must be zero or a power of two >= 512"
670                )
671            }
672            FormatError::UserblockContentTooLarge { content, userblock } => {
673                write!(
674                    f,
675                    "{content} bytes of userblock content do not fit a userblock of {userblock} \
676                     bytes"
677                )
678            }
679            FormatError::InvalidFreeSpaceManager => {
680                write!(f, "malformed free-space manager block (FSHD/FSSE)")
681            }
682            FormatError::EnumBaseNotInteger => {
683                write!(
684                    f,
685                    "an enumeration's base type must be an integer (fixed-point) type"
686                )
687            }
688            FormatError::EnumMemberValueSize(name, expected, actual) => {
689                write!(
690                    f,
691                    "enumeration member '{name}' has a {actual}-byte value, but its base type is \
692                     {expected} bytes"
693                )
694            }
695            FormatError::EnumMemberValueRange(name, value, size) => {
696                write!(
697                    f,
698                    "enumeration member '{name}' value {value} does not fit in its \
699                     {size}-byte base type"
700                )
701            }
702            FormatError::InvalidCompoundSize => {
703                write!(f, "compound datatype size must be greater than zero")
704            }
705            FormatError::EmptyCompoundType => {
706                write!(f, "compound datatype must contain at least one field")
707            }
708            FormatError::DuplicateCompoundField(name) => {
709                write!(f, "duplicate compound field name: {name}")
710            }
711            FormatError::CompoundFieldOutOfBounds {
712                name,
713                offset,
714                field_size,
715                compound_size,
716            } => {
717                write!(
718                    f,
719                    "compound field {name:?} at offset {offset} with size {field_size} \
720                     exceeds compound size {compound_size}"
721                )
722            }
723            FormatError::CompoundFieldOverlap { first, second } => {
724                write!(f, "compound fields {first:?} and {second:?} overlap")
725            }
726            FormatError::CompoundFieldMissing(name) => {
727                write!(f, "compound field {name:?} is missing")
728            }
729            FormatError::CompoundFieldTypeMismatch(name) => {
730                write!(f, "compound field {name:?} has an incompatible datatype")
731            }
732            FormatError::InvalidDataspaceVersion(v) => {
733                write!(f, "invalid dataspace version: {v}")
734            }
735            FormatError::InvalidDataspaceType(t) => {
736                write!(f, "invalid dataspace type: {t}")
737            }
738            FormatError::InvalidLayoutVersion(v) => {
739                write!(f, "invalid data layout version: {v}")
740            }
741            FormatError::InvalidLayoutClass(c) => {
742                write!(f, "invalid data layout class: {c}")
743            }
744            FormatError::UnreadableFillValue => write!(
745                f,
746                "the dataset's fill value message could not be parsed, and part of its \
747                 storage was never allocated, so those elements have no determined value"
748            ),
749            FormatError::TypeMismatch { expected, actual } => {
750                write!(f, "type mismatch: expected {expected}, got {actual}")
751            }
752            FormatError::DataSizeMismatch { expected, actual } => {
753                write!(
754                    f,
755                    "data size mismatch: expected {expected} bytes, got {actual} bytes"
756                )
757            }
758            FormatError::InvalidLocalHeapSignature => {
759                write!(f, "invalid local heap signature")
760            }
761            FormatError::InvalidLocalHeapVersion(v) => {
762                write!(f, "invalid local heap version: {v}")
763            }
764            FormatError::InvalidBTreeSignature => {
765                write!(f, "invalid B-tree v1 signature")
766            }
767            FormatError::InvalidBTreeNodeType(t) => {
768                write!(f, "invalid B-tree node type: {t}")
769            }
770            FormatError::InvalidSymbolTableNodeSignature => {
771                write!(f, "invalid symbol table node signature")
772            }
773            FormatError::InvalidSymbolTableNodeVersion(v) => {
774                write!(f, "invalid symbol table node version: {v}")
775            }
776            FormatError::PathNotFound(p) => {
777                write!(f, "path not found: {p}")
778            }
779            FormatError::InvalidLinkVersion(v) => {
780                write!(f, "invalid link message version: {v}")
781            }
782            FormatError::InvalidLinkType(t) => {
783                write!(f, "invalid link type: {t}")
784            }
785            FormatError::InvalidLinkInfoVersion(v) => {
786                write!(f, "invalid link info message version: {v}")
787            }
788            FormatError::InvalidBTreeV2Signature => {
789                write!(f, "invalid B-tree v2 signature")
790            }
791            FormatError::InvalidBTreeV2Version(v) => {
792                write!(f, "invalid B-tree v2 version: {v}")
793            }
794            FormatError::InvalidFractalHeapSignature => {
795                write!(f, "invalid fractal heap signature")
796            }
797            FormatError::InvalidFractalHeapVersion(v) => {
798                write!(f, "invalid fractal heap version: {v}")
799            }
800            FormatError::InvalidHeapIdType(t) => {
801                write!(f, "invalid heap ID type: {t}")
802            }
803            FormatError::HugeObjectNotFound(id) => {
804                write!(f, "fractal-heap huge object {id} not found in B-tree")
805            }
806            FormatError::UnexpectedHugeObjectBTree {
807                tree_type,
808                record_size,
809                required,
810            } => {
811                write!(
812                    f,
813                    "fractal-heap huge-objects B-tree is not the expected record type 1: \
814                     type {tree_type}, records of {record_size} bytes (type 1 needs {required})"
815                )
816            }
817            FormatError::UnsupportedFilteredHeapObject => {
818                write!(f, "filtered fractal-heap objects are not supported")
819            }
820            FormatError::UnsupportedVirtualLayout => {
821                write!(f, "virtual (VDS) data layout is not supported")
822            }
823            FormatError::UnsupportedExternalStorage => {
824                write!(
825                    f,
826                    "dataset stores its elements in external files (H5Pset_external), \
827                     which this reader does not follow"
828                )
829            }
830            FormatError::InvalidAttributeVersion(v) => {
831                write!(f, "invalid attribute message version: {v}")
832            }
833            FormatError::InvalidAttributeInfoVersion(v) => {
834                write!(f, "invalid attribute info message version: {v}")
835            }
836            FormatError::InvalidSharedMessageVersion(v) => {
837                write!(f, "invalid shared message version: {v}")
838            }
839            FormatError::InvalidAttributeFlags(v) => {
840                write!(f, "undefined flag bits in attribute message: {v:#04x}")
841            }
842            FormatError::UnsupportedSohmReference => {
843                write!(
844                    f,
845                    "a message references the shared object header message (SOHM) heap, and no readable shared message table was found for it"
846                )
847            }
848            FormatError::InvalidSohmTableVersion(v) => {
849                write!(f, "invalid shared message table version: {v}")
850            }
851            FormatError::InvalidSohmIndexCount(n) => {
852                write!(f, "invalid shared message index count: {n}")
853            }
854            FormatError::InvalidSohmTableSignature => {
855                write!(f, "invalid shared message table signature")
856            }
857            FormatError::InvalidSohmListSignature => {
858                write!(f, "invalid shared message list signature")
859            }
860            FormatError::InvalidSohmIndexKind(v) => {
861                write!(f, "invalid shared message index storage kind: {v}")
862            }
863            FormatError::InvalidSohmBTreeType(v) => {
864                write!(
865                    f,
866                    "a shared message index names a v2 B-tree of type {v}, not a shared message index"
867                )
868            }
869            FormatError::InvalidSohmRecordLocation(v) => {
870                write!(f, "invalid shared message record location: {v}")
871            }
872            FormatError::SohmIndexMissing(t) => {
873                write!(f, "no shared message index holds messages of type {t:#06x}")
874            }
875            FormatError::SharedMessageMissing {
876                object_header_address,
877                message_type,
878            } => {
879                write!(
880                    f,
881                    "object header at {object_header_address} holds no message of type {message_type:#06x} for a shared reference to it"
882                )
883            }
884            FormatError::UnresolvedSharedMessage(t) => {
885                write!(
886                    f,
887                    "a reference to a shared message of type {t:#06x} cannot be resolved without the file that holds it"
888                )
889            }
890            FormatError::UnknownCommittedDatatype(path) => {
891                write!(f, "no committed datatype is written at path {path:?}")
892            }
893            FormatError::CommittedDatatypeMismatch { path, user } => {
894                write!(
895                    f,
896                    "{user} names the committed datatype {path:?} but declares a different type"
897                )
898            }
899            FormatError::InvalidGlobalHeapSignature => {
900                write!(f, "invalid global heap collection signature")
901            }
902            FormatError::InvalidGlobalHeapVersion(v) => {
903                write!(f, "invalid global heap version: {v}")
904            }
905            FormatError::GlobalHeapObjectNotFound {
906                collection_address,
907                index,
908            } => {
909                write!(
910                    f,
911                    "global heap object not found: collection {collection_address:#x}, index {index}"
912                )
913            }
914            FormatError::VlDataError(msg) => {
915                write!(f, "variable-length data error: {msg}")
916            }
917            FormatError::VariableLengthElementLimitExceeded { limit, actual } => {
918                write!(
919                    f,
920                    "variable-length element limit exceeded: limit is {limit}, data contains {actual}"
921                )
922            }
923            FormatError::VariableLengthByteLimitExceeded { limit, required } => {
924                write!(
925                    f,
926                    "variable-length payload limit exceeded: limit is {limit} bytes, \
927                     data requires {required} bytes"
928                )
929            }
930            FormatError::SerializationError(msg) => {
931                write!(f, "serialization error: {msg}")
932            }
933            FormatError::DatasetMissingData => {
934                write!(f, "dataset is missing data")
935            }
936            FormatError::DatasetMissingShape => {
937                write!(f, "dataset is missing shape")
938            }
939            FormatError::ShapeDataMismatch {
940                expected,
941                actual,
942                element_size,
943            } => {
944                write!(
945                    f,
946                    "shape/data mismatch: shape requires {} elements ({expected} bytes), \
947                     but {} elements ({actual} bytes) were supplied",
948                    expected / element_size.get(),
949                    actual / element_size.get(),
950                )
951            }
952            FormatError::InvalidChunkGeometry(reason) => {
953                write!(f, "invalid chunk geometry: {reason}")
954            }
955            FormatError::InvalidFilterPipelineVersion(v) => {
956                write!(f, "invalid filter pipeline version: {v}")
957            }
958            FormatError::UnsupportedFilter(id) => {
959                write!(f, "unsupported filter: {id}")
960            }
961            FormatError::FilterError(msg) => {
962                write!(f, "filter error: {msg}")
963            }
964            FormatError::Fletcher32Mismatch { expected, computed } => {
965                write!(
966                    f,
967                    "fletcher32 mismatch: expected {expected:#010x}, computed {computed:#010x}"
968                )
969            }
970            FormatError::ChunkedReadError(msg) => {
971                write!(f, "chunked read error: {msg}")
972            }
973            FormatError::ChecksumMismatch { expected, computed } => {
974                write!(
975                    f,
976                    "checksum mismatch: expected {expected:#010x}, computed {computed:#010x}"
977                )
978            }
979            FormatError::NestingDepthExceeded => {
980                write!(f, "maximum nesting/continuation depth exceeded")
981            }
982            FormatError::UnsupportedZfp(msg) => {
983                write!(f, "unsupported ZFP configuration: {msg}")
984            }
985            FormatError::ValueTooLargeForPlatform { value, target } => {
986                write!(
987                    f,
988                    "file value {value} does not fit in {target} on this platform \
989                     (a 64-bit HDF5 offset/length exceeds this target's address width)"
990                )
991            }
992            FormatError::OffsetOverflow { offset, length } => {
993                write!(
994                    f,
995                    "offset arithmetic overflow: {offset} + {length} exceeds u64"
996                )
997            }
998            FormatError::AddressBelowBase { address, base } => {
999                write!(
1000                    f,
1001                    "file address {address} is below the superblock base address \
1002                     {base}, so it names no stored (base-relative) position"
1003                )
1004            }
1005            FormatError::Source(msg) => {
1006                write!(f, "byte source error: {msg}")
1007            }
1008            FormatError::LibverBoundsUnsatisfiable {
1009                writes,
1010                requested_low,
1011                requested_high,
1012            } => {
1013                write!(
1014                    f,
1015                    "requested library-version bounds [{requested_low}, {requested_high}] \
1016                     cannot be satisfied: this crate writes the v1.8 and {writes} formats"
1017                )
1018            }
1019            FormatError::LibverTooOldForContent {
1020                content,
1021                needs,
1022                writing,
1023            } => {
1024                write!(
1025                    f,
1026                    "{content} requires the {needs} format, but the requested \
1027                     library-version bounds write {writing}"
1028                )
1029            }
1030            FormatError::InvalidObjectReference(addr) => {
1031                write!(
1032                    f,
1033                    "invalid HDF5 object reference: address {addr:#x} is null/undefined \
1034                     or does not point at a group or dataset"
1035                )
1036            }
1037            FormatError::UnsupportedFillValueVersion(v) => {
1038                write!(f, "unsupported fill value message version: {v}")
1039            }
1040            FormatError::FillValueSizeMismatch { expected, actual } => {
1041                write!(
1042                    f,
1043                    "fill value size {actual} bytes does not match the dataset datatype \
1044                     element size of {expected} bytes"
1045                )
1046            }
1047            FormatError::AttributeMessageTooLarge { name, size } => {
1048                write!(
1049                    f,
1050                    "attribute {name:?} serializes to {size} bytes, past the \
1051                     {OBJECT_HEADER_MESSAGE_MAX}-byte limit of the object header's \
1052                     message size field"
1053                )
1054            }
1055            FormatError::ObjectHeaderMessageTooLarge { message_type, size } => {
1056                write!(
1057                    f,
1058                    "object header message {message_type:#06x} is {size} bytes, past the \
1059                     {OBJECT_HEADER_MESSAGE_MAX}-byte limit of the object header's \
1060                     message size field"
1061                )
1062            }
1063            FormatError::AttributeFieldTooLong {
1064                name,
1065                field,
1066                size,
1067                limit,
1068            } => {
1069                write!(
1070                    f,
1071                    "attribute {name:?} has a {size}-byte {field}, past the {limit}-byte limit of \
1072                     the attribute message's {field} size field"
1073                )
1074            }
1075            FormatError::DenseAttributeHeapTooLarge { limit } => {
1076                write!(
1077                    f,
1078                    "these attributes need more than the {limit}-byte address space of a dense \
1079                     attribute heap"
1080                )
1081            }
1082            FormatError::ZeroFixedStringWidth => {
1083                write!(
1084                    f,
1085                    "a fixed-width string datatype must be at least one byte wide"
1086                )
1087            }
1088            FormatError::FixedStringTooLong { index, len, width } => {
1089                write!(
1090                    f,
1091                    "string element {index} is {len} bytes, past the declared {width}-byte width"
1092                )
1093            }
1094            FormatError::NumericElementTooWide { size } => {
1095                write!(
1096                    f,
1097                    "a {size}-byte numeric element is wider than the 64-bit values these readers \
1098                     decode into"
1099                )
1100            }
1101        }
1102    }
1103}
1104
1105#[cfg(feature = "std")]
1106impl std::error::Error for FormatError {}
1107
1108/// How resolving a path can fail: at a component that is not a group, named, or
1109/// at anything else the parse refuses.
1110///
1111/// Crate-internal, and a separate type rather than another [`FormatError`]
1112/// variant, because the answer a caller wants for the first case is
1113/// [`Error::NotAGroup`] — the same error a *final* component that is not a group
1114/// returns (issue #352), so that one match covers a path that goes wrong
1115/// anywhere along it. A public `FormatError::NotAGroup` would be a variant no
1116/// caller could observe (`group_v2` is private, and the conversion below turns
1117/// every one of them into `Error::NotAGroup`) while making
1118/// `Error::Format(<a non-group>)` a shape a hand-written `Error::Format(..)`
1119/// could still produce. Here it is unrepresentable instead of merely unwritten.
1120#[derive(Debug)]
1121pub(crate) enum ResolveError {
1122    /// A component the walk had to descend through does not name a group. The
1123    /// string is that object's own root-relative path — empty for the root
1124    /// group, which is how this crate names the root throughout — and not the
1125    /// path that was asked for (issue #365).
1126    NotAGroup(String),
1127    /// Anything else, including a component that names nothing at all, which
1128    /// stays a [`FormatError::PathNotFound`] naming it.
1129    Format(FormatError),
1130}
1131
1132impl From<FormatError> for ResolveError {
1133    fn from(e: FormatError) -> Self {
1134        ResolveError::Format(e)
1135    }
1136}
1137
1138// ---------------------------------------------------------------------------
1139// High-level Error type
1140// ---------------------------------------------------------------------------
1141
1142/// Errors that can occur when using the high-level API.
1143#[cfg(feature = "std")]
1144#[derive(Debug)]
1145#[non_exhaustive]
1146pub enum Error {
1147    /// I/O error from the filesystem.
1148    Io(std::io::Error),
1149    /// Low-level format parsing error.
1150    Format(FormatError),
1151    /// The object at the given path is not a dataset.
1152    NotADataset(String),
1153    /// The object at the given path is not a group. A name that resolves to
1154    /// nothing at all is
1155    /// [`FormatError::PathNotFound`](crate::FormatError::PathNotFound) instead.
1156    ///
1157    /// The path may be an *intermediate* component of the one that was asked
1158    /// for: resolving `a/b/c` opens `a` and then `a/b` to look inside them, so
1159    /// a dataset at `a/b` reports `NotAGroup("a/b")` rather than anything about
1160    /// `a/b/c` (issue #365). `Group::group`, which takes a child name rather
1161    /// than a path, reports that name.
1162    NotAGroup(String),
1163    /// The child of the given name is not a committed (`H5Tcommit`) datatype. A
1164    /// name that resolves to nothing at all is
1165    /// [`FormatError::PathNotFound`](crate::FormatError::PathNotFound) instead.
1166    NotANamedDatatype(String),
1167    /// A required header message was not found.
1168    MissingMessage(crate::message_type::MessageType),
1169    /// An array shape error from the `ndarray` integration: either the flat
1170    /// data could not be reshaped to the dataset's dimensions, or a requested
1171    /// static rank (e.g. `read_array::<_, Ix2>`) did not match the dataset's
1172    /// runtime rank. Only constructed when the `ndarray` feature is enabled.
1173    Shape(String),
1174    /// A SWMR operation (e.g. [`crate::File::refresh`]) was requested on a file
1175    /// that was not opened for SWMR reading via `File::open_swmr`.
1176    SwmrUnsupported,
1177    /// An operation that needs exclusive access to the open file (e.g.
1178    /// [`crate::File::refresh`]) was requested while owned [`crate::Dataset`] /
1179    /// [`crate::Group`] handles, or a clone of the [`crate::File`], are still
1180    /// alive. Drop them and retry.
1181    HandlesOutstanding,
1182    /// A write (e.g. [`crate::Dataset::append`]) was requested on a file opened
1183    /// read-only. Open it with [`crate::File::open_rw`] to modify it in place.
1184    ReadOnly,
1185    /// A write was requested through a handle whose [`crate::File`] has already
1186    /// been sealed by [`crate::File::close`]. Immediate and staged edits are
1187    /// refused; reads through surviving handles still work. Re-open the file to
1188    /// modify it again.
1189    FileClosed,
1190    /// A [`crate::Dataset`] / [`crate::Group`] handle reached by object
1191    /// reference ([`crate::Dataset::dereference`]) was used after the file
1192    /// changed under it.
1193    ///
1194    /// Such a handle knows only the object-header address the reference gave
1195    /// it, and an edit can rewrite and relocate object headers, so there is no
1196    /// name left to look the object up by. Every handle opened by *path*
1197    /// re-resolves itself instead and never reports this. Dereference again from
1198    /// a fresh read to get a handle onto the current file.
1199    ///
1200    /// An immediate [`crate::Dataset::append`] rewrites a header where it stands
1201    /// and does not end such a handle; a commit does, as does staging one,
1202    /// [`crate::File::sync`], and [`crate::File::close`].
1203    StaleHandle,
1204    /// The object named by the payload has been *staged* by this read-write
1205    /// session and not yet published by [`crate::File::commit`], so the
1206    /// operation asked for would have had to read bytes that are not in the
1207    /// file.
1208    ///
1209    /// A staged creation is addressable as soon as it is staged: the handle
1210    /// [`crate::Group::create_group`] / [`crate::Group::create_group_with`] /
1211    /// [`crate::Group::create_dataset`] returns — and the one a lookup for that
1212    /// name gives back — can stage further edits under it, and a staged dataset
1213    /// answers [`crate::Dataset::shape`], [`crate::Dataset::maxshape`],
1214    /// [`crate::Dataset::dtype`], [`crate::Dataset::datatype`],
1215    /// [`crate::Dataset::is_chunked`], [`crate::Dataset::filters`] and
1216    /// [`crate::Dataset::filter_pipeline`] from what was staged. Everything that reads the object's bytes reports this until
1217    /// the commit: element reads, attribute reads, and the edits that rewrite an
1218    /// existing object in place ([`crate::Dataset::append`],
1219    /// [`crate::Dataset::write`], `set_attr` on a dataset). Add the elements to
1220    /// the builder that stages the dataset instead — or, for a dataset,
1221    /// [`crate::Dataset::append_staged`], which folds them into the pending
1222    /// creation.
1223    NotCommitted(String),
1224    /// A [`crate::Dataset`] / [`crate::Group`] handle onto a staged creation was
1225    /// used after that creation was **withdrawn**, so it names nothing: the path
1226    /// in the payload was staged when the handle was made, and this session has
1227    /// since dropped that staging without committing it.
1228    ///
1229    /// [`crate::Group::delete`] of an object staged in the same session is what
1230    /// withdraws one — including the second `delete` of a delete-then-create
1231    /// *replacement*, which leaves the deletion of the file's own object
1232    /// standing and the staged replacement gone. The handle is not retargeted at
1233    /// whatever the file holds at that path, because that object is precisely
1234    /// the one the session is removing; it reports this instead, and a fresh
1235    /// lookup by name is how to reach whatever the path means now.
1236    ///
1237    /// Only a handle *born* onto a staged creation can report it. One opened
1238    /// onto an object in the file names that object however the staged set
1239    /// changes around it.
1240    StagingWithdrawn(String),
1241    /// A staged edit (`write` / `set_attr` / `create_*` / `delete` / `copy` /
1242    /// `commit`) was requested on a file opened with
1243    /// [`crate::File::open_swmr_writer`], which permits only immediate
1244    /// [`crate::Dataset::append`]. Committing a structural edit would clear the
1245    /// SWMR-write flag out from under a concurrent reader, so the whole staged
1246    /// surface is refused in SWMR-writer mode.
1247    SwmrStagedUnsupported,
1248    /// The file or dataset is not a supported target for the SWMR append writer
1249    /// (e.g. a userblock or non-latest-format file, or a dataset that is
1250    /// filtered, not rank-1 with an unlimited dimension, or not
1251    /// Extensible-Array indexed). The payload is a human-readable reason.
1252    SwmrAppendUnsupported(&'static str),
1253    /// The dataset is not a supported target for
1254    /// [`Dataset::append_staged`](crate::Dataset::append_staged) — for
1255    /// example a dataset that is not chunked, not extensible along its first
1256    /// dimension, not indexed by an Extensible Array, higher than rank 1, uses a
1257    /// filter this engine cannot re-encode, has a big-endian on-disk element
1258    /// datatype (for a raw append), or has more than one hard link. The payload
1259    /// is a human-readable reason.
1260    AppendUnsupported(&'static str),
1261    /// The dataset or file is not a supported target for the fast, immediate
1262    /// in-place append
1263    /// ([`Dataset::append`](crate::Dataset::append)) — for
1264    /// example a userblock or non-latest-format file, a dataset whose
1265    /// Extensible-Array index is not yet allocated, one that is not rank-1 /
1266    /// unlimited / Extensible-Array indexed, one reachable through more than one
1267    /// hard link, or a path an uncommitted staged edit in the same session will
1268    /// relocate or delete. Distinct from [`AppendUnsupported`](Self::AppendUnsupported)
1269    /// so a caller can catch this fast-path refusal and fall back to the staged
1270    /// [`Dataset::append_staged`](crate::Dataset::append_staged). The
1271    /// payload is a human-readable reason.
1272    AppendInPlaceUnsupported(&'static str),
1273    /// The file or the requested object is not a supported target for the
1274    /// in-place editor ([`crate::File::open_rw`]) — for example a userblock or
1275    /// non-latest-format file, a group whose links are densely stored, or a
1276    /// dataset shape/datatype/filter combination the in-place writer cannot
1277    /// emit yet. The payload is a human-readable reason.
1278    EditUnsupported(&'static str),
1279    /// An object in the source file cannot be reproduced faithfully by
1280    /// [`repack`](crate::repack), so the repack was refused rather than write a
1281    /// silently degraded file — for example a variable-length, time, bitfield,
1282    /// or opaque datatype, a virtual/external data layout, an unsupported
1283    /// filter, or an object reference. The payload names the object and reason.
1284    RepackUnsupported(String),
1285    /// The file could not be opened because another process holds a conflicting
1286    /// OS advisory lock — for a writer ([`crate::File::open_swmr_writer`],
1287    /// [`crate::File::open_rw`]) this means another writer or reader is active;
1288    /// for a plain reader it means a writer is active. The lock is released
1289    /// automatically when the holder's process exits, so a crashed writer does
1290    /// not leave a stale lock. Locking can be disabled per open with
1291    /// [`crate::FileLocking::Disabled`] or globally with
1292    /// `HDF5_USE_FILE_LOCKING=FALSE`. The payload is a human-readable reason.
1293    FileLocked(String),
1294    /// The file could not be opened because its superblock's status-flags byte
1295    /// marks it as held by a writer — the durable flag
1296    /// [`crate::File::open_swmr_writer`] raises, and which a page-buffered
1297    /// session ([`crate::FileAccessProperties::with_page_buffer_size`]) raises
1298    /// alone. Unlike [`FileLocked`](Self::FileLocked) this outlives the process
1299    /// that set it, so it means either that a writer is active *or* that one
1300    /// exited without clearing it; the payload names
1301    /// [`crate::File::clear_swmr_flag`] (the `h5clear -s` equivalent) as the
1302    /// recovery for the latter.
1303    ///
1304    /// Which reader can get at the file depends on which mark it is, and the
1305    /// payload names the one that applies:
1306    ///
1307    /// - both bits, a live SWMR writer: follow it with
1308    ///   [`crate::File::open_swmr`];
1309    /// - the write bit alone, which no SWMR reader can follow: read a snapshot
1310    ///   with
1311    ///   [`crate::FileAccessProperties::with_write_mark_policy`], once the
1312    ///   writer has synced or closed.
1313    ///
1314    /// The payload is a human-readable reason.
1315    FileMarkedInUse(String),
1316    /// A [`commit`](crate::File::commit) failed, *and* could not put back a
1317    /// value it had already written over — so the file holds part of a batch
1318    /// that was refused.
1319    ///
1320    /// Every other edit a commit applies lands where nothing reaches it until
1321    /// the superblock is repointed, so a commit that stops short of that leaves
1322    /// the file exactly as it found it. A same-length value overwrite is the
1323    /// exception: it writes straight over the dataset's existing data block,
1324    /// which the live root already reaches. A refused commit therefore replays
1325    /// the prior bytes over each such write before returning, and this is what
1326    /// it returns instead when that replay itself failed.
1327    ///
1328    /// It is the one refusal after which a caller must **re-read** rather than
1329    /// simply retry: the datasets the batch overwrote may hold either value.
1330    ///
1331    /// Both errors are carried because in the shape this is most likely to take
1332    /// they say different things: a commit refused for a reason the caller can
1333    /// act on, followed by an I/O failure that prevented the restore. Reporting
1334    /// only the second would leave the caller retrying a batch without knowing
1335    /// what was wrong with it.
1336    CommitPartiallyApplied {
1337        /// Why the commit was refused — what a caller must fix before staging
1338        /// the batch again.
1339        refusal: Box<Error>,
1340        /// The write failure that then prevented the prior values being put
1341        /// back. This is the one that proves the file changed, so it is what
1342        /// [`source`](std::error::Error::source) reports.
1343        restore: Box<Error>,
1344    },
1345}
1346
1347#[cfg(feature = "std")]
1348impl fmt::Display for Error {
1349    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1350        match self {
1351            Error::Io(e) => write!(f, "I/O error: {e}"),
1352            Error::Format(e) => write!(f, "HDF5 format error: {e}"),
1353            Error::NotADataset(path) => write!(f, "not a dataset: {path}"),
1354            Error::NotAGroup(path) => write!(f, "not a group: {path}"),
1355            Error::NotANamedDatatype(path) => write!(f, "not a named datatype: {path}"),
1356            Error::MissingMessage(mt) => write!(f, "missing required message: {mt}"),
1357            Error::Shape(msg) => write!(f, "array shape error: {msg}"),
1358            Error::SwmrUnsupported => write!(
1359                f,
1360                "refresh requires a file opened with File::open_swmr (live handle)"
1361            ),
1362            Error::HandlesOutstanding => write!(
1363                f,
1364                "operation needs exclusive file access: drop outstanding Dataset/Group handles and File clones first"
1365            ),
1366            Error::ReadOnly => write!(
1367                f,
1368                "cannot write to a read-only file; open it with File::open_rw"
1369            ),
1370            Error::StaleHandle => write!(
1371                f,
1372                "this handle was reached by object reference and the file has changed since; \
1373                 it has no path to re-resolve, so dereference again"
1374            ),
1375            Error::NotCommitted(path) => write!(
1376                f,
1377                "\"{path}\" is staged and not written yet; File::commit publishes it"
1378            ),
1379            Error::StagingWithdrawn(path) => write!(
1380                f,
1381                "this handle was made onto the staged creation of \"{path}\", and that staging \
1382                 was withdrawn before any commit; look the path up again to reach what it means now"
1383            ),
1384            Error::FileClosed => write!(
1385                f,
1386                "cannot write through a handle after File::close; re-open the file to modify it"
1387            ),
1388            Error::SwmrStagedUnsupported => write!(
1389                f,
1390                "a file opened with File::open_swmr_writer allows only immediate Dataset::append, not staged edits"
1391            ),
1392            Error::SwmrAppendUnsupported(reason) => {
1393                write!(f, "unsupported SWMR append target: {reason}")
1394            }
1395            Error::AppendUnsupported(reason) => {
1396                write!(f, "unsupported append target: {reason}")
1397            }
1398            Error::AppendInPlaceUnsupported(reason) => {
1399                write!(f, "unsupported in-place append target: {reason}")
1400            }
1401            Error::EditUnsupported(reason) => {
1402                write!(f, "unsupported in-place edit target: {reason}")
1403            }
1404            Error::RepackUnsupported(reason) => {
1405                write!(f, "cannot repack faithfully: {reason}")
1406            }
1407            Error::FileLocked(reason) => write!(f, "file is locked: {reason}"),
1408            Error::FileMarkedInUse(reason) => write!(f, "file is marked in use: {reason}"),
1409            Error::CommitPartiallyApplied { refusal, restore } => write!(
1410                f,
1411                "a commit refused ({refusal}) could not restore a value it had overwritten \
1412                 ({restore}), so the file holds part of the refused batch"
1413            ),
1414        }
1415    }
1416}
1417
1418#[cfg(feature = "std")]
1419impl std::error::Error for Error {
1420    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1421        match self {
1422            Error::Io(e) => Some(e),
1423            Error::Format(e) => Some(e),
1424            Error::CommitPartiallyApplied { restore, .. } => Some(&**restore),
1425            _ => None,
1426        }
1427    }
1428}
1429
1430#[cfg(feature = "std")]
1431impl From<FormatError> for Error {
1432    fn from(e: FormatError) -> Self {
1433        Error::Format(e)
1434    }
1435}
1436
1437#[cfg(feature = "std")]
1438impl From<ResolveError> for Error {
1439    fn from(e: ResolveError) -> Self {
1440        match e {
1441            ResolveError::NotAGroup(path) => Error::NotAGroup(path),
1442            ResolveError::Format(e) => Error::Format(e),
1443        }
1444    }
1445}
1446
1447#[cfg(feature = "std")]
1448impl From<std::io::Error> for Error {
1449    fn from(e: std::io::Error) -> Self {
1450        Error::Io(e)
1451    }
1452}