use std::{collections::TryReserveError, fmt::Display};
use zip::result::ZipError;
use crate::{validate, SubblockMode};
#[derive(Debug, thiserror::Error)]
pub enum Limit {
#[error("uncompressed JSON too big")]
JsonBytes,
#[error("uncompressed array or image too big")]
ArrayBytes,
#[error("image width or height is too large")]
ImageDim,
}
fn format_corners<T: Display>(corners: &[T; 6]) -> String {
format!(
"[{}, {}, {}] to [{}, {}, {}]",
corners[0], corners[1], corners[2], corners[3], corners[4], corners[5],
)
}
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum InvalidData {
#[error("Error: array length {found} does not the declared length {expected}")]
LengthMismatch { found: u64, expected: u64 },
#[error("size value {value} is zero or less")]
SizeZeroOrLess { value: f64 },
#[error("discrete colormap boundary decreases")]
BoundaryDecreases,
#[error("index value {value} exceeds the maximum index {maximum}")]
IndexOutOfRange { value: u64, maximum: u64 },
#[error("block index {value:?} exceeds the maximum index {maximum:?}")]
BlockIndexOutOfRange { value: [u32; 3], maximum: [u32; 3] },
#[error("sub-block {} has zero or negative size", format_corners(corners))]
RegularSubblockZeroSize { corners: [u32; 6] },
#[error(
"sub-block {} exceeds the maximum {maximum:?}",
format_corners(corners)
)]
RegularSubblockOutsideParent {
corners: [u32; 6],
maximum: [u32; 3],
},
#[error("sub-block {} is invalid for {mode:?} mode", format_corners(corners))]
RegularSubblockNotInMode {
corners: [u32; 6],
mode: SubblockMode,
},
#[error("sub-block {} has zero or negative size", format_corners(corners))]
FreeformSubblockZeroSize { corners: [f64; 6] },
#[error(
"sub-block {} is outside the valid range of 0.0 to 1.0",
format_corners(corners)
)]
FreeformSubblockOutsideParent { corners: [f64; 6] },
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("Memory allocation failed")]
OutOfMemory,
#[error("File IO error: {0}")]
IoError(std::io::Error),
#[error("Error: the zip comment does not identify this as an OMF file: '{0}'")]
NotOmf(String),
#[error("Version error: the file uses OMF v{0}.{1} but this version library can only read 0.9 and 2.0")]
NewerVersion(u32, u32),
#[error("Version error: the file uses pre-release OMF v{0}.{1}-{2} and can't be loaded")]
PreReleaseVersion(u32, u32, String),
#[error("JSON deserialization error: {0}")]
DeserializationFailed(#[from] serde_json::Error),
#[error("JSON serialization error: {0}")]
SerializationFailed(serde_json::Error),
#[error("Validation failed")]
ValidationFailed(#[from] validate::Problems),
#[error("Error: can't cast from {0} to {1} without losing data")]
UnsafeCast(&'static str, &'static str),
#[error("Error: image is not in PNG or JPEG encoding")]
NotImageData,
#[error("Error: image is not in Parquet encoding")]
NotParquetData,
#[error("Error: safety limit exceeded")]
LimitExceeded(Limit),
#[error("Data error: {0}")]
InvalidData(Box<InvalidData>),
#[error("Error: missing archive member '{0}'")]
ZipMemberMissing(String),
#[error("Zip error: {0}")]
ZipError(String),
#[cfg(feature = "parquet")]
#[error("{}", mismatch_string(.0, .1))]
ParquetSchemaMismatch(
std::sync::Arc<parquet::schema::types::Type>,
std::sync::Arc<Vec<parquet::schema::types::Type>>,
),
#[cfg(feature = "parquet")]
#[error("{0}")]
ParquetError(Box<parquet::errors::ParquetError>),
#[cfg(feature = "image")]
#[error("Image processing error: {0}")]
ImageError(Box<image::ImageError>),
#[cfg(feature = "omf1")]
#[error("OMF1 conversion failed: {0}")]
Omf1Error(#[from] crate::omf1::Omf1Error),
}
impl From<InvalidData> for Error {
fn from(value: InvalidData) -> Self {
Self::InvalidData(value.into())
}
}
impl From<std::io::Error> for Error {
fn from(error: std::io::Error) -> Self {
match error.kind() {
std::io::ErrorKind::OutOfMemory => Error::OutOfMemory,
std::io::ErrorKind::Other if error.get_ref().is_some_and(|r| r.is::<Error>()) => {
*error.into_inner().unwrap().downcast().unwrap()
}
_ => Error::IoError(error),
}
}
}
impl From<TryReserveError> for Error {
fn from(_: TryReserveError) -> Self {
Error::OutOfMemory
}
}
impl From<ZipError> for Error {
fn from(value: ZipError) -> Self {
match value {
ZipError::Io(e) => Self::IoError(e),
other => Self::ZipError(other.to_string()),
}
}
}
#[cfg(feature = "image")]
impl From<image::ImageError> for Error {
fn from(value: image::ImageError) -> Self {
use image::error::LimitErrorKind;
match &value {
image::ImageError::Limits(err @ image::error::LimitError { .. }) => match err.kind() {
LimitErrorKind::DimensionError => Error::LimitExceeded(Limit::ImageDim),
LimitErrorKind::InsufficientMemory => Error::LimitExceeded(Limit::ArrayBytes),
_ => Error::ImageError(value.into()),
},
_ => Error::ImageError(value.into()),
}
}
}
#[cfg(feature = "parquet")]
impl From<parquet::errors::ParquetError> for Error {
fn from(value: parquet::errors::ParquetError) -> Self {
Self::ParquetError(value.into())
}
}
#[cfg(feature = "parquet")]
fn mismatch_string(
found: &parquet::schema::types::Type,
expected: &[parquet::schema::types::Type],
) -> String {
use parquet::schema::{printer::print_schema, types::Type};
fn schema_string(ty: &Type) -> String {
let mut buf = Vec::new();
print_schema(&mut buf, ty);
String::from_utf8_lossy(&buf).trim_end().to_owned()
}
let mut out = format!(
"Parquet schema mismatch, found:\n{found}\n\n{expected_label}:\n",
found = schema_string(found),
expected_label = if expected.len() == 1 {
"Expected"
} else {
"Expected one of"
}
);
for ty in expected {
out.push_str(&schema_string(ty));
out.push('\n');
}
out
}