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;
13
14/// Errors that can occur when parsing HDF5 binary format structures.
15#[derive(Debug, Clone, PartialEq, Eq)]
16#[non_exhaustive]
17pub enum FormatError {
18    /// The HDF5 magic signature was not found at any valid offset.
19    SignatureNotFound,
20    /// The superblock version is not supported.
21    UnsupportedVersion(u8),
22    /// Unexpected end of data.
23    UnexpectedEof {
24        /// Number of bytes expected.
25        expected: usize,
26        /// Number of bytes actually available.
27        available: usize,
28    },
29    /// Invalid offset size (must be 2, 4, or 8).
30    InvalidOffsetSize(u8),
31    /// Invalid length size (must be 2, 4, or 8).
32    InvalidLengthSize(u8),
33    /// Invalid object header signature.
34    InvalidObjectHeaderSignature,
35    /// Invalid object header version.
36    InvalidObjectHeaderVersion(u8),
37    /// Unknown message type that is marked as must-understand.
38    UnsupportedMessage(u16),
39    /// Invalid datatype class.
40    InvalidDatatypeClass(u8),
41    /// Invalid datatype version for a given class.
42    InvalidDatatypeVersion {
43        /// The type class.
44        class: u8,
45        /// The version found.
46        version: u8,
47    },
48    /// Invalid string padding type.
49    InvalidStringPadding(u8),
50    /// Invalid character set.
51    InvalidCharacterSet(u8),
52    /// Invalid byte order.
53    InvalidByteOrder(u8),
54    /// Invalid reference type.
55    InvalidReferenceType(u8),
56    /// Invalid file-space management strategy code in a File Space Info message.
57    InvalidFileSpaceStrategy(u8),
58    /// Unsupported File Space Info message version (only version 1 is handled).
59    UnsupportedFileSpaceInfoVersion(u8),
60    /// A paged file-space strategy was requested with a page size the writer
61    /// cannot use: it must be a power of two of at least 512 bytes.
62    InvalidFileSpacePageSize(u64),
63    /// A paged file-space strategy was requested alongside a userblock that is
64    /// not a whole number of pages. File-space pages are measured from the file
65    /// base, so the two boundaries coincide only when the userblock divides by
66    /// the page size: `(userblock bytes, page size)`.
67    UserblockNotPageAligned(u64, u64),
68    /// A userblock size the format does not define: it must be zero, or a power
69    /// of two of at least 512 bytes. A reader looks for the superblock at 0, 512,
70    /// 1024, and so on doubling, so any other size produces a file nothing can
71    /// open.
72    InvalidUserblockSize(u64),
73    /// More userblock content was supplied than the userblock region holds. The
74    /// overflow would displace the superblock, so it is refused rather than
75    /// truncated.
76    UserblockContentTooLarge {
77        /// Bytes supplied.
78        content: u64,
79        /// Bytes the userblock region holds.
80        userblock: u64,
81    },
82    /// A free-space manager block (`FSHD`/`FSSE`) is malformed.
83    InvalidFreeSpaceManager,
84    /// An enumeration datatype was built over a base type that is not an
85    /// integer. HDF5 enumerations must have a fixed-point base.
86    EnumBaseNotInteger,
87    /// An enumeration member's value does not occupy exactly the base type's
88    /// size: `(member name, expected bytes, actual bytes)`.
89    EnumMemberValueSize(String, u32, usize),
90    /// An enumeration member's integer value does not fit in the base type:
91    /// `(member name, value, base size in bytes)`.
92    EnumMemberValueRange(String, i64, u32),
93    /// A compound datatype has a zero total size.
94    InvalidCompoundSize,
95    /// A compound datatype contains no fields.
96    EmptyCompoundType,
97    /// A compound datatype contains the same field name more than once.
98    DuplicateCompoundField(String),
99    /// A compound field extends past the declared compound size.
100    CompoundFieldOutOfBounds {
101        /// Field name.
102        name: String,
103        /// Field byte offset.
104        offset: u64,
105        /// Field size in bytes.
106        field_size: u32,
107        /// Declared compound size in bytes.
108        compound_size: u32,
109    },
110    /// Two compound fields overlap.
111    CompoundFieldOverlap {
112        /// Earlier field in byte order.
113        first: String,
114        /// Later field in byte order.
115        second: String,
116    },
117    /// A named compound field was not present.
118    CompoundFieldMissing(String),
119    /// A compound field has an incompatible datatype.
120    CompoundFieldTypeMismatch(String),
121    /// Invalid dataspace version.
122    InvalidDataspaceVersion(u8),
123    /// Invalid dataspace type.
124    InvalidDataspaceType(u8),
125    /// Invalid data layout version.
126    InvalidLayoutVersion(u8),
127    /// Invalid data layout class.
128    InvalidLayoutClass(u8),
129    /// No data allocated for contiguous layout.
130    NoDataAllocated,
131    /// Type mismatch when reading data.
132    TypeMismatch {
133        /// Expected type description.
134        expected: &'static str,
135        /// Actual type description.
136        actual: &'static str,
137    },
138    /// Data size mismatch.
139    DataSizeMismatch {
140        /// Expected size in bytes.
141        expected: usize,
142        /// Actual size in bytes.
143        actual: usize,
144    },
145    /// Invalid local heap signature.
146    InvalidLocalHeapSignature,
147    /// Invalid local heap version.
148    InvalidLocalHeapVersion(u8),
149    /// Invalid B-tree v1 signature.
150    InvalidBTreeSignature,
151    /// Invalid B-tree node type.
152    InvalidBTreeNodeType(u8),
153    /// Invalid symbol table node signature.
154    InvalidSymbolTableNodeSignature,
155    /// Invalid symbol table node version.
156    InvalidSymbolTableNodeVersion(u8),
157    /// Path not found during group traversal.
158    PathNotFound(String),
159    /// Invalid Link message version.
160    InvalidLinkVersion(u8),
161    /// Invalid link type code.
162    InvalidLinkType(u8),
163    /// Invalid Link Info message version.
164    InvalidLinkInfoVersion(u8),
165    /// Invalid B-tree v2 signature.
166    InvalidBTreeV2Signature,
167    /// Invalid B-tree v2 version.
168    InvalidBTreeV2Version(u8),
169    /// Invalid fractal heap signature.
170    InvalidFractalHeapSignature,
171    /// Invalid fractal heap version.
172    InvalidFractalHeapVersion(u8),
173    /// Invalid heap ID type.
174    InvalidHeapIdType(u8),
175    /// A fractal-heap "huge" object's heap ID referenced a B-tree key that is
176    /// not present in the heap's huge-objects v2 B-tree.
177    HugeObjectNotFound(u64),
178    /// A fractal heap's huge-objects v2 B-tree is not the indirectly accessed,
179    /// non-filtered layout (record type 1) this reader decodes: either the tree
180    /// declares a different record type, or its records are too short to hold
181    /// that one. Reading its records as that layout would decode an object ID
182    /// out of another field's bytes.
183    UnexpectedHugeObjectBTree {
184        /// The record type the B-tree declares.
185        tree_type: u8,
186        /// The record size the B-tree declares, in bytes.
187        record_size: usize,
188        /// The bytes a type-1 record needs: address + length + object ID.
189        required: usize,
190    },
191    /// A fractal-heap object lives in an I/O-filter-encoded heap (filtered
192    /// managed or huge storage), whose filtered bytes this reader does not
193    /// decode. Link and attribute heaps are never filtered, so this does not
194    /// arise for them.
195    UnsupportedFilteredHeapObject,
196    /// A dataset uses the Virtual (VDS) data layout, which maps its elements to
197    /// regions of other datasets, possibly in other files. This reader does not
198    /// yet resolve virtual mappings, so such a dataset is refused rather than
199    /// read as empty or wrong.
200    UnsupportedVirtualLayout,
201    /// Invalid attribute message version.
202    InvalidAttributeVersion(u8),
203    /// Invalid Attribute Info message version.
204    InvalidAttributeInfoVersion(u8),
205    /// Invalid shared message version.
206    InvalidSharedMessageVersion(u8),
207    /// Invalid global heap collection signature.
208    InvalidGlobalHeapSignature,
209    /// Invalid global heap version.
210    InvalidGlobalHeapVersion(u8),
211    /// Global heap object not found.
212    GlobalHeapObjectNotFound {
213        /// Address of the collection.
214        collection_address: u64,
215        /// Index that was not found.
216        index: u16,
217    },
218    /// Variable-length data error.
219    VlDataError(String),
220    /// A variable-length read exceeded its configured element limit.
221    VariableLengthElementLimitExceeded {
222        /// Maximum number of elements permitted by the caller.
223        limit: usize,
224        /// Number of elements present in the selected data.
225        actual: u64,
226    },
227    /// A variable-length read exceeded its configured payload-byte limit.
228    VariableLengthByteLimitExceeded {
229        /// Maximum number of payload bytes permitted by the caller.
230        limit: usize,
231        /// Number of payload bytes required by the selected data.
232        required: u64,
233    },
234    /// Serialization error.
235    SerializationError(String),
236    /// Dataset is missing data.
237    DatasetMissingData,
238    /// Dataset is missing shape.
239    DatasetMissingShape,
240    /// The dataset's element count implied by its shape does not match the
241    /// amount of data supplied (`shape.product() * element_size != data.len()`).
242    ShapeDataMismatch {
243        /// Number of data bytes the shape requires (`product(shape) * element_size`).
244        expected: usize,
245        /// Number of data bytes actually supplied.
246        actual: usize,
247        /// Size in bytes of one element (the dataset's datatype size). Always
248        /// non-zero; used to report the mismatch in elements as well as bytes.
249        element_size: usize,
250    },
251    /// A chunked/filtered/extensible dataset's chunk geometry is invalid — for
252    /// example chunk dimensions whose rank disagrees with the shape, a zero chunk
253    /// dimension, a maximum shape whose rank disagrees with the shape or that is
254    /// smaller than the current shape, or chunking requested on a scalar dataset.
255    /// Reported up front so a malformed request is refused instead of panicking
256    /// in the chunk splitter or producing an unreadable dataset. The payload is a
257    /// human-readable reason.
258    InvalidChunkGeometry(&'static str),
259    /// Invalid filter pipeline version.
260    InvalidFilterPipelineVersion(u8),
261    /// Unsupported filter ID.
262    UnsupportedFilter(u16),
263    /// Filter processing error, including a stream that failed to decode. Every
264    /// filter in the pipeline — shuffle, scale-offset, LZF, deflate — reports a
265    /// bad chunk with this variant, so "this chunk did not decode" is one match
266    /// arm rather than one per compressor. The payload names the filter.
267    FilterError(String),
268    /// Compression error.
269    CompressionError(String),
270    /// Fletcher32 checksum mismatch.
271    Fletcher32Mismatch {
272        /// Expected checksum.
273        expected: u32,
274        /// Computed checksum.
275        computed: u32,
276    },
277    /// Chunked dataset read error.
278    ChunkedReadError(String),
279    /// Chunk assembly error.
280    ChunkAssemblyError(String),
281    /// CRC32C checksum mismatch.
282    ChecksumMismatch {
283        /// The checksum stored in the file.
284        expected: u32,
285        /// The checksum we computed.
286        computed: u32,
287    },
288    /// Maximum nesting/continuation depth exceeded (malformed data protection).
289    NestingDepthExceeded,
290    /// Duplicate dataset name detected during parallel metadata merge.
291    DuplicateDatasetName(String),
292    /// ZFP filter configuration is invalid (e.g. missing element type, rank out of range).
293    UnsupportedZfp(String),
294    /// A file-derived 64-bit value (an offset, length, size, or element count)
295    /// does not fit in the target integer type on this platform. This is the
296    /// guard that replaces silent `as usize` / `as u32` truncation: on a 32-bit
297    /// host, `usize` is 32 bits, so an HDF5 offset or length above `usize::MAX`
298    /// would otherwise wrap and read the wrong bytes. The original value is
299    /// preserved for diagnostics, and `target` names the type we tried to
300    /// narrow to (e.g. `"usize"`, `"u32"`).
301    ValueTooLargeForPlatform {
302        /// The original 64-bit value read from the file.
303        value: u64,
304        /// The platform integer type the value could not fit into.
305        target: &'static str,
306    },
307    /// Two file-derived values (typically an offset and a length) overflow `u64`
308    /// when added to form a slice bound. Reported instead of wrapping so a
309    /// malformed file cannot produce a wrapped or out-of-range index.
310    OffsetOverflow {
311        /// First operand (typically the base offset/address).
312        offset: u64,
313        /// Second operand (typically the length/size).
314        length: u64,
315    },
316    /// A random-access byte source failed to
317    /// supply the requested bytes. The string carries a backend-specific reason
318    /// (e.g. an underlying `std::io::Error` rendered to text), so this stays
319    /// `no_std`/`alloc`-friendly and free of an `std::io` dependency.
320    Source(String),
321    /// The library-version bounds requested via
322    /// [`FileBuilder::with_libver_bounds`](crate::FileBuilder::with_libver_bounds)
323    /// cannot be satisfied. This crate's writer emits exactly one on-disk format
324    /// (the version 3 / HDF5 1.10 superblock), so a bound that excludes it — an
325    /// upper bound older than 1.10, or a lower bound newer than 1.10 — is
326    /// unsatisfiable. The fields carry the format produced and the bounds asked
327    /// for, as [`LibVer::name`](crate::LibVer::name) labels.
328    LibverBoundsUnsatisfiable {
329        /// The library-version label of the format this crate writes.
330        writes: &'static str,
331        /// The requested lower bound.
332        requested_low: &'static str,
333        /// The requested upper bound.
334        requested_high: &'static str,
335    },
336    /// An HDF5 object reference (`H5R_OBJECT`) could not be resolved to an
337    /// object: the stored address is null or undefined (`HADDR_UNDEF`), or it
338    /// does not point at a group or dataset object header. The payload is the
339    /// stored (base-relative) address, preserved for diagnostics.
340    InvalidObjectReference(u64),
341    /// A Fill Value message (`0x0005`) has an on-disk version this crate does
342    /// not recognize (only 1, 2, and 3 are defined). The payload is the version
343    /// byte found.
344    UnsupportedFillValueVersion(u8),
345    /// A user-supplied fill value's byte width does not match the dataset's
346    /// datatype element size (for example a `u8` fill value on an `i32`
347    /// dataset). The fields carry the datatype element size and the fill value
348    /// size, both in bytes.
349    FillValueSizeMismatch {
350        /// The dataset datatype's element size in bytes.
351        expected: usize,
352        /// The supplied fill value's size in bytes.
353        actual: usize,
354    },
355    /// An attribute's serialized message is larger than the version 2 object
356    /// header's 2-byte message-size field can describe, so it cannot be stored
357    /// as a compact (in-header) attribute. Refused rather than written, because
358    /// a truncated size field would desynchronize every message after it. The
359    /// fields carry the attribute name and its serialized message size in
360    /// bytes; the limit is [`OBJECT_HEADER_MESSAGE_MAX`].
361    ///
362    /// A backstop rather than an outcome you should expect to see: the
363    /// whole-file writer selects dense (fractal-heap) storage for exactly the
364    /// attributes that would trip this, so no input reaches it today. It stays
365    /// because the limit it describes is a real property of the object header,
366    /// and any future path that must keep an attribute compact needs it.
367    AttributeMessageTooLarge {
368        /// The attribute's name.
369        name: String,
370        /// The attribute message's serialized size in bytes.
371        size: usize,
372    },
373    /// An object-header message is larger than the version 2 object header's
374    /// 2-byte message-size field can describe. This is the whole-file writer's
375    /// backstop against emitting a truncated size field, covering every message
376    /// it builds; callers that can name the offending object (for example an
377    /// attribute) report a more specific error first. The in-place editor
378    /// encodes headers separately and refuses the same condition with
379    /// [`Error::EditUnsupported`](crate::Error::EditUnsupported). The fields
380    /// carry the message type code and the message's serialized size in bytes;
381    /// the limit is [`OBJECT_HEADER_MESSAGE_MAX`].
382    ObjectHeaderMessageTooLarge {
383        /// The header message's type code (see the HDF5 message type table).
384        message_type: u16,
385        /// The message's serialized size in bytes.
386        size: usize,
387    },
388    /// An attribute's name, datatype or dataspace is longer than the attribute
389    /// message can describe: each of the three has a 2-byte length field, so a
390    /// longer one would truncate and produce a message that decodes as something
391    /// else. Refused rather than written.
392    ///
393    /// This bounds the attribute's *description*, not its data. An attribute
394    /// whose data is arbitrarily large is written to dense storage as a
395    /// fractal-heap huge object.
396    ///
397    /// Reported by the whole-file writer (including through `repack`), which
398    /// sends any attribute too large for an object-header message to dense
399    /// storage and so reaches this check.
400    ///
401    /// In practice `field` is always `"name"`. Every attribute this crate writes
402    /// is built from an [`AttrValue`](crate::AttrValue) — including the ones
403    /// `repack` carries over, which it refuses outright if it cannot represent
404    /// them — and no variant of it produces a datatype past 20 bytes or a
405    /// dataspace past 12, whatever the data. The other two are still checked,
406    /// because the message encodes all three lengths the same way, and named
407    /// rather than merged so that a datatype which does one day reach the limit
408    /// is diagnosable from the error rather than mistaken for a long name.
409    AttributeFieldTooLong {
410        /// The attribute's name.
411        name: String,
412        /// Which of the message's three 2-byte length fields overflowed:
413        /// `"name"`, `"datatype"` or `"dataspace"`. Diagnostic detail rather than
414        /// a discriminator to branch on, since only `"name"` is reachable today.
415        field: &'static str,
416        /// That field's encoded length in bytes.
417        size: usize,
418        /// The largest length the message can describe, in bytes.
419        limit: usize,
420    },
421    /// An object's attributes need more space than a dense attribute heap can
422    /// address: its offsets are 40 bits wide, so the blocks holding the
423    /// attributes cannot span more than `limit` bytes between them. Reaching this
424    /// takes about a terabyte of attributes on a single object.
425    ///
426    /// A set that fits the heap but not the host reports
427    /// [`FormatError::ValueTooLargeForPlatform`] instead, so the limit named here
428    /// is always the one that actually applied.
429    DenseAttributeHeapTooLarge {
430        /// The heap address space, in bytes.
431        limit: u64,
432    },
433}
434
435/// The largest message a version 2 object header can describe: its per-message
436/// size field is 2 bytes wide. A message past this must be refused rather than
437/// written with a truncated length (see
438/// [`FormatError::ObjectHeaderMessageTooLarge`]).
439pub const OBJECT_HEADER_MESSAGE_MAX: usize = u16::MAX as usize;
440
441impl fmt::Display for FormatError {
442    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
443        match self {
444            FormatError::SignatureNotFound => {
445                write!(f, "HDF5 signature not found at any valid offset")
446            }
447            FormatError::UnsupportedVersion(v) => {
448                write!(f, "unsupported superblock version: {v}")
449            }
450            FormatError::UnexpectedEof {
451                expected,
452                available,
453            } => {
454                write!(f, "unexpected EOF: need {expected} bytes, have {available}")
455            }
456            FormatError::InvalidOffsetSize(s) => {
457                write!(f, "invalid offset size: {s} (must be 2, 4, or 8)")
458            }
459            FormatError::InvalidLengthSize(s) => {
460                write!(f, "invalid length size: {s} (must be 2, 4, or 8)")
461            }
462            FormatError::InvalidObjectHeaderSignature => {
463                write!(f, "invalid object header signature")
464            }
465            FormatError::InvalidObjectHeaderVersion(v) => {
466                write!(f, "invalid object header version: {v}")
467            }
468            FormatError::UnsupportedMessage(id) => {
469                write!(
470                    f,
471                    "unsupported message type {id:#06x} marked as must-understand"
472                )
473            }
474            FormatError::InvalidDatatypeClass(c) => {
475                write!(f, "invalid datatype class: {c}")
476            }
477            FormatError::InvalidDatatypeVersion { class, version } => {
478                write!(f, "invalid datatype version {version} for class {class}")
479            }
480            FormatError::InvalidStringPadding(p) => {
481                write!(f, "invalid string padding type: {p}")
482            }
483            FormatError::InvalidCharacterSet(c) => {
484                write!(f, "invalid character set: {c}")
485            }
486            FormatError::InvalidByteOrder(b) => {
487                write!(f, "invalid byte order: {b}")
488            }
489            FormatError::InvalidReferenceType(r) => {
490                write!(f, "invalid reference type: {r}")
491            }
492            FormatError::InvalidFileSpaceStrategy(s) => {
493                write!(f, "invalid file-space strategy code: {s}")
494            }
495            FormatError::UnsupportedFileSpaceInfoVersion(v) => {
496                write!(f, "unsupported File Space Info message version: {v}")
497            }
498            FormatError::InvalidFileSpacePageSize(p) => {
499                write!(
500                    f,
501                    "invalid file-space page size {p}: must be a power of two >= 512"
502                )
503            }
504            FormatError::UserblockNotPageAligned(userblock, page_size) => {
505                write!(
506                    f,
507                    "userblock of {userblock} bytes is not a whole number of {page_size}-byte \
508                     file-space pages: a paged file measures its pages from the file base, so \
509                     the userblock must be a multiple of the page size (or zero)"
510                )
511            }
512            FormatError::InvalidUserblockSize(size) => {
513                write!(
514                    f,
515                    "invalid userblock size {size}: must be zero or a power of two >= 512"
516                )
517            }
518            FormatError::UserblockContentTooLarge { content, userblock } => {
519                write!(
520                    f,
521                    "{content} bytes of userblock content do not fit a userblock of {userblock} \
522                     bytes"
523                )
524            }
525            FormatError::InvalidFreeSpaceManager => {
526                write!(f, "malformed free-space manager block (FSHD/FSSE)")
527            }
528            FormatError::EnumBaseNotInteger => {
529                write!(
530                    f,
531                    "an enumeration's base type must be an integer (fixed-point) type"
532                )
533            }
534            FormatError::EnumMemberValueSize(name, expected, actual) => {
535                write!(
536                    f,
537                    "enumeration member '{name}' has a {actual}-byte value, but its base type is \
538                     {expected} bytes"
539                )
540            }
541            FormatError::EnumMemberValueRange(name, value, size) => {
542                write!(
543                    f,
544                    "enumeration member '{name}' value {value} does not fit in its \
545                     {size}-byte base type"
546                )
547            }
548            FormatError::InvalidCompoundSize => {
549                write!(f, "compound datatype size must be greater than zero")
550            }
551            FormatError::EmptyCompoundType => {
552                write!(f, "compound datatype must contain at least one field")
553            }
554            FormatError::DuplicateCompoundField(name) => {
555                write!(f, "duplicate compound field name: {name}")
556            }
557            FormatError::CompoundFieldOutOfBounds {
558                name,
559                offset,
560                field_size,
561                compound_size,
562            } => {
563                write!(
564                    f,
565                    "compound field {name:?} at offset {offset} with size {field_size} \
566                     exceeds compound size {compound_size}"
567                )
568            }
569            FormatError::CompoundFieldOverlap { first, second } => {
570                write!(f, "compound fields {first:?} and {second:?} overlap")
571            }
572            FormatError::CompoundFieldMissing(name) => {
573                write!(f, "compound field {name:?} is missing")
574            }
575            FormatError::CompoundFieldTypeMismatch(name) => {
576                write!(f, "compound field {name:?} has an incompatible datatype")
577            }
578            FormatError::InvalidDataspaceVersion(v) => {
579                write!(f, "invalid dataspace version: {v}")
580            }
581            FormatError::InvalidDataspaceType(t) => {
582                write!(f, "invalid dataspace type: {t}")
583            }
584            FormatError::InvalidLayoutVersion(v) => {
585                write!(f, "invalid data layout version: {v}")
586            }
587            FormatError::InvalidLayoutClass(c) => {
588                write!(f, "invalid data layout class: {c}")
589            }
590            FormatError::NoDataAllocated => {
591                write!(f, "no data allocated for contiguous layout")
592            }
593            FormatError::TypeMismatch { expected, actual } => {
594                write!(f, "type mismatch: expected {expected}, got {actual}")
595            }
596            FormatError::DataSizeMismatch { expected, actual } => {
597                write!(
598                    f,
599                    "data size mismatch: expected {expected} bytes, got {actual} bytes"
600                )
601            }
602            FormatError::InvalidLocalHeapSignature => {
603                write!(f, "invalid local heap signature")
604            }
605            FormatError::InvalidLocalHeapVersion(v) => {
606                write!(f, "invalid local heap version: {v}")
607            }
608            FormatError::InvalidBTreeSignature => {
609                write!(f, "invalid B-tree v1 signature")
610            }
611            FormatError::InvalidBTreeNodeType(t) => {
612                write!(f, "invalid B-tree node type: {t}")
613            }
614            FormatError::InvalidSymbolTableNodeSignature => {
615                write!(f, "invalid symbol table node signature")
616            }
617            FormatError::InvalidSymbolTableNodeVersion(v) => {
618                write!(f, "invalid symbol table node version: {v}")
619            }
620            FormatError::PathNotFound(p) => {
621                write!(f, "path not found: {p}")
622            }
623            FormatError::InvalidLinkVersion(v) => {
624                write!(f, "invalid link message version: {v}")
625            }
626            FormatError::InvalidLinkType(t) => {
627                write!(f, "invalid link type: {t}")
628            }
629            FormatError::InvalidLinkInfoVersion(v) => {
630                write!(f, "invalid link info message version: {v}")
631            }
632            FormatError::InvalidBTreeV2Signature => {
633                write!(f, "invalid B-tree v2 signature")
634            }
635            FormatError::InvalidBTreeV2Version(v) => {
636                write!(f, "invalid B-tree v2 version: {v}")
637            }
638            FormatError::InvalidFractalHeapSignature => {
639                write!(f, "invalid fractal heap signature")
640            }
641            FormatError::InvalidFractalHeapVersion(v) => {
642                write!(f, "invalid fractal heap version: {v}")
643            }
644            FormatError::InvalidHeapIdType(t) => {
645                write!(f, "invalid heap ID type: {t}")
646            }
647            FormatError::HugeObjectNotFound(id) => {
648                write!(f, "fractal-heap huge object {id} not found in B-tree")
649            }
650            FormatError::UnexpectedHugeObjectBTree {
651                tree_type,
652                record_size,
653                required,
654            } => {
655                write!(
656                    f,
657                    "fractal-heap huge-objects B-tree is not the expected record type 1: \
658                     type {tree_type}, records of {record_size} bytes (type 1 needs {required})"
659                )
660            }
661            FormatError::UnsupportedFilteredHeapObject => {
662                write!(f, "filtered fractal-heap objects are not supported")
663            }
664            FormatError::UnsupportedVirtualLayout => {
665                write!(f, "virtual (VDS) data layout is not supported")
666            }
667            FormatError::InvalidAttributeVersion(v) => {
668                write!(f, "invalid attribute message version: {v}")
669            }
670            FormatError::InvalidAttributeInfoVersion(v) => {
671                write!(f, "invalid attribute info message version: {v}")
672            }
673            FormatError::InvalidSharedMessageVersion(v) => {
674                write!(f, "invalid shared message version: {v}")
675            }
676            FormatError::InvalidGlobalHeapSignature => {
677                write!(f, "invalid global heap collection signature")
678            }
679            FormatError::InvalidGlobalHeapVersion(v) => {
680                write!(f, "invalid global heap version: {v}")
681            }
682            FormatError::GlobalHeapObjectNotFound {
683                collection_address,
684                index,
685            } => {
686                write!(
687                    f,
688                    "global heap object not found: collection {collection_address:#x}, index {index}"
689                )
690            }
691            FormatError::VlDataError(msg) => {
692                write!(f, "variable-length data error: {msg}")
693            }
694            FormatError::VariableLengthElementLimitExceeded { limit, actual } => {
695                write!(
696                    f,
697                    "variable-length element limit exceeded: limit is {limit}, data contains {actual}"
698                )
699            }
700            FormatError::VariableLengthByteLimitExceeded { limit, required } => {
701                write!(
702                    f,
703                    "variable-length payload limit exceeded: limit is {limit} bytes, \
704                     data requires {required} bytes"
705                )
706            }
707            FormatError::SerializationError(msg) => {
708                write!(f, "serialization error: {msg}")
709            }
710            FormatError::DatasetMissingData => {
711                write!(f, "dataset is missing data")
712            }
713            FormatError::DatasetMissingShape => {
714                write!(f, "dataset is missing shape")
715            }
716            FormatError::ShapeDataMismatch {
717                expected,
718                actual,
719                element_size,
720            } => {
721                // `element_size` is guaranteed non-zero at construction, so the
722                // element counts below are well defined.
723                write!(
724                    f,
725                    "shape/data mismatch: shape requires {} elements ({expected} bytes), \
726                     but {} elements ({actual} bytes) were supplied",
727                    expected / element_size,
728                    actual / element_size,
729                )
730            }
731            FormatError::InvalidChunkGeometry(reason) => {
732                write!(f, "invalid chunk geometry: {reason}")
733            }
734            FormatError::InvalidFilterPipelineVersion(v) => {
735                write!(f, "invalid filter pipeline version: {v}")
736            }
737            FormatError::UnsupportedFilter(id) => {
738                write!(f, "unsupported filter: {id}")
739            }
740            FormatError::FilterError(msg) => {
741                write!(f, "filter error: {msg}")
742            }
743            FormatError::CompressionError(msg) => {
744                write!(f, "compression error: {msg}")
745            }
746            FormatError::Fletcher32Mismatch { expected, computed } => {
747                write!(
748                    f,
749                    "fletcher32 mismatch: expected {expected:#010x}, computed {computed:#010x}"
750                )
751            }
752            FormatError::ChunkedReadError(msg) => {
753                write!(f, "chunked read error: {msg}")
754            }
755            FormatError::ChunkAssemblyError(msg) => {
756                write!(f, "chunk assembly error: {msg}")
757            }
758            FormatError::ChecksumMismatch { expected, computed } => {
759                write!(
760                    f,
761                    "checksum mismatch: expected {expected:#010x}, computed {computed:#010x}"
762                )
763            }
764            FormatError::NestingDepthExceeded => {
765                write!(f, "maximum nesting/continuation depth exceeded")
766            }
767            FormatError::DuplicateDatasetName(name) => {
768                write!(f, "duplicate dataset name during parallel merge: {name}")
769            }
770            FormatError::UnsupportedZfp(msg) => {
771                write!(f, "unsupported ZFP configuration: {msg}")
772            }
773            FormatError::ValueTooLargeForPlatform { value, target } => {
774                write!(
775                    f,
776                    "file value {value} does not fit in {target} on this platform \
777                     (a 64-bit HDF5 offset/length exceeds this target's address width)"
778                )
779            }
780            FormatError::OffsetOverflow { offset, length } => {
781                write!(
782                    f,
783                    "offset arithmetic overflow: {offset} + {length} exceeds u64"
784                )
785            }
786            FormatError::Source(msg) => {
787                write!(f, "byte source error: {msg}")
788            }
789            FormatError::LibverBoundsUnsatisfiable {
790                writes,
791                requested_low,
792                requested_high,
793            } => {
794                write!(
795                    f,
796                    "requested library-version bounds [{requested_low}, {requested_high}] \
797                     cannot be satisfied: this crate writes the {writes} format"
798                )
799            }
800            FormatError::InvalidObjectReference(addr) => {
801                write!(
802                    f,
803                    "invalid HDF5 object reference: address {addr:#x} is null/undefined \
804                     or does not point at a group or dataset"
805                )
806            }
807            FormatError::UnsupportedFillValueVersion(v) => {
808                write!(f, "unsupported fill value message version: {v}")
809            }
810            FormatError::FillValueSizeMismatch { expected, actual } => {
811                write!(
812                    f,
813                    "fill value size {actual} bytes does not match the dataset datatype \
814                     element size of {expected} bytes"
815                )
816            }
817            FormatError::AttributeMessageTooLarge { name, size } => {
818                write!(
819                    f,
820                    "attribute {name:?} serializes to {size} bytes, past the \
821                     {OBJECT_HEADER_MESSAGE_MAX}-byte limit of the object header's \
822                     message size field"
823                )
824            }
825            FormatError::ObjectHeaderMessageTooLarge { message_type, size } => {
826                write!(
827                    f,
828                    "object header message {message_type:#06x} is {size} bytes, past the \
829                     {OBJECT_HEADER_MESSAGE_MAX}-byte limit of the object header's \
830                     message size field"
831                )
832            }
833            FormatError::AttributeFieldTooLong {
834                name,
835                field,
836                size,
837                limit,
838            } => {
839                write!(
840                    f,
841                    "attribute {name:?} has a {size}-byte {field}, past the {limit}-byte limit of \
842                     the attribute message's {field} size field"
843                )
844            }
845            FormatError::DenseAttributeHeapTooLarge { limit } => {
846                write!(
847                    f,
848                    "these attributes need more than the {limit}-byte address space of a dense \
849                     attribute heap"
850                )
851            }
852        }
853    }
854}
855
856#[cfg(feature = "std")]
857impl std::error::Error for FormatError {}
858
859// ---------------------------------------------------------------------------
860// High-level Error type
861// ---------------------------------------------------------------------------
862
863/// Errors that can occur when using the high-level API.
864#[cfg(feature = "std")]
865#[derive(Debug)]
866#[non_exhaustive]
867pub enum Error {
868    /// I/O error from the filesystem.
869    Io(std::io::Error),
870    /// Low-level format parsing error.
871    Format(FormatError),
872    /// The object at the given path is not a dataset.
873    NotADataset(String),
874    /// A required header message was not found.
875    MissingMessage(crate::message_type::MessageType),
876    /// Alignment or size error for zero-copy typed access.
877    AlignmentError(String),
878    /// An array shape error from the `ndarray` integration: either the flat
879    /// data could not be reshaped to the dataset's dimensions, or a requested
880    /// static rank (e.g. `read_array::<_, Ix2>`) did not match the dataset's
881    /// runtime rank. Only constructed when the `ndarray` feature is enabled.
882    Shape(String),
883    /// A SWMR operation (e.g. [`crate::File::refresh`]) was requested on a file
884    /// that was not opened for SWMR reading via `File::open_swmr`.
885    SwmrUnsupported,
886    /// An operation that needs exclusive access to the open file (e.g.
887    /// [`crate::File::refresh`]) was requested while owned [`crate::Dataset`] /
888    /// [`crate::Group`] handles, or a clone of the [`crate::File`], are still
889    /// alive. Drop them and retry.
890    HandlesOutstanding,
891    /// A write (e.g. [`crate::Dataset::append`]) was requested on a file opened
892    /// read-only. Open it with [`crate::File::open_rw`] to modify it in place.
893    ReadOnly,
894    /// A write was requested through a handle whose [`crate::File`] has already
895    /// been sealed by [`crate::File::close`]. Immediate and staged edits are
896    /// refused; reads through surviving handles still work. Re-open the file to
897    /// modify it again.
898    FileClosed,
899    /// A staged edit (`write` / `set_attr` / `create_*` / `delete` / `copy` /
900    /// `commit`) was requested on a file opened with
901    /// [`crate::File::open_swmr_writer`], which permits only immediate
902    /// [`crate::Dataset::append`]. Committing a structural edit would clear the
903    /// SWMR-write flag out from under a concurrent reader, so the whole staged
904    /// surface is refused in SWMR-writer mode.
905    SwmrStagedUnsupported,
906    /// The file or dataset is not a supported target for the SWMR append writer
907    /// (e.g. a userblock or non-latest-format file, or a dataset that is
908    /// filtered, not rank-1 with an unlimited dimension, or not
909    /// Extensible-Array indexed). The payload is a human-readable reason.
910    SwmrAppendUnsupported(&'static str),
911    /// The dataset is not a supported target for
912    /// [`Dataset::append_staged`](crate::Dataset::append_staged) — for
913    /// example a dataset that is not chunked, not extensible along its first
914    /// dimension, not indexed by an Extensible Array, higher than rank 1, uses a
915    /// filter this engine cannot re-encode, has a big-endian on-disk element
916    /// datatype (for a raw append), or has more than one hard link. The payload
917    /// is a human-readable reason.
918    AppendUnsupported(&'static str),
919    /// The dataset or file is not a supported target for the fast, immediate
920    /// in-place append
921    /// ([`Dataset::append`](crate::Dataset::append)) — for
922    /// example a userblock or non-latest-format file, a dataset whose
923    /// Extensible-Array index is not yet allocated, one that is not rank-1 /
924    /// unlimited / Extensible-Array indexed, one reachable through more than one
925    /// hard link, or a path an uncommitted staged edit in the same session will
926    /// relocate or delete. Distinct from [`AppendUnsupported`](Self::AppendUnsupported)
927    /// so a caller can catch this fast-path refusal and fall back to the staged
928    /// [`Dataset::append_staged`](crate::Dataset::append_staged). The
929    /// payload is a human-readable reason.
930    AppendInPlaceUnsupported(&'static str),
931    /// The file or the requested object is not a supported target for the
932    /// in-place editor ([`crate::File::open_rw`]) — for example a userblock or
933    /// non-latest-format file, a group whose links are densely stored, or a
934    /// dataset shape/datatype/filter combination the in-place writer cannot
935    /// emit yet. The payload is a human-readable reason.
936    EditUnsupported(&'static str),
937    /// An object in the source file cannot be reproduced faithfully by
938    /// [`repack`](crate::repack), so the repack was refused rather than write a
939    /// silently degraded file — for example a variable-length, time, bitfield,
940    /// or opaque datatype, a virtual/external data layout, an unsupported
941    /// filter, or an object reference. The payload names the object and reason.
942    RepackUnsupported(String),
943    /// The file could not be opened because another process holds a conflicting
944    /// OS advisory lock — for a writer ([`crate::File::open_swmr_writer`],
945    /// [`crate::File::open_rw`]) this means another writer or reader is active;
946    /// for a plain reader it means a writer is active. The lock is released
947    /// automatically when the holder's process exits, so a crashed writer does
948    /// not leave a stale lock. Locking can be disabled per open with
949    /// [`crate::FileLocking::Disabled`] or globally with
950    /// `HDF5_USE_FILE_LOCKING=FALSE`. The payload is a human-readable reason.
951    FileLocked(String),
952    /// The file could not be opened because its superblock's status-flags byte
953    /// marks it as held by a writer — the durable flag
954    /// [`crate::File::open_swmr_writer`] raises, and the reference C library
955    /// raises for any writer. Unlike [`FileLocked`](Self::FileLocked) this
956    /// outlives the process that set it, so it means either that a writer is
957    /// active *or* that one exited without clearing it; the payload names
958    /// [`crate::File::clear_swmr_flag`] (the `h5clear -s` equivalent) as the
959    /// recovery for the latter. A live SWMR writer can still be followed with
960    /// [`crate::File::open_swmr`]. The payload is a human-readable reason.
961    FileMarkedInUse(String),
962}
963
964#[cfg(feature = "std")]
965impl fmt::Display for Error {
966    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
967        match self {
968            Error::Io(e) => write!(f, "I/O error: {e}"),
969            Error::Format(e) => write!(f, "HDF5 format error: {e}"),
970            Error::NotADataset(path) => write!(f, "not a dataset: {path}"),
971            Error::MissingMessage(mt) => write!(f, "missing required message: {mt}"),
972            Error::AlignmentError(msg) => write!(f, "alignment error: {msg}"),
973            Error::Shape(msg) => write!(f, "array shape error: {msg}"),
974            Error::SwmrUnsupported => write!(
975                f,
976                "refresh requires a file opened with File::open_swmr (live handle)"
977            ),
978            Error::HandlesOutstanding => write!(
979                f,
980                "operation needs exclusive file access: drop outstanding Dataset/Group handles and File clones first"
981            ),
982            Error::ReadOnly => write!(
983                f,
984                "cannot write to a read-only file; open it with File::open_rw"
985            ),
986            Error::FileClosed => write!(
987                f,
988                "cannot write through a handle after File::close; re-open the file to modify it"
989            ),
990            Error::SwmrStagedUnsupported => write!(
991                f,
992                "a file opened with File::open_swmr_writer allows only immediate Dataset::append, not staged edits"
993            ),
994            Error::SwmrAppendUnsupported(reason) => {
995                write!(f, "unsupported SWMR append target: {reason}")
996            }
997            Error::AppendUnsupported(reason) => {
998                write!(f, "unsupported append target: {reason}")
999            }
1000            Error::AppendInPlaceUnsupported(reason) => {
1001                write!(f, "unsupported in-place append target: {reason}")
1002            }
1003            Error::EditUnsupported(reason) => {
1004                write!(f, "unsupported in-place edit target: {reason}")
1005            }
1006            Error::RepackUnsupported(reason) => {
1007                write!(f, "cannot repack faithfully: {reason}")
1008            }
1009            Error::FileLocked(reason) => write!(f, "file is locked: {reason}"),
1010            Error::FileMarkedInUse(reason) => write!(f, "file is marked in use: {reason}"),
1011        }
1012    }
1013}
1014
1015#[cfg(feature = "std")]
1016impl std::error::Error for Error {
1017    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1018        match self {
1019            Error::Io(e) => Some(e),
1020            Error::Format(e) => Some(e),
1021            _ => None,
1022        }
1023    }
1024}
1025
1026#[cfg(feature = "std")]
1027impl From<FormatError> for Error {
1028    fn from(e: FormatError) -> Self {
1029        Error::Format(e)
1030    }
1031}
1032
1033#[cfg(feature = "std")]
1034impl From<std::io::Error> for Error {
1035    fn from(e: std::io::Error) -> Self {
1036        Error::Io(e)
1037    }
1038}