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
//! 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 2-D matrix had inconsistent row lengths.
RaggedMatrix {
/// Expected row length (first row).
expected: usize,
/// The row that differed.
got: usize,
},
/// 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,
/// 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::RaggedMatrix { expected, got } => write!(
f,
"ragged 2-D matrix: expected row length {expected}, got {got}"
),
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::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())
}
}