use std::collections::HashMap;
use crate::convert::TryToUsize;
use crate::error::FormatError;
use crate::file_writer::AttrValue;
use crate::mat::class::MatClass;
use crate::mat::de::mcos_reader::{self, Mcos, PropValue};
use crate::mat::error::MatError;
use crate::mat::string_object::MCOS_MAGIC_NUMBER;
use crate::mat::utf16;
use crate::mat::value::{MatValue, NumVec, ScalarNum};
use crate::reader::{Dataset, File, Group, Object};
use crate::types::DType;
const MAX_NESTING_DEPTH: usize = 256;
pub(crate) fn read_file(bytes: &[u8]) -> Result<Vec<(String, MatValue)>, MatError> {
let file = File::from_bytes(bytes.to_vec()).map_err(MatError::Hdf5)?;
let mcos = Mcos::parse(&file)?;
let root = file.root();
read_group(&root, mcos.as_ref(), false, 0)
}
fn read_group(
group: &Group<'_>,
mcos: Option<&Mcos<'_>>,
in_heap: bool,
depth: usize,
) -> Result<Vec<(String, MatValue)>, MatError> {
if depth > MAX_NESTING_DEPTH {
return Err(MatError::Format(FormatError::NestingDepthExceeded));
}
let mut out = Vec::new();
for name in group.datasets().map_err(MatError::Hdf5)? {
if name.starts_with('#') {
continue;
}
let ds = group.dataset(&name).map_err(MatError::Hdf5)?;
out.push((name, read_dataset(&ds, mcos, in_heap, depth)?));
}
for name in group.groups().map_err(MatError::Hdf5)? {
if name.starts_with('#') {
continue;
}
let sub = group.group(&name).map_err(MatError::Hdf5)?;
out.push((name, read_group_as_value(&sub, mcos, in_heap, depth + 1)?));
}
Ok(out)
}
fn read_group_as_value(
group: &Group<'_>,
mcos: Option<&Mcos<'_>>,
in_heap: bool,
depth: usize,
) -> Result<MatValue, MatError> {
if let Some(mcos) = mcos {
if let Some(value) = try_decode_enum_instance(group, mcos, depth)? {
return Ok(value);
}
}
let fields = read_group(group, mcos, in_heap, depth)?;
Ok(MatValue::Struct(fields))
}
fn try_decode_enum_instance(
group: &Group<'_>,
mcos: &Mcos<'_>,
depth: usize,
) -> Result<Option<MatValue>, MatError> {
let tag_ds = match group.dataset("EnumerationInstanceTag") {
Ok(ds) => ds,
Err(_) => return Ok(None),
};
let tag = tag_ds.read_u32().map_err(MatError::Hdf5)?;
if tag.first() != Some(&MCOS_MAGIC_NUMBER) {
return Ok(None);
}
let class_id = read_enum_scalar_u32(group, "ClassName")?;
let class_name = mcos
.class_name(class_id)
.ok()
.filter(|name| !name.is_empty())
.or_else(|| enum_class_from_attr(group))
.ok_or_else(|| {
MatError::Custom(format!(
"enum ClassName id {class_id} did not resolve and no MATLAB_class fallback was usable"
))
})?;
let pool: Vec<String> = read_enum_u32_vec(group, "ValueNames")?
.iter()
.map(|&idx| mcos.name(idx))
.collect::<Result<_, _>>()?;
let names = enum_member_names(&pool, &read_enum_indices(group, depth)?)?;
Ok(Some(mcos_reader::build_enum_value(class_name, names)))
}
fn enum_member_names(pool: &[String], indices: &[usize]) -> Result<Vec<String>, MatError> {
indices
.iter()
.map(|&i| {
pool.get(i).cloned().ok_or_else(|| {
MatError::Custom(format!(
"enum ValueIndices index {i} out of range (pool has {} members)",
pool.len()
))
})
})
.collect()
}
fn enum_dataset<'f>(group: &Group<'f>, field: &str) -> Result<Dataset<'f>, MatError> {
group.dataset(field).map_err(|e| {
MatError::Custom(format!(
"malformed enum instance: cannot read required dataset {field:?} ({e})"
))
})
}
fn read_enum_scalar_u32(group: &Group<'_>, field: &str) -> Result<u32, MatError> {
let ds = enum_dataset(group, field)?;
Ok(ds
.read_u32()
.map_err(MatError::Hdf5)?
.first()
.copied()
.unwrap_or(0))
}
fn read_enum_u32_vec(group: &Group<'_>, field: &str) -> Result<Vec<u32>, MatError> {
let ds = enum_dataset(group, field)?;
ds.read_u32().map_err(MatError::Hdf5)
}
fn read_enum_indices(group: &Group<'_>, depth: usize) -> Result<Vec<usize>, MatError> {
let ds = enum_dataset(group, "ValueIndices")?;
let dtype = ds.dtype().map_err(MatError::Hdf5)?;
if dtype != DType::U32 {
return Err(MatError::Custom(format!(
"enum ValueIndices has datatype {dtype:?}; expected uint32"
)));
}
let value = read_dataset(&ds, None, false, depth)?;
enum_indices_to_usize(&value)
}
fn enum_indices_to_usize(value: &MatValue) -> Result<Vec<usize>, MatError> {
match value {
MatValue::Scalar(ScalarNum::U32(x)) => Ok(vec![x.to_usize().map_err(MatError::Format)?]),
MatValue::Vec1D(NumVec::U32(v))
| MatValue::Matrix {
vec: NumVec::U32(v),
..
} => v
.iter()
.map(|&x| x.to_usize().map_err(MatError::Format))
.collect(),
other => Err(MatError::Custom(format!(
"enum ValueIndices is not a uint32 array (got {})",
other.kind()
))),
}
}
fn enum_class_from_attr(group: &Group<'_>) -> Option<String> {
let attrs = group.attrs().ok()?;
let class = mcos_reader::raw_matlab_class(&attrs)?;
(class != "struct").then_some(class)
}
fn read_dataset(
ds: &Dataset<'_>,
mcos: Option<&Mcos<'_>>,
in_heap: bool,
depth: usize,
) -> Result<MatValue, MatError> {
if depth > MAX_NESTING_DEPTH {
return Err(MatError::Format(FormatError::NestingDepthExceeded));
}
let attrs = ds.attrs().map_err(MatError::Hdf5)?;
if let Some(decode) = mcos_reader::matlab_object_decode(&attrs) {
return decode_opaque(ds, &attrs, decode, mcos, depth);
}
let class = matlab_class_from_attrs(&attrs)?;
let shape = ds.shape().map_err(MatError::Hdf5)?;
let dtype = ds.dtype().map_err(MatError::Hdf5)?;
let is_empty = is_empty_attr(&attrs) || shape.contains(&0);
let mut probed_u32: Option<Vec<u32>> = None;
if in_heap && !is_empty && dtype == DType::U32 {
if let Some(mcos) = mcos {
let raw = ds.read_u32().map_err(MatError::Hdf5)?;
if let Ok(meta) = mcos_reader::parse_opaque_metadata(&raw) {
if !meta.object_ids.is_empty() {
return decode_object_ids(mcos, &meta.object_ids, depth);
}
}
probed_u32 = Some(raw);
}
}
let class = class.unwrap_or_else(|| class_from_dtype(&dtype));
if is_empty {
if matches!(class, MatClass::Double | MatClass::Single) && is_complex_dtype(&dtype) {
return empty_complex_value(class, &shape);
}
if class == MatClass::Struct {
return Ok(MatValue::EmptyStructArray);
}
return Ok(empty_value_for_class(class));
}
if class == MatClass::UInt32 {
if let Some(raw) = probed_u32 {
return numeric_value_from_flat(NumVec::U32(raw), &shape);
}
}
match class {
MatClass::Char => {
let units = ds.read_u16().map_err(MatError::Hdf5)?;
let s = utf16::decode_utf16(&units)?;
Ok(MatValue::String(s))
}
MatClass::Logical => read_numeric(ds, &shape, class),
MatClass::Double
| MatClass::Single
| MatClass::Int8
| MatClass::Int16
| MatClass::Int32
| MatClass::Int64
| MatClass::UInt8
| MatClass::UInt16
| MatClass::UInt32
| MatClass::UInt64 => {
if is_complex_dtype(&dtype) {
read_complex(ds, &shape, class)
} else {
read_numeric(ds, &shape, class)
}
}
MatClass::Struct => Err(MatError::Custom(
"dataset has MATLAB_class='struct'; expected a group".into(),
)),
MatClass::Cell => read_cell(ds, mcos, in_heap, depth),
}
}
fn read_cell(
ds: &Dataset<'_>,
mcos: Option<&Mcos<'_>>,
in_heap: bool,
depth: usize,
) -> Result<MatValue, MatError> {
let members = ds.dereference().map_err(MatError::Hdf5)?;
let mut elems = Vec::with_capacity(members.len());
for member in members {
elems.push(read_object(&member, mcos, in_heap, depth + 1)?);
}
Ok(MatValue::Cell(elems))
}
fn read_object(
obj: &Object<'_>,
mcos: Option<&Mcos<'_>>,
in_heap: bool,
depth: usize,
) -> Result<MatValue, MatError> {
match obj {
Object::Dataset(d) => read_dataset(d, mcos, in_heap, depth),
Object::Group(g) => read_group_as_value(g, mcos, in_heap, depth),
}
}
fn decode_opaque(
ds: &Dataset<'_>,
attrs: &HashMap<String, AttrValue>,
decode: i64,
mcos: Option<&Mcos<'_>>,
depth: usize,
) -> Result<MatValue, MatError> {
let class = mcos_reader::raw_matlab_class(attrs).ok_or_else(|| {
MatError::Custom("opaque object (MATLAB_object_decode set) has no MATLAB_class".into())
})?;
if !mcos_reader::is_mcos_decode(decode) {
return Err(MatError::UnsupportedMatlabClass(class));
}
let mcos = mcos.ok_or_else(|| {
MatError::Custom(
"file references an MCOS opaque object but has no #subsystem# store".into(),
)
})?;
if mcos_reader::is_string_class(&class, decode) {
return mcos.decode_string(ds);
}
let meta = ds.read_u32().map_err(MatError::Hdf5)?;
let parsed = mcos_reader::parse_opaque_metadata(&meta)?;
if parsed.object_ids.is_empty() {
let class_name = mcos.class_name(parsed.class_id).unwrap_or(class);
return Ok(MatValue::Opaque {
class_name,
fields: Vec::new(),
});
}
decode_object_ids(mcos, &parsed.object_ids, depth)
}
fn decode_object_ids(
mcos: &Mcos<'_>,
object_ids: &[u32],
depth: usize,
) -> Result<MatValue, MatError> {
let mut values = Vec::with_capacity(object_ids.len());
for &object_id in object_ids {
values.push(decode_opaque_object(mcos, object_id, depth)?);
}
if values.len() == 1 {
Ok(values.pop().expect("len checked"))
} else {
Ok(MatValue::Cell(values))
}
}
fn decode_opaque_object(
mcos: &Mcos<'_>,
object_id: u32,
depth: usize,
) -> Result<MatValue, MatError> {
if depth > MAX_NESTING_DEPTH {
return Err(MatError::Format(FormatError::NestingDepthExceeded));
}
let class_name = mcos.object_class_name(object_id)?;
let mut fields = Vec::new();
for prop in mcos.properties(object_id)? {
let value = match prop.value {
PropValue::Heap(v) => read_object(mcos.heap_object(v)?, Some(mcos), true, depth + 1)?,
PropValue::Inline(n) => MatValue::Scalar(mcos_reader::inline_to_scalar(n)),
PropValue::Name(s) => MatValue::String(s),
};
fields.push((prop.name, value));
}
mcos_reader::decode_object_fields(class_name, fields)
}
fn matlab_class_from_attrs(
attrs: &HashMap<String, AttrValue>,
) -> Result<Option<MatClass>, MatError> {
let raw = match attrs.get("MATLAB_class") {
Some(AttrValue::AsciiString(s)) | Some(AttrValue::String(s)) => Some(s.clone()),
Some(AttrValue::StringArray(v)) if v.len() == 1 => Some(v[0].clone()),
None => None,
other => {
return Err(MatError::Custom(format!(
"MATLAB_class attribute has unexpected type: {other:?}"
)));
}
};
match raw {
Some(s) => Ok(Some(MatClass::parse(&s)?)),
None => Ok(None),
}
}
fn is_empty_attr(attrs: &HashMap<String, AttrValue>) -> bool {
match attrs.get("MATLAB_empty") {
Some(AttrValue::U32(v)) => *v != 0,
Some(AttrValue::U64(v)) => *v != 0,
Some(AttrValue::I64(v)) => *v != 0,
Some(AttrValue::I32(v)) => *v != 0,
_ => false,
}
}
fn empty_complex_value(class: MatClass, shape: &[u64]) -> Result<MatValue, MatError> {
let (rows, cols, _total) = shape_decomposition(shape)?;
Ok(match class {
MatClass::Double => MatValue::ComplexMatrix64 {
rows,
cols,
pairs: Vec::new(),
},
MatClass::Single => MatValue::ComplexMatrix32 {
rows,
cols,
pairs: Vec::new(),
},
_ => unreachable!("empty_complex_value called with non-float class"),
})
}
fn empty_value_for_class(class: MatClass) -> MatValue {
use crate::mat::value::ScalarTag;
match class {
MatClass::Char => MatValue::String(String::new()),
MatClass::Logical => MatValue::Vec1D(NumVec::empty_with_tag(ScalarTag::Bool)),
MatClass::Double => MatValue::Vec1D(NumVec::empty_with_tag(ScalarTag::F64)),
MatClass::Single => MatValue::Vec1D(NumVec::empty_with_tag(ScalarTag::F32)),
MatClass::Int8 => MatValue::Vec1D(NumVec::empty_with_tag(ScalarTag::I8)),
MatClass::Int16 => MatValue::Vec1D(NumVec::empty_with_tag(ScalarTag::I16)),
MatClass::Int32 => MatValue::Vec1D(NumVec::empty_with_tag(ScalarTag::I32)),
MatClass::Int64 => MatValue::Vec1D(NumVec::empty_with_tag(ScalarTag::I64)),
MatClass::UInt8 => MatValue::Vec1D(NumVec::empty_with_tag(ScalarTag::U8)),
MatClass::UInt16 => MatValue::Vec1D(NumVec::empty_with_tag(ScalarTag::U16)),
MatClass::UInt32 => MatValue::Vec1D(NumVec::empty_with_tag(ScalarTag::U32)),
MatClass::UInt64 => MatValue::Vec1D(NumVec::empty_with_tag(ScalarTag::U64)),
MatClass::Struct => MatValue::Struct(Vec::new()),
MatClass::Cell => MatValue::Cell(Vec::new()),
}
}
fn class_from_dtype(dtype: &DType) -> MatClass {
match dtype {
DType::F64 => MatClass::Double,
DType::F32 => MatClass::Single,
DType::I8 => MatClass::Int8,
DType::I16 => MatClass::Int16,
DType::I32 => MatClass::Int32,
DType::I64 => MatClass::Int64,
DType::U8 => MatClass::UInt8,
DType::U16 => MatClass::UInt16,
DType::U32 => MatClass::UInt32,
DType::U64 => MatClass::UInt64,
DType::String => MatClass::Char,
DType::VariableLengthString => MatClass::Char,
_ => MatClass::Double, }
}
fn is_complex_dtype(dtype: &DType) -> bool {
match dtype {
DType::Compound(fields) => {
fields.len() == 2
&& fields.iter().any(|(n, _)| n == "real")
&& fields.iter().any(|(n, _)| n == "imag")
}
_ => false,
}
}
fn read_numeric(ds: &Dataset<'_>, shape: &[u64], class: MatClass) -> Result<MatValue, MatError> {
let (_, _, total) = shape_decomposition(shape)?;
if total == 1 {
return Ok(MatValue::Scalar(read_scalar(ds, class)?));
}
let flat = read_all_elements(ds, class)?;
numeric_value_from_flat(flat, shape)
}
fn numeric_value_from_flat(flat: NumVec, shape: &[u64]) -> Result<MatValue, MatError> {
let (rows, cols, total) = shape_decomposition(shape)?;
if total == 1 {
return flat
.get(0)
.map(MatValue::Scalar)
.ok_or_else(|| MatError::Custom("single-element dataset had no element".into()));
}
if shape.len() <= 1 {
return Ok(MatValue::Vec1D(flat));
}
let matrix = if rows == 1 || cols == 1 {
flat
} else {
transpose_col_major_to_row_major(flat, rows, cols)?
};
Ok(MatValue::Matrix {
rows,
cols,
vec: matrix,
})
}
fn shape_decomposition(shape: &[u64]) -> Result<(usize, usize, usize), MatError> {
Ok(match shape.len() {
0 => (1, 1, 1),
1 => {
let n = shape[0].to_usize()?;
(1, n, n)
}
2 => {
let cols_hdf5 = shape[0].to_usize()?;
let rows_hdf5 = shape[1].to_usize()?;
let total = cols_hdf5 * rows_hdf5;
(rows_hdf5, cols_hdf5, total)
}
_ => {
let mut total: usize = 1;
for &d in shape {
total *= d.to_usize()?;
}
(1, total, total)
}
})
}
fn read_all_elements(ds: &Dataset<'_>, class: MatClass) -> Result<NumVec, MatError> {
Ok(match class {
MatClass::Double => NumVec::F64(ds.read_f64().map_err(MatError::Hdf5)?),
MatClass::Single => NumVec::F32(ds.read_f32().map_err(MatError::Hdf5)?),
MatClass::Int8 => NumVec::I8(ds.read_i8().map_err(MatError::Hdf5)?),
MatClass::Int16 => NumVec::I16(ds.read_i16().map_err(MatError::Hdf5)?),
MatClass::Int32 => NumVec::I32(ds.read_i32().map_err(MatError::Hdf5)?),
MatClass::Int64 => NumVec::I64(ds.read_i64().map_err(MatError::Hdf5)?),
MatClass::UInt8 => NumVec::U8(ds.read_u8().map_err(MatError::Hdf5)?),
MatClass::UInt16 => NumVec::U16(ds.read_u16().map_err(MatError::Hdf5)?),
MatClass::UInt32 => NumVec::U32(ds.read_u32().map_err(MatError::Hdf5)?),
MatClass::UInt64 => NumVec::U64(ds.read_u64().map_err(MatError::Hdf5)?),
MatClass::Logical => {
let bytes = ds.read_u8().map_err(MatError::Hdf5)?;
NumVec::Bool(bytes.into_iter().map(|b| b != 0).collect())
}
_ => return Err(MatError::Custom(format!("read_numeric: class {class:?}"))),
})
}
fn read_scalar(ds: &Dataset<'_>, class: MatClass) -> Result<ScalarNum, MatError> {
Ok(match class {
MatClass::Double => ScalarNum::F64(ds.read_f64().map_err(MatError::Hdf5)?[0]),
MatClass::Single => ScalarNum::F32(ds.read_f32().map_err(MatError::Hdf5)?[0]),
MatClass::Int8 => ScalarNum::I8(ds.read_i8().map_err(MatError::Hdf5)?[0]),
MatClass::Int16 => ScalarNum::I16(ds.read_i16().map_err(MatError::Hdf5)?[0]),
MatClass::Int32 => ScalarNum::I32(ds.read_i32().map_err(MatError::Hdf5)?[0]),
MatClass::Int64 => ScalarNum::I64(ds.read_i64().map_err(MatError::Hdf5)?[0]),
MatClass::UInt8 => ScalarNum::U8(ds.read_u8().map_err(MatError::Hdf5)?[0]),
MatClass::UInt16 => ScalarNum::U16(ds.read_u16().map_err(MatError::Hdf5)?[0]),
MatClass::UInt32 => ScalarNum::U32(ds.read_u32().map_err(MatError::Hdf5)?[0]),
MatClass::UInt64 => ScalarNum::U64(ds.read_u64().map_err(MatError::Hdf5)?[0]),
MatClass::Logical => ScalarNum::Bool(ds.read_u8().map_err(MatError::Hdf5)?[0] != 0),
_ => return Err(MatError::Custom(format!("read_scalar: class {class:?}"))),
})
}
fn transpose_col_major_to_row_major(
col_major: NumVec,
rows: usize,
cols: usize,
) -> Result<NumVec, MatError> {
debug_assert_eq!(col_major.len(), rows * cols);
fn transpose<T: Copy>(v: Vec<T>, rows: usize, cols: usize) -> Vec<T> {
let mut out = Vec::with_capacity(rows * cols);
for r in 0..rows {
for c in 0..cols {
out.push(v[c * rows + r]);
}
}
out
}
Ok(match col_major {
NumVec::F64(v) => NumVec::F64(transpose(v, rows, cols)),
NumVec::F32(v) => NumVec::F32(transpose(v, rows, cols)),
NumVec::I8(v) => NumVec::I8(transpose(v, rows, cols)),
NumVec::I16(v) => NumVec::I16(transpose(v, rows, cols)),
NumVec::I32(v) => NumVec::I32(transpose(v, rows, cols)),
NumVec::I64(v) => NumVec::I64(transpose(v, rows, cols)),
NumVec::U8(v) => NumVec::U8(transpose(v, rows, cols)),
NumVec::U16(v) => NumVec::U16(transpose(v, rows, cols)),
NumVec::U32(v) => NumVec::U32(transpose(v, rows, cols)),
NumVec::U64(v) => NumVec::U64(transpose(v, rows, cols)),
NumVec::Bool(v) => NumVec::Bool(transpose(v, rows, cols)),
})
}
fn read_complex(ds: &Dataset<'_>, shape: &[u64], class: MatClass) -> Result<MatValue, MatError> {
let (rows, cols, total) = shape_decomposition(shape)?;
let bytes = ds.read_u8().map_err(MatError::Hdf5)?;
match class {
MatClass::Double => {
let pairs = parse_complex64_pairs(&bytes, total)?;
if total == 1 {
let (re, im) = pairs[0];
return Ok(MatValue::ComplexScalar64 { re, im });
}
let row_major = if rows == 1 || cols == 1 {
pairs
} else {
transpose_pairs_col_to_row(pairs, rows, cols)
};
Ok(MatValue::ComplexMatrix64 {
rows,
cols,
pairs: row_major,
})
}
MatClass::Single => {
let pairs = parse_complex32_pairs(&bytes, total)?;
if total == 1 {
let (re, im) = pairs[0];
return Ok(MatValue::ComplexScalar32 { re, im });
}
let row_major = if rows == 1 || cols == 1 {
pairs
} else {
transpose_pairs_col_to_row(pairs, rows, cols)
};
Ok(MatValue::ComplexMatrix32 {
rows,
cols,
pairs: row_major,
})
}
_ => Err(MatError::Custom(
"complex compound on non-float class".into(),
)),
}
}
fn parse_complex64_pairs(bytes: &[u8], count: usize) -> Result<Vec<(f64, f64)>, MatError> {
if bytes.len() < count * 16 {
return Err(MatError::Custom(format!(
"complex64 raw bytes too short: need {}, have {}",
count * 16,
bytes.len()
)));
}
let mut out = Vec::with_capacity(count);
for i in 0..count {
let off = i * 16;
let re = f64::from_le_bytes(bytes[off..off + 8].try_into().unwrap());
let im = f64::from_le_bytes(bytes[off + 8..off + 16].try_into().unwrap());
out.push((re, im));
}
Ok(out)
}
fn parse_complex32_pairs(bytes: &[u8], count: usize) -> Result<Vec<(f32, f32)>, MatError> {
if bytes.len() < count * 8 {
return Err(MatError::Custom(format!(
"complex32 raw bytes too short: need {}, have {}",
count * 8,
bytes.len()
)));
}
let mut out = Vec::with_capacity(count);
for i in 0..count {
let off = i * 8;
let re = f32::from_le_bytes(bytes[off..off + 4].try_into().unwrap());
let im = f32::from_le_bytes(bytes[off + 4..off + 8].try_into().unwrap());
out.push((re, im));
}
Ok(out)
}
fn transpose_pairs_col_to_row<T: Copy>(
col_major: Vec<(T, T)>,
rows: usize,
cols: usize,
) -> Vec<(T, T)> {
let mut out = Vec::with_capacity(rows * cols);
for r in 0..rows {
for c in 0..cols {
out.push(col_major[c * rows + r]);
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn enum_member_names_maps_indices_to_pool() {
let pool = vec!["enum1".to_owned(), "enum2".to_owned(), "enum3".to_owned()];
let names = enum_member_names(&pool, &[0, 2, 1, 0]).unwrap();
assert_eq!(names, ["enum1", "enum3", "enum2", "enum1"]);
}
#[test]
fn enum_member_names_rejects_out_of_range_index() {
let pool = vec!["enum1".to_owned()];
let err = enum_member_names(&pool, &[0, 5]).unwrap_err();
assert!(
matches!(&err, MatError::Custom(m) if m.contains("out of range")),
"unexpected error: {err:?}"
);
}
#[test]
fn enum_indices_to_usize_accepts_uint32_shapes() {
assert_eq!(
enum_indices_to_usize(&MatValue::Scalar(ScalarNum::U32(4))).unwrap(),
[4]
);
assert_eq!(
enum_indices_to_usize(&MatValue::Vec1D(NumVec::U32(vec![1, 2, 3]))).unwrap(),
[1, 2, 3]
);
assert_eq!(
enum_indices_to_usize(&MatValue::Matrix {
rows: 2,
cols: 2,
vec: NumVec::U32(vec![0, 1, 2, 3]),
})
.unwrap(),
[0, 1, 2, 3]
);
}
#[test]
fn enum_indices_to_usize_rejects_non_uint32() {
let err = enum_indices_to_usize(&MatValue::Vec1D(NumVec::F64(vec![1.0]))).unwrap_err();
assert!(matches!(err, MatError::Custom(_)), "unexpected: {err:?}");
}
}