#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum Error {
#[error("IO error: {0}")]
#[cfg_attr(feature = "serde", serde(skip))]
Io(#[from] std::io::Error),
#[error("Invalid MRC header")]
InvalidHeader,
#[error("Unsupported mode")]
UnsupportedMode,
#[error(
"Bounds error at offset ({ox},{oy},{oz}) shape ({sx},{sy},{sz}) of volume ({vx},{vy},{vz})",
ox = .offset.map_or(0, |o| o[0]),
oy = .offset.map_or(0, |o| o[1]),
oz = .offset.map_or(0, |o| o[2]),
sx = .shape.map_or(0, |s| s[0]),
sy = .shape.map_or(0, |s| s[1]),
sz = .shape.map_or(0, |s| s[2]),
vx = .volume.map_or(0, |v| v[0]),
vy = .volume.map_or(0, |v| v[1]),
vz = .volume.map_or(0, |v| v[2]),
)]
BoundsError {
offset: Option<[usize; 3]>,
shape: Option<[usize; 3]>,
volume: Option<[usize; 3]>,
},
#[error("Type mismatch: expected {expected} bytes per voxel, got {actual} bytes")]
TypeMismatch {
expected: usize,
actual: usize,
},
#[error("Invalid block shape: expected {expected} elements, got {actual}")]
BlockShapeMismatch {
expected: usize,
actual: usize,
},
#[error("Mode mismatch: file stores {file_mode:?}, requested {requested_mode:?}{}",
match .offset {
Some(o) => format!(" at offset ({},{},{})", o[0], o[1], o[2]),
None => String::new(),
}
)]
ModeMismatch {
file_mode: crate::Mode,
requested_mode: crate::Mode,
offset: Option<[usize; 3]>,
},
#[error("Invalid header: {0}")]
InvalidHeaderDetailed(#[from] HeaderValidationError),
#[error(
"Stats mismatch: header claims dmin={claimed_dmin}, dmax={claimed_dmax}, dmean={claimed_dmean}, rms={claimed_rms} but actual data has dmin={actual_dmin}, dmax={actual_dmax}, dmean={actual_dmean}, rms={actual_rms}"
)]
StatsMismatch {
claimed_dmin: f32,
claimed_dmax: f32,
claimed_dmean: f32,
claimed_rms: f32,
actual_dmin: f32,
actual_dmax: f32,
actual_dmean: f32,
actual_rms: f32,
},
#[cfg(feature = "mmap")]
#[error("Memory mapping error")]
Mmap,
#[error("File size mismatch: expected {expected} bytes, got {actual} bytes")]
FileSizeMismatch {
expected: usize,
actual: usize,
},
#[error("Not a volume stack: ispg={ispg}, mz={mz} (expected ispg in 401-630 with mz > 0)")]
NotAVolumeStack {
ispg: i32,
mz: i32,
},
#[error("Value out of range: {value} exceeds maximum of {max}")]
#[cfg_attr(feature = "serde", serde(skip))]
ValueOutOfRange {
value: u64,
max: u64,
},
}
impl Error {
#[cold]
pub(crate) fn bounds_err() -> Self {
Self::BoundsError {
offset: None,
shape: None,
volume: None,
}
}
}
impl From<Error> for std::io::Error {
fn from(err: Error) -> Self {
match &err {
Error::Io(e) => std::io::Error::new(e.kind(), err.to_string()),
_ => std::io::Error::other(err.to_string()),
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(thiserror::Error, Debug, Clone, PartialEq)]
pub enum HeaderValidationError {
#[error("Invalid dimensions: nx={nx}, ny={ny}, nz={nz} (must all be positive)")]
InvalidDimensions {
nx: i32,
ny: i32,
nz: i32,
},
#[error("Unsupported mode: {0}")]
UnsupportedMode(i32),
#[error("Invalid MAP field: expected 'MAP ', got {0:?}")]
InvalidMap([u8; 4]),
#[error("Invalid ISPG: {0} (expected 0, 1-230, or 400-630)")]
InvalidIspg(i32),
#[error(
"Invalid axis mapping: mapc={mapc}, mapr={mapr}, maps={maps} (must be a permutation of 1,2,3)"
)]
InvalidAxisMapping {
mapc: i32,
mapr: i32,
maps: i32,
},
#[error("Invalid nsymbt: {0} (must be non-negative)")]
InvalidNsymbt(i32),
#[error("Invalid nlabl: {0} (must be between 0 and 10)")]
InvalidNlabl(i32),
#[error("Invalid nversion: {0} (expected 0, 20140, or 20141)")]
InvalidNversion(i32),
#[error(
"Invalid volume stack: nz={nz} is not divisible by mz={mz} (required when ispg={ispg} indicates a volume stack)"
)]
InvalidVolumeStack {
nz: i32,
mz: i32,
ispg: i32,
},
#[error("Invalid sampling: mx={mx}, my={my}, mz={mz} (must all be positive)")]
InvalidSampling {
mx: i32,
my: i32,
mz: i32,
},
#[error("Label count mismatch: nlabl={nlabl} but {actual} non-empty labels found")]
LabelCountMismatch {
nlabl: i32,
actual: i32,
},
#[error("Empty label at index {index} before all filled labels")]
EmptyLabelBeforeFilled {
index: i32,
},
}