hdf5_pure/mat/error.rs
1//! Error type for MATLAB v7.3 serde (de)serialization.
2
3use core::fmt;
4
5use crate::error::{Error as Hdf5Error, FormatError};
6
7/// Errors that can occur when (de)serializing `.mat` v7.3 files.
8///
9/// Marked `#[non_exhaustive]`: reading MATLAB's MCOS opaque classes is an
10/// ongoing effort (`datetime`, `categorical`, `table`, `containers.Map`,
11/// `dictionary`, …), and each newly decoded — or newly refused — class can
12/// introduce a more specific error variant. Keeping the enum open lets those
13/// additions land without a breaking change, so downstream `match`es must
14/// include a wildcard arm.
15#[derive(Debug)]
16#[non_exhaustive]
17pub enum MatError {
18 /// Underlying HDF5 I/O or format error.
19 Hdf5(Hdf5Error),
20 /// Underlying HDF5 format parse error.
21 Format(FormatError),
22 /// I/O error when reading or writing a file path.
23 Io(std::io::Error),
24 /// Top-level must be a struct with named fields (each field becomes a MATLAB variable).
25 RootMustBeStruct,
26 /// The requested Rust type has no MATLAB v7.3 encoding in this crate.
27 UnsupportedType(&'static str),
28 /// A sequence contained elements of different primitive types.
29 MixedSequenceElementTypes,
30 /// A 2-D matrix had inconsistent row lengths.
31 RaggedMatrix {
32 /// Expected row length (first row).
33 expected: usize,
34 /// The row that differed.
35 got: usize,
36 },
37 /// A dataset's on-disk shape didn't match the Rust type.
38 ShapeMismatch {
39 /// The Rust side's expectation.
40 expected: String,
41 /// What the file contained.
42 actual: String,
43 },
44 /// A required struct field was missing from the file.
45 MissingField(String),
46 /// A `MATLAB_class` attribute value wasn't recognized.
47 UnknownClass(String),
48 /// A recognized but not-yet-supported MATLAB class was encountered on read
49 /// — an MCOS opaque class (`datetime`, `categorical`, `table`,
50 /// `containers.Map`, `dictionary`, an enumeration, a user `classdef`, …)
51 /// whose decoder is not yet implemented. Refused by name rather than
52 /// misread; the modern `string` class is supported.
53 UnsupportedMatlabClass(String),
54 /// UTF-16 decoding of a `char` dataset failed.
55 Utf16Decode(String),
56 /// A [`DataProducer`](crate::mat::DataProducer) wrote the wrong number of
57 /// bytes for a block. Refused rather than written: a block of the wrong size
58 /// displaces every address after it, and the result would be a file that
59 /// fails to open for reasons that no longer point back here.
60 BlockSizeMismatch {
61 /// Block index the producer was asked for.
62 block: usize,
63 /// Bytes it had to write, as
64 /// [`Blocking::block_len`](crate::mat::Blocking::block_len) reports.
65 expected: usize,
66 /// Bytes it actually wrote.
67 actual: usize,
68 },
69 /// A producer-backed dataset was asked for on a builder configured for
70 /// compression. The layout needs each block's exact on-disk size before it
71 /// writes anything, and a compressed block's size is not knowable without
72 /// compressing it — which would buffer the data the path exists to avoid.
73 CompressionUnsupportedForBlocks,
74 /// A generic serde-originated error (from `Error::custom`).
75 Custom(String),
76}
77
78impl fmt::Display for MatError {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 match self {
81 MatError::Hdf5(e) => write!(f, "HDF5 error: {e}"),
82 MatError::Format(e) => write!(f, "HDF5 format error: {e}"),
83 MatError::Io(e) => write!(f, "I/O error: {e}"),
84 MatError::RootMustBeStruct => write!(
85 f,
86 "top-level value must be a struct with named fields; each field becomes a MATLAB variable"
87 ),
88 MatError::UnsupportedType(t) => write!(f, "unsupported Rust type for MAT v7.3: {t}"),
89 MatError::MixedSequenceElementTypes => write!(
90 f,
91 "sequence elements have mixed primitive types; all elements of a numeric array must share a type"
92 ),
93 MatError::RaggedMatrix { expected, got } => write!(
94 f,
95 "ragged 2-D matrix: expected row length {expected}, got {got}"
96 ),
97 MatError::ShapeMismatch { expected, actual } => {
98 write!(f, "shape mismatch: expected {expected}, got {actual}")
99 }
100 MatError::MissingField(name) => write!(f, "missing required field: {name}"),
101 MatError::UnknownClass(c) => write!(f, "unknown MATLAB_class: {c:?}"),
102 MatError::UnsupportedMatlabClass(c) => write!(
103 f,
104 "MATLAB class {c:?} is not yet supported for reading (modern `string` is; \
105 other MCOS opaque classes such as datetime/categorical/table are refused for now)"
106 ),
107 MatError::Utf16Decode(msg) => write!(f, "UTF-16 decode: {msg}"),
108 MatError::BlockSizeMismatch {
109 block,
110 expected,
111 actual,
112 } => write!(
113 f,
114 "block producer wrote {actual} bytes for block {block}, which must carry exactly \
115 {expected}"
116 ),
117 MatError::CompressionUnsupportedForBlocks => write!(
118 f,
119 "a producer-backed dataset cannot be compressed: its blocks' on-disk sizes must be \
120 known before the file is laid out"
121 ),
122 MatError::Custom(msg) => write!(f, "{msg}"),
123 }
124 }
125}
126
127impl std::error::Error for MatError {
128 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
129 match self {
130 MatError::Hdf5(e) => Some(e),
131 MatError::Format(e) => Some(e),
132 MatError::Io(e) => Some(e),
133 _ => None,
134 }
135 }
136}
137
138impl From<Hdf5Error> for MatError {
139 fn from(e: Hdf5Error) -> Self {
140 MatError::Hdf5(e)
141 }
142}
143
144impl From<FormatError> for MatError {
145 fn from(e: FormatError) -> Self {
146 MatError::Format(e)
147 }
148}
149
150impl From<std::io::Error> for MatError {
151 fn from(e: std::io::Error) -> Self {
152 MatError::Io(e)
153 }
154}
155
156#[cfg(feature = "serde")]
157impl serde::ser::Error for MatError {
158 fn custom<T: fmt::Display>(msg: T) -> Self {
159 MatError::Custom(msg.to_string())
160 }
161}
162
163#[cfg(feature = "serde")]
164impl serde::de::Error for MatError {
165 fn custom<T: fmt::Display>(msg: T) -> Self {
166 MatError::Custom(msg.to_string())
167 }
168}