Skip to main content

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}