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 generic serde-originated error (from `Error::custom`).
57    Custom(String),
58}
59
60impl fmt::Display for MatError {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        match self {
63            MatError::Hdf5(e) => write!(f, "HDF5 error: {e}"),
64            MatError::Format(e) => write!(f, "HDF5 format error: {e}"),
65            MatError::Io(e) => write!(f, "I/O error: {e}"),
66            MatError::RootMustBeStruct => write!(
67                f,
68                "top-level value must be a struct with named fields; each field becomes a MATLAB variable"
69            ),
70            MatError::UnsupportedType(t) => write!(f, "unsupported Rust type for MAT v7.3: {t}"),
71            MatError::MixedSequenceElementTypes => write!(
72                f,
73                "sequence elements have mixed primitive types; all elements of a numeric array must share a type"
74            ),
75            MatError::RaggedMatrix { expected, got } => write!(
76                f,
77                "ragged 2-D matrix: expected row length {expected}, got {got}"
78            ),
79            MatError::ShapeMismatch { expected, actual } => {
80                write!(f, "shape mismatch: expected {expected}, got {actual}")
81            }
82            MatError::MissingField(name) => write!(f, "missing required field: {name}"),
83            MatError::UnknownClass(c) => write!(f, "unknown MATLAB_class: {c:?}"),
84            MatError::UnsupportedMatlabClass(c) => write!(
85                f,
86                "MATLAB class {c:?} is not yet supported for reading (modern `string` is; \
87                 other MCOS opaque classes such as datetime/categorical/table are refused for now)"
88            ),
89            MatError::Utf16Decode(msg) => write!(f, "UTF-16 decode: {msg}"),
90            MatError::Custom(msg) => write!(f, "{msg}"),
91        }
92    }
93}
94
95impl std::error::Error for MatError {
96    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
97        match self {
98            MatError::Hdf5(e) => Some(e),
99            MatError::Format(e) => Some(e),
100            MatError::Io(e) => Some(e),
101            _ => None,
102        }
103    }
104}
105
106impl From<Hdf5Error> for MatError {
107    fn from(e: Hdf5Error) -> Self {
108        MatError::Hdf5(e)
109    }
110}
111
112impl From<FormatError> for MatError {
113    fn from(e: FormatError) -> Self {
114        MatError::Format(e)
115    }
116}
117
118impl From<std::io::Error> for MatError {
119    fn from(e: std::io::Error) -> Self {
120        MatError::Io(e)
121    }
122}
123
124#[cfg(feature = "serde")]
125impl serde::ser::Error for MatError {
126    fn custom<T: fmt::Display>(msg: T) -> Self {
127        MatError::Custom(msg.to_string())
128    }
129}
130
131#[cfg(feature = "serde")]
132impl serde::de::Error for MatError {
133    fn custom<T: fmt::Display>(msg: T) -> Self {
134        MatError::Custom(msg.to_string())
135    }
136}