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
//! Reading MATLAB struct *arrays* (issue #127) into `Vec<T>` / `Vec<Vec<T>>`.
//!
//! On disk a struct array is a `MATLAB_class="struct"` group whose every field
//! is a dataset of per-element object references, unlike a scalar struct whose
//! fields are direct value datasets. The fixture is synthetic, built to MATLAB's
//! documented v7.3 layout by `tests/fixtures/mat_synth/gen_struct_array.py`
//! (see `tests/fixtures/mat_synth/NOTICE.md`).
#![cfg(feature = "serde")]
use hdf5_pure::mat;
use serde::Deserialize;
#[derive(Deserialize, Debug, PartialEq)]
#[allow(non_snake_case)]
struct Data {
fieldA: u64,
fieldB: String,
fieldC: Vec<i64>,
}
#[derive(Deserialize, Debug, PartialEq)]
struct GridElem {
id: f64,
tag: String,
}
#[derive(Deserialize, Debug, PartialEq)]
struct Inner {
p: f64,
}
#[derive(Deserialize, Debug, PartialEq)]
#[allow(non_snake_case)]
struct Nested {
fieldA: f64,
inner: Inner,
}
fn read<T: serde::de::DeserializeOwned>() -> T {
let bytes = std::fs::read("tests/fixtures/mat_synth/struct_array_v73.mat")
.expect("read struct_array_v73.mat fixture");
mat::from_bytes(&bytes).expect("decode fixture")
}
fn expected_data(n: u64) -> Data {
Data {
fieldA: n,
fieldB: ((b'a' + (n as u8) - 1) as char).to_string(),
fieldC: vec![-6, -5, -4, -3, -2, -1],
}
}
/// The issue's exact case: a `1×6` struct array deserializes into `Vec<Data>`.
#[test]
fn row_struct_array_reads_as_vec() {
#[derive(Deserialize)]
struct File {
row: Vec<Data>,
}
let row = read::<File>().row;
let expected: Vec<Data> = (1..=6).map(expected_data).collect();
assert_eq!(row, expected);
}
/// A `6×1` column struct array flattens into the same `Vec<Data>` as the row
/// orientation, matching how the crate flattens `1×N` / `N×1` numeric arrays.
#[test]
fn col_struct_array_reads_as_vec() {
#[derive(Deserialize)]
struct File {
col: Vec<Data>,
}
let col = read::<File>().col;
let expected: Vec<Data> = (1..=6).map(expected_data).collect();
assert_eq!(col, expected);
}
/// A true `2×3` struct array yields a row-major `Vec<Vec<T>>`, mirroring the
/// numeric `Matrix` row split. `id = row*10 + col` pins the ordering.
#[test]
fn grid_struct_array_reads_as_rows() {
#[derive(Deserialize)]
struct File {
grid: Vec<Vec<GridElem>>,
}
let grid = read::<File>().grid;
assert_eq!(grid.len(), 2);
assert_eq!(
grid[0],
vec![
GridElem {
id: 0.0,
tag: "a".into()
},
GridElem {
id: 1.0,
tag: "b".into()
},
GridElem {
id: 2.0,
tag: "c".into()
},
]
);
assert_eq!(
grid[1],
vec![
GridElem {
id: 10.0,
tag: "d".into()
},
GridElem {
id: 11.0,
tag: "e".into()
},
GridElem {
id: 12.0,
tag: "f".into()
},
]
);
}
/// Each struct-array element may itself contain a nested *scalar* struct,
/// resolved through the element's reference like any other field value.
#[test]
fn nested_scalar_struct_in_array_decodes() {
#[derive(Deserialize)]
struct File {
nested: Vec<Nested>,
}
let nested = read::<File>().nested;
assert_eq!(
nested,
vec![
Nested {
fieldA: 1.0,
inner: Inner { p: 0.0 }
},
Nested {
fieldA: 2.0,
inner: Inner { p: 100.0 }
},
]
);
}
/// Regression guard: a scalar (1×1) struct must still read as a single struct,
/// not be misdetected as a struct array.
#[test]
fn scalar_struct_is_not_treated_as_array() {
#[derive(Deserialize)]
struct File {
scalar: Data,
}
assert_eq!(read::<File>().scalar, expected_data(1));
}
/// Deserializing a multi-element struct array into a single struct fails with a
/// clear error rather than silently taking the first element.
#[test]
fn struct_array_into_single_struct_errors() {
#[derive(Deserialize, Debug)]
struct File {
#[allow(dead_code)]
row: Data,
}
let bytes = std::fs::read("tests/fixtures/mat_synth/struct_array_v73.mat").unwrap();
let err = mat::from_bytes::<File>(&bytes).unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("struct array"),
"error should mention the struct array, got: {msg}"
);
}