use std::path::{Path, PathBuf};
pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("{operation} failed for {path}: {source}", path = path.display())]
Io {
operation: &'static str,
path: PathBuf,
#[source]
source: std::io::Error,
},
#[cfg(feature = "hdf5")]
#[error("HDF5 error while {operation}: {source}")]
Hdf5 {
operation: &'static str,
#[source]
source: hdf5::Error,
},
#[error("invalid file name '{path}': {reason}", path = path.display())]
InvalidFileName {
path: PathBuf,
reason: String,
},
#[error("invalid configuration: {reason}")]
InvalidConfiguration {
reason: String,
},
#[error("invalid mesh: {reason}")]
InvalidMesh {
reason: String,
},
#[error("invalid time step '{time}': {reason}")]
InvalidTimeStep {
time: String,
reason: String,
},
#[error("invalid data: {reason}")]
InvalidData {
reason: String,
},
#[error("integer value {value} is out of range: {reason}")]
IntegerOutOfRange {
value: i128,
reason: String,
},
#[error("internal invariant violated: {0}")]
Internal(&'static str),
#[error("invalid XDMF document: {reason}")]
InvalidDocument {
reason: String,
},
#[error("unsupported: {reason}")]
Unsupported {
reason: String,
},
#[error("number type mismatch: {reason}")]
NumberTypeMismatch {
reason: String,
},
}
pub(crate) fn io_ctx<'a>(
operation: &'static str,
path: &'a Path,
) -> impl FnOnce(std::io::Error) -> Error + 'a {
move |source| Error::Io {
operation,
path: path.to_path_buf(),
source,
}
}
impl From<Error> for std::io::Error {
fn from(err: Error) -> Self {
let kind = match &err {
Error::Io { source, .. } => source.kind(),
_ => std::io::ErrorKind::InvalidInput,
};
Self::new(kind, err)
}
}
#[cfg(test)]
mod error_messages {
use super::*;
#[test]
fn io() {
let err = Error::Io {
operation: "creating data file",
path: PathBuf::from("/tmp/out/data.txt"),
source: std::io::Error::other("No such file or directory"),
};
assert_eq!(
err.to_string(),
"creating data file failed for /tmp/out/data.txt: No such file or directory"
);
}
#[test]
fn invalid_file_name() {
assert_eq!(
Error::InvalidFileName {
path: PathBuf::from("a:b"),
reason: "file name component must not contain any of the following characters"
.to_string(),
}
.to_string(),
"invalid file name 'a:b': file name component must not contain any of the following \
characters"
);
}
#[test]
fn invalid_configuration() {
assert_eq!(
Error::InvalidConfiguration {
reason: "deflate level 10 is out of range, must be between 0 and 9".to_string(),
}
.to_string(),
"invalid configuration: deflate level 10 is out of range, must be between 0 and 9"
);
assert_eq!(
Error::InvalidConfiguration {
reason: "using Hdf5SingleFile { deflate_level: None } DataStorage requires the 'hdf5' feature".to_string(),
}
.to_string(),
"invalid configuration: using Hdf5SingleFile { deflate_level: None } DataStorage requires the 'hdf5' feature"
);
}
#[test]
fn invalid_mesh() {
assert_eq!(
Error::InvalidMesh {
reason: "at least one point is required".to_string(),
}
.to_string(),
"invalid mesh: at least one point is required"
);
}
#[test]
fn invalid_time_step() {
assert_eq!(
Error::InvalidTimeStep {
time: "not_a_float".to_string(),
reason: "must be a valid float".to_string(),
}
.to_string(),
"invalid time step 'not_a_float': must be a valid float"
);
assert_eq!(
Error::InvalidTimeStep {
time: "0.10".to_string(),
reason: "already written (as '0.1')".to_string(),
}
.to_string(),
"invalid time step '0.10': already written (as '0.1')"
);
}
#[test]
fn invalid_data() {
assert_eq!(
Error::InvalidData {
reason: "size of point_data 'temperature' must be 10, but is 9".to_string(),
}
.to_string(),
"invalid data: size of point_data 'temperature' must be 10, but is 9"
);
}
#[test]
fn integer_out_of_range() {
assert_eq!(
Error::IntegerOutOfRange {
value: -2_147_483_649,
reason: "Binary storage narrows 64-bit integers to 32 bits".to_string(),
}
.to_string(),
"integer value -2147483649 is out of range: Binary storage narrows 64-bit integers \
to 32 bits"
);
assert_eq!(
Error::IntegerOutOfRange {
value: 4_294_967_296,
reason: "u64 data must fit in 32 bits".to_string(),
}
.to_string(),
"integer value 4294967296 is out of range: u64 data must fit in 32 bits"
);
}
#[test]
fn internal() {
assert_eq!(
Error::Internal("writing data was not initialized").to_string(),
"internal invariant violated: writing data was not initialized"
);
}
#[test]
fn invalid_document() {
assert_eq!(
Error::InvalidDocument {
reason: "DataItem has no Dimensions".to_string(),
}
.to_string(),
"invalid XDMF document: DataItem has no Dimensions"
);
}
#[test]
fn unsupported() {
assert_eq!(
Error::Unsupported {
reason: "ItemType \"Function\" is not supported".to_string(),
}
.to_string(),
"unsupported: ItemType \"Function\" is not supported"
);
}
#[test]
fn number_type_mismatch() {
assert_eq!(
Error::NumberTypeMismatch {
reason: "requested u32, but the file holds u64".to_string(),
}
.to_string(),
"number type mismatch: requested u32, but the file holds u64"
);
}
#[cfg(feature = "hdf5")]
#[test]
fn hdf5() {
let err = Error::Hdf5 {
operation: "creating group",
source: hdf5::Error::from("boom".to_string()),
};
assert_eq!(err.to_string(), "HDF5 error while creating group: boom");
}
#[test]
fn from_error_for_io_error_preserves_io_kind() {
let err = Error::Io {
operation: "creating data file",
path: PathBuf::from("/tmp/out/data.txt"),
source: std::io::Error::new(std::io::ErrorKind::PermissionDenied, "denied"),
};
let io_err: std::io::Error = err.into();
assert_eq!(io_err.kind(), std::io::ErrorKind::PermissionDenied);
std::assert_matches!(
io_err.get_ref().and_then(|e| e.downcast_ref::<Error>()),
Some(Error::Io { operation, .. }) if *operation == "creating data file"
);
}
#[test]
fn from_error_for_io_error_defaults_to_invalid_input() {
let io_err: std::io::Error = Error::InvalidMesh {
reason: "at least one point is required".to_string(),
}
.into();
assert_eq!(io_err.kind(), std::io::ErrorKind::InvalidInput);
}
}