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