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