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}