omf/
error.rs

1//! Error codes and details.
2
3use std::{collections::TryReserveError, fmt::Display};
4
5use zip::result::ZipError;
6
7use crate::{SubblockMode, validate};
8
9/// The types of limit that may be exceeded.
10///
11/// Variants name the field in [`Limits`](crate::file::Limits) that was exceeded.
12#[derive(Debug, thiserror::Error)]
13pub enum Limit {
14    #[error("uncompressed JSON too big")]
15    JsonBytes,
16    #[error("uncompressed array or image too big")]
17    ArrayBytes,
18    #[error("image width or height is too large")]
19    ImageDim,
20}
21
22fn format_corners<T: Display>(corners: &[T; 6]) -> String {
23    format!(
24        "[{}, {}, {}] to [{}, {}, {}]",
25        corners[0], corners[1], corners[2], corners[3], corners[4], corners[5],
26    )
27}
28
29/// Ways that data, either in a file or being written to one, can be invalid.
30#[derive(Debug, Clone, PartialEq, thiserror::Error)]
31pub enum InvalidData {
32    /// Data length does not match the Array.
33    #[error("Error: array length {found} does not match the declared length {expected}")]
34    LengthMismatch { found: u64, expected: u64 },
35    /// A size is <= 0.
36    #[error("size value {value} is zero or less")]
37    SizeZeroOrLess { value: f64 },
38    /// A discrete colormap boundary is less than the previous boundary.
39    #[error("discrete colormap boundary decreases")]
40    BoundaryDecreases,
41    /// A segment, triangle, or category index is out of range.
42    #[error("index value {value} exceeds the maximum index {maximum}")]
43    IndexOutOfRange { value: u64, maximum: u64 },
44    /// A block index is out of range.
45    #[error("block index {value:?} exceeds the maximum index {maximum:?}")]
46    BlockIndexOutOfRange { value: [u32; 3], maximum: [u32; 3] },
47    /// A regular sub-block has zero or negative size.
48    #[error("sub-block {} has zero or negative size", format_corners(corners))]
49    RegularSubblockZeroSize { corners: [u32; 6] },
50    /// A regular sub-block extends outside the parent.
51    #[error(
52        "sub-block {} exceeds the maximum {maximum:?}",
53        format_corners(corners)
54    )]
55    RegularSubblockOutsideParent {
56        corners: [u32; 6],
57        maximum: [u32; 3],
58    },
59    /// A regular sub-block doesn't match the octree or full sub-block mode.
60    #[error("sub-block {} is invalid for {mode:?} mode", format_corners(corners))]
61    RegularSubblockNotInMode {
62        corners: [u32; 6],
63        mode: SubblockMode,
64    },
65    /// A free-form sub-block has zero or negative size.
66    #[error("sub-block {} has zero or negative size", format_corners(corners))]
67    FreeformSubblockZeroSize { corners: [f64; 6] },
68    /// A free-form sub-block is outside the [0.0, 1.0] parent range.
69    #[error(
70        "sub-block {} is outside the valid range of 0.0 to 1.0",
71        format_corners(corners)
72    )]
73    FreeformSubblockOutsideParent { corners: [f64; 6] },
74}
75
76/// Errors generated by this crate.
77#[derive(Debug, thiserror::Error)]
78#[non_exhaustive]
79pub enum Error {
80    /// Used when memory allocation fails.
81    #[error("Memory allocation failed")]
82    OutOfMemory,
83    /// Forward errors from file operations.
84    #[error("File IO error: {0}")]
85    IoError(std::io::Error),
86    /// When the correct file header is not detected.
87    #[error("Error: the zip comment does not identify this as an OMF file: '{0}'")]
88    NotOmf(String),
89    /// When the file version is newer than the library.
90    #[error(
91        "Version error: the file uses OMF v{0}.{1} but this version library can only read 0.9 and 2.0"
92    )]
93    NewerVersion(u32, u32),
94    /// The file version is pre-release and can't be loaded by release versions.
95    #[error("Version error: the file uses pre-release OMF v{0}.{1}-{2} and can't be loaded")]
96    PreReleaseVersion(u32, u32, String),
97    /// Forwards `serde_json` errors when deserializing.
98    #[error("JSON deserialization error: {0}")]
99    DeserializationFailed(#[from] serde_json::Error),
100    /// Forwards `serde_json` errors when serializing.
101    #[error("JSON serialization error: {0}")]
102    SerializationFailed(serde_json::Error),
103    /// Passes out errors detected during OMF validation.
104    #[error("Validation failed")]
105    ValidationFailed(#[from] validate::Problems),
106    /// When trying to cast `f64` to `f32` for example, as that would lose precision.
107    #[error("Error: can't cast from {0} to {1} without losing data")]
108    UnsafeCast(&'static str, &'static str),
109    /// Writing an image that isn't in PNG or JPEG format.
110    #[error("Error: image is not in PNG or JPEG encoding")]
111    NotImageData,
112    /// Writing an array that isn't in Parquet format.
113    #[error("Error: image is not in Parquet encoding")]
114    NotParquetData,
115    /// Tried to read something that exceeds the provided limits.
116    #[error("Error: safety limit exceeded")]
117    LimitExceeded(Limit),
118    /// Array data errors, when reading or writing.
119    #[error("Data error: {0}")]
120    InvalidData(Box<InvalidData>),
121    /// A data file or index is missing from the zip.
122    #[error("Error: missing archive member '{0}'")]
123    ZipMemberMissing(String),
124    /// Zip read or write failed.
125    #[error("Zip error: {0}")]
126    ZipError(String),
127    /// When a Parquet schema doesn't match.
128    #[cfg(feature = "parquet")]
129    #[error("{}", mismatch_string(.0, .1))]
130    ParquetSchemaMismatch(
131        std::sync::Arc<parquet::schema::types::Type>,
132        std::sync::Arc<Vec<parquet::schema::types::Type>>,
133    ),
134    /// Forward errors from array operations.
135    #[cfg(feature = "parquet")]
136    #[error("{0}")]
137    ParquetError(Box<parquet::errors::ParquetError>),
138    /// Forward errors from image operations.
139    #[cfg(feature = "image")]
140    #[error("Image processing error: {0}")]
141    ImageError(Box<image::ImageError>),
142    /// Errors from OMF1 conversion.
143    #[cfg(feature = "omf1")]
144    #[error("OMF1 conversion failed: {0}")]
145    Omf1Error(#[from] crate::omf1::Omf1Error),
146}
147
148impl From<InvalidData> for Error {
149    fn from(value: InvalidData) -> Self {
150        Self::InvalidData(value.into())
151    }
152}
153
154impl From<std::io::Error> for Error {
155    fn from(error: std::io::Error) -> Self {
156        match error.kind() {
157            std::io::ErrorKind::OutOfMemory => Error::OutOfMemory,
158            std::io::ErrorKind::Other if error.get_ref().is_some_and(|r| r.is::<Error>()) => {
159                *error.into_inner().unwrap().downcast().unwrap()
160            }
161            _ => Error::IoError(error),
162        }
163    }
164}
165
166impl From<TryReserveError> for Error {
167    fn from(_: TryReserveError) -> Self {
168        Error::OutOfMemory
169    }
170}
171
172impl From<ZipError> for Error {
173    fn from(value: ZipError) -> Self {
174        match value {
175            ZipError::Io(e) => Self::IoError(e),
176            other => Self::ZipError(other.to_string()),
177        }
178    }
179}
180
181#[cfg(feature = "image")]
182impl From<image::ImageError> for Error {
183    fn from(value: image::ImageError) -> Self {
184        use image::error::LimitErrorKind;
185        match &value {
186            image::ImageError::Limits(err @ image::error::LimitError { .. }) => match err.kind() {
187                LimitErrorKind::DimensionError => Error::LimitExceeded(Limit::ImageDim),
188                LimitErrorKind::InsufficientMemory => Error::LimitExceeded(Limit::ArrayBytes),
189                _ => Error::ImageError(value.into()),
190            },
191            _ => Error::ImageError(value.into()),
192        }
193    }
194}
195
196#[cfg(feature = "parquet")]
197impl From<parquet::errors::ParquetError> for Error {
198    fn from(value: parquet::errors::ParquetError) -> Self {
199        Self::ParquetError(value.into())
200    }
201}
202
203#[cfg(feature = "parquet")]
204fn mismatch_string(
205    found: &parquet::schema::types::Type,
206    expected: &[parquet::schema::types::Type],
207) -> String {
208    use parquet::schema::{printer::print_schema, types::Type};
209
210    fn schema_string(ty: &Type) -> String {
211        let mut buf = Vec::new();
212        print_schema(&mut buf, ty);
213        String::from_utf8_lossy(&buf).trim_end().to_owned()
214    }
215    let mut out = format!(
216        "Parquet schema mismatch, found:\n{found}\n\n{expected_label}:\n",
217        found = schema_string(found),
218        expected_label = if expected.len() == 1 {
219            "Expected"
220        } else {
221            "Expected one of"
222        }
223    );
224    for ty in expected {
225        out.push_str(&schema_string(ty));
226        out.push('\n');
227    }
228    out
229}