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