Skip to main content

nord_format/
error.rs

1//! What can go wrong: [`ParseError`] for a file that violates its format,
2//! [`Error`] folding that together with I/O (and ZIP, under `bundle`).
3
4use std::io;
5use thiserror::Error as ThisError;
6
7/// Shorthand for `Result<T, Error>`.
8pub type Result<T> = std::result::Result<T, Error>;
9
10/// A file that violates its format: unknown tags or lengths, checksum
11/// mismatches, out-of-range values, unsupported schema versions.
12#[derive(ThisError, Debug)]
13#[non_exhaustive]
14pub enum ParseError {
15    #[error("value {value} is outside {bound}")]
16    OutOfBounds { value: String, bound: String },
17
18    #[error("unknown format: {0}")]
19    UnknownFormat(String),
20
21    #[error("unknown filetype: {0}")]
22    UnknownFileType(String),
23
24    /// A CBIN tag other than the one the reader was asked for. Formats sharing a body
25    /// layout decode each other's files without complaint, so the tag is the only thing
26    /// that tells them apart.
27    #[error("expected a {expected} file, got {got}")]
28    WrongFormat { expected: &'static str, got: String },
29
30    #[error("{0}")]
31    AssertFail(String),
32
33    /// A file whose schema version this build has never been validated against.
34    ///
35    /// Field offsets are only known to be right for the versions in the corpus.
36    /// Decoding a newer one would produce plausible-looking but wrong values, and
37    /// writing it back would then persist them — so refuse instead.
38    #[error(
39        "{format}: schema version {version} is not supported (known: {supported:?}); \
40             refusing to decode rather than risk misreading fields"
41    )]
42    UnsupportedVersion {
43        format: &'static str,
44        version: u32,
45        supported: &'static [u32],
46    },
47
48    /// A body whose length is not the one the format declares — a truncated or
49    /// padded file on read, a miscounting writer on write.
50    #[error(
51        "{format}: the body is {got} bytes where the format holds {expected}; \
52             refusing rather than misread fields"
53    )]
54    WrongBodyLength {
55        format: String,
56        got: u64,
57        expected: u64,
58    },
59}
60
61/// Lets an infallible decode sit alongside fallible ones behind the same `?`.
62impl From<std::convert::Infallible> for ParseError {
63    fn from(never: std::convert::Infallible) -> Self {
64        match never {}
65    }
66}
67
68/// Everything a read or write can fail with: I/O, a format violation, or
69/// (under `bundle`) a ZIP error.
70#[derive(ThisError, Debug)]
71#[non_exhaustive]
72pub enum Error {
73    #[error(transparent)]
74    Io(#[from] io::Error),
75
76    #[error(transparent)]
77    Parse(#[from] ParseError),
78
79    #[cfg(feature = "bundle")]
80    #[error(transparent)]
81    Zip(#[from] zip::result::ZipError),
82}
83
84/// A zeroed buffer of `len` bytes, reporting an allocation the platform cannot
85/// make instead of aborting the process on it.
86pub(crate) fn try_vec(len: usize) -> std::result::Result<Vec<u8>, ParseError> {
87    let mut buf = Vec::new();
88    buf.try_reserve_exact(len)
89        .map_err(|_| ParseError::OutOfBounds {
90            value: format!("{len} bytes"),
91            bound: "an allocation that fits memory".into(),
92        })?;
93    buf.resize(len, 0);
94    Ok(buf)
95}