use nodedb_query::msgpack_scan;
pub(crate) fn explode_row_array(bytes: &[u8]) -> crate::Result<Vec<&[u8]>> {
if bytes.is_empty() {
return Ok(Vec::new());
}
let Some((count, mut pos)) = msgpack_scan::array_header(bytes, 0) else {
return Err(crate::Error::Storage {
engine: "shuffle-stage".into(),
detail: "malformed shuffle chunk: expected a msgpack array header".into(),
});
};
let mut rows = Vec::with_capacity(count);
for i in 0..count {
let start = pos;
let Some(end) = msgpack_scan::skip_value(bytes, pos) else {
return Err(crate::Error::Storage {
engine: "shuffle-stage".into(),
detail: format!("malformed shuffle chunk: truncated row {i} of {count}"),
});
};
rows.push(&bytes[start..end]);
pos = end;
}
Ok(rows)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_payload_yields_no_rows() {
assert!(explode_row_array(&[]).expect("empty ok").is_empty());
}
#[test]
fn explodes_each_array_element() {
let bytes = vec![0x93, 0x01, 0x02, 0x03];
let rows = explode_row_array(&bytes).expect("explode");
assert_eq!(rows, vec![&[0x01u8][..], &[0x02u8][..], &[0x03u8][..]]);
}
#[test]
fn truncated_array_is_hard_error() {
let res = explode_row_array(&[0x91]);
assert!(
matches!(res, Err(crate::Error::Storage { .. })),
"a malformed chunk must surface a Storage error, never a silent drop"
);
}
#[test]
fn non_array_header_is_hard_error() {
let res = explode_row_array(&[0xc0]);
assert!(matches!(res, Err(crate::Error::Storage { .. })));
}
}