use thiserror::Error;
use crate::dict::BundleId;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Error)]
#[non_exhaustive]
pub enum InvalidDataKind {
#[error("frame magic number")]
Magic,
#[error("frame header")]
FrameHeader,
#[error("frame header checksum")]
HeaderChecksum,
#[error("chunk header")]
ChunkHeader,
#[error("chunk stream graph")]
Graph,
#[error("codec stream")]
CodecStream,
#[error("dictionary index")]
DictionaryIndex,
#[error("frame without chunks")]
EmptyFrame,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Error)]
#[non_exhaustive]
pub enum Unsupported {
#[error("format version {version}")]
FormatVersion {
version: u32,
},
#[error("standard codec {id}")]
Codec {
id: u32,
},
#[error("custom codec {id}")]
CustomCodec {
id: u32,
},
#[error("frames of unknown size")]
UnknownSize,
#[error("frames with {outputs} outputs")]
MultipleOutputs {
outputs: usize,
},
#[error("typed outputs")]
TypedOutput,
}
#[derive(Debug, Clone, Eq, PartialEq, Error)]
#[non_exhaustive]
pub enum DecodeError {
#[cfg(feature = "custom-codec")]
#[error("custom codec {id} failed: {source}")]
CustomCodec {
id: u32,
#[source]
source: crate::custom_codec::CodecError,
},
#[error("invalid OpenZL {kind}")]
InvalidData {
#[source]
kind: InvalidDataKind,
},
#[error("unsupported OpenZL feature: {feature}")]
Unsupported {
#[source]
feature: Unsupported,
},
#[error("unexpected end of compressed input")]
UnexpectedEndOfInput,
#[error("trailing data at byte {offset}")]
TrailingData {
offset: u64,
},
#[error("output slice is full after {written} bytes")]
OutputTooSmall {
written: usize,
},
#[error("content checksum mismatch: stored {expected:#010x}, computed {computed:#010x}")]
ContentChecksumMismatch {
expected: u32,
computed: u32,
},
#[error("compressed checksum mismatch: stored {expected:#010x}, computed {computed:#010x}")]
CompressedChecksumMismatch {
expected: u32,
computed: u32,
},
#[error("frame declares {declared} content bytes, decoded {decoded}")]
ContentSizeMismatch {
declared: u64,
decoded: u64,
},
#[error("input exceeds the limit of {limit} bytes")]
InputLimitExceeded {
limit: u64,
},
#[error("output exceeds the limit of {limit} bytes")]
OutputLimitExceeded {
limit: u64,
},
#[error("dictionary bundle {bundle} is not available")]
MissingBundle {
bundle: BundleId,
},
#[error("the session cannot accept this operation in its current state")]
InvalidState,
}
impl DecodeError {
pub(crate) const fn invalid(kind: InvalidDataKind) -> Self {
Self::InvalidData { kind }
}
pub(crate) const fn unsupported(feature: Unsupported) -> Self {
Self::Unsupported { feature }
}
}
#[cfg(not(feature = "no_std"))]
impl From<DecodeError> for std::io::Error {
fn from(error: DecodeError) -> Self {
use std::io::ErrorKind;
let kind = match &error {
DecodeError::InvalidData { .. }
| DecodeError::TrailingData { .. }
| DecodeError::ContentChecksumMismatch { .. }
| DecodeError::CompressedChecksumMismatch { .. }
| DecodeError::ContentSizeMismatch { .. } => ErrorKind::InvalidData,
#[cfg(feature = "custom-codec")]
DecodeError::CustomCodec { .. } => ErrorKind::InvalidData,
DecodeError::Unsupported { .. } => ErrorKind::Unsupported,
DecodeError::MissingBundle { .. } => ErrorKind::NotFound,
DecodeError::UnexpectedEndOfInput => ErrorKind::UnexpectedEof,
DecodeError::OutputTooSmall { .. } | DecodeError::InvalidState => {
ErrorKind::InvalidInput
}
DecodeError::InputLimitExceeded { .. } | DecodeError::OutputLimitExceeded { .. } => {
ErrorKind::Other
}
};
Self::new(kind, error)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
#[test]
fn messages_name_the_structure_and_feature() {
assert_eq!(
DecodeError::invalid(InvalidDataKind::ChunkHeader).to_string(),
"invalid OpenZL chunk header"
);
assert_eq!(
DecodeError::unsupported(Unsupported::Codec { id: 49 }).to_string(),
"unsupported OpenZL feature: standard codec 49"
);
assert_eq!(
DecodeError::invalid(InvalidDataKind::DictionaryIndex).to_string(),
"invalid OpenZL dictionary index"
);
assert!(
DecodeError::MissingBundle {
bundle: BundleId::from([0xab; 32])
}
.to_string()
.contains(&"ab".repeat(32))
);
assert!(
Unsupported::MultipleOutputs { outputs: 3 }
.to_string()
.contains('3')
);
assert!(
DecodeError::ContentChecksumMismatch {
expected: 1,
computed: 2
}
.to_string()
.contains("0x00000001")
);
}
#[cfg(not(feature = "no_std"))]
#[test]
fn io_errors_keep_the_decode_error() {
use std::io::ErrorKind;
let cases = [
(
DecodeError::invalid(InvalidDataKind::Magic),
ErrorKind::InvalidData,
),
(
DecodeError::unsupported(Unsupported::UnknownSize),
ErrorKind::Unsupported,
),
(
DecodeError::MissingBundle {
bundle: BundleId::from([1; 32]),
},
ErrorKind::NotFound,
),
(DecodeError::UnexpectedEndOfInput, ErrorKind::UnexpectedEof),
(DecodeError::InvalidState, ErrorKind::InvalidInput),
(
DecodeError::OutputLimitExceeded { limit: 1 },
ErrorKind::Other,
),
];
for (error, kind) in cases {
let io = std::io::Error::from(error.clone());
assert_eq!(io.kind(), kind);
assert_eq!(
io.get_ref().and_then(|e| e.downcast_ref::<DecodeError>()),
Some(&error)
);
}
}
}