use crate::{Error, Result, Values, xdmf_elements::data_item::Format};
const MAX_EXACT_ASCII_INT: u64 = 1 << 53;
pub(crate) fn validate(data: &Values<'_>, format: Format) -> Result<()> {
validate_uint_range(data)?;
match format {
Format::XML => validate_ascii_int_range(data),
Format::Binary => reject_64_bit_integers(data),
Format::HDF => Ok(()),
}
}
fn validate_uint_range(data: &Values<'_>) -> Result<()> {
let Values::U64(values) = data else {
return Ok(());
};
for &value in values.iter() {
if value > u64::from(u32::MAX) {
return Err(Error::IntegerOutOfRange {
value: i128::from(value),
reason: "u64 data must fit in 32 bits, since ParaView decodes UInt data into a \
32-bit array whatever precision is declared; no DataStorage avoids this, \
use i64 for integers beyond 32 bits"
.to_string(),
});
}
}
Ok(())
}
fn validate_ascii_int_range(data: &Values<'_>) -> Result<()> {
let Values::I64(values) = data else {
return Ok(());
};
for &value in values.iter() {
if value.unsigned_abs() > MAX_EXACT_ASCII_INT {
return Err(Error::IntegerOutOfRange {
value: i128::from(value),
reason: format!(
"the ascii storage methods are read back through a double, so an i64 beyond \
+/-{MAX_EXACT_ASCII_INT} is shown rounded; the Hdf5SingleFile and \
Hdf5MultipleFiles storages keep the full width"
),
});
}
}
Ok(())
}
fn reject_64_bit_integers(data: &Values<'_>) -> Result<()> {
let element_type = match data {
Values::I64(_) => "i64",
Values::U64(_) => "u64",
Values::F64(_) | Values::F32(_) | Values::I32(_) | Values::U32(_) => return Ok(()),
};
Err(Error::InvalidData {
reason: format!(
"the Binary storage cannot hold {element_type} data, since ParaView reads 64-bit \
integers in Format=\"Binary\" at the wrong stride and gets neither the values nor \
the mesh back; pass the data as i32/u32, or use another DataStorage"
),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn uint_range_is_capped_for_every_format() {
for format in [Format::XML, Format::HDF] {
std::assert_matches!(
validate(&vec![u64::from(u32::MAX) + 1].into(), format).unwrap_err(),
Error::IntegerOutOfRange { value, reason }
if value == i128::from(u32::MAX) + 1
&& reason.contains("no DataStorage avoids this"),
"{format:?} must reject a u64 above u32::MAX"
);
validate(&vec![u64::from(u32::MAX)].into(), format).unwrap();
}
std::assert_matches!(
validate(&vec![0_u64].into(), Format::Binary).unwrap_err(),
Error::InvalidData { reason } if reason.contains("cannot hold u64 data")
);
}
#[test]
fn ascii_int_range_boundary() {
let max = i64::try_from(MAX_EXACT_ASCII_INT).unwrap();
validate(&vec![max, -max, 0, 1].into(), Format::XML).unwrap();
for out_of_range in [max + 1, -max - 1, i64::MAX, i64::MIN] {
std::assert_matches!(
validate(&vec![0_i64, out_of_range].into(), Format::XML).unwrap_err(),
Error::IntegerOutOfRange { value, reason }
if value == i128::from(out_of_range)
&& reason.contains("read back through a double"),
"an i64 of {out_of_range} must be rejected for the ascii storages"
);
validate(&vec![0_i64, out_of_range].into(), Format::HDF).unwrap();
}
validate(&vec![u64::from(u32::MAX)].into(), Format::XML).unwrap();
validate(&vec![i32::MIN, i32::MAX].into(), Format::XML).unwrap();
validate(&vec![f64::MAX, f64::MIN].into(), Format::XML).unwrap();
}
#[test]
fn binary_rejects_64_bit_integers_whatever_they_hold() {
for (data, expected) in [
(Values::from(vec![0_i64, 1]), "cannot hold i64 data"),
(Values::from(vec![0_u64, 1]), "cannot hold u64 data"),
] {
std::assert_matches!(
validate(&data, Format::Binary).unwrap_err(),
Error::InvalidData { reason }
if reason.contains(expected) && reason.contains("use another DataStorage"),
"Binary must reject {data:?}"
);
validate(&data, Format::HDF).unwrap();
}
validate(&vec![i32::MIN, i32::MAX].into(), Format::Binary).unwrap();
validate(&vec![0_u32, u32::MAX].into(), Format::Binary).unwrap();
validate(&vec![1.5_f64].into(), Format::Binary).unwrap();
validate(&vec![1.5_f32].into(), Format::Binary).unwrap();
}
}