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