use crate::{LimitExceeded, LimitKind, UnsupportedOperation};
use alloc::boxed::Box;
use enough::StopReason;
use whereat::At;
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ErrorCategory {
Image(ImageError),
Request(RequestError),
Resource(ResourceError),
Policy(PolicyKind),
Stopped(StopReason),
Io(CodecIoKind),
Internal(InternalKind),
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum PolicyKind {
Decode,
Encode,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum InternalKind {
Bug,
Dependency,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ImageError {
Malformed,
UnexpectedEof,
Unsupported(UnsupportedImageKind),
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum UnsupportedImageKind {
Type,
Feature,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum RequestError {
Invalid(InvalidKind),
Unsupported(UnsupportedOperation),
CmsRequired,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum InvalidKind {
Parameters,
Buffer,
State,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ResourceError {
Limits(LimitKind),
OutOfMemory,
}
impl From<ImageError> for ErrorCategory {
#[inline]
fn from(e: ImageError) -> Self {
Self::Image(e)
}
}
impl From<RequestError> for ErrorCategory {
#[inline]
fn from(e: RequestError) -> Self {
Self::Request(e)
}
}
impl From<ResourceError> for ErrorCategory {
#[inline]
fn from(e: ResourceError) -> Self {
Self::Resource(e)
}
}
impl From<StopReason> for ErrorCategory {
#[inline]
fn from(r: StopReason) -> Self {
Self::Stopped(r)
}
}
impl From<UnsupportedImageKind> for ErrorCategory {
#[inline]
fn from(k: UnsupportedImageKind) -> Self {
Self::Image(ImageError::Unsupported(k))
}
}
impl From<InvalidKind> for ErrorCategory {
#[inline]
fn from(k: InvalidKind) -> Self {
Self::Request(RequestError::Invalid(k))
}
}
impl From<PolicyKind> for ErrorCategory {
#[inline]
fn from(k: PolicyKind) -> Self {
Self::Policy(k)
}
}
impl From<InternalKind> for ErrorCategory {
#[inline]
fn from(k: InternalKind) -> Self {
Self::Internal(k)
}
}
impl From<LimitKind> for ErrorCategory {
#[inline]
fn from(k: LimitKind) -> Self {
Self::Resource(ResourceError::Limits(k))
}
}
impl core::fmt::Display for ErrorCategory {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Image(e) => write!(f, "{e}"),
Self::Request(e) => write!(f, "{e}"),
Self::Resource(e) => write!(f, "{e}"),
Self::Policy(k) => write!(f, "{k}"),
Self::Stopped(e) => write!(f, "{e}"),
Self::Io(_) => f.write_str("I/O error"),
Self::Internal(k) => write!(f, "{k}"),
}
}
}
impl core::fmt::Display for PolicyKind {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Decode => f.write_str("rejected by decode policy"),
Self::Encode => f.write_str("rejected by encode policy"),
}
}
}
impl core::fmt::Display for InternalKind {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Bug => f.write_str("internal error (bug)"),
Self::Dependency => f.write_str("internal error (unclassified dependency failure)"),
}
}
}
impl core::fmt::Display for ImageError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Malformed => f.write_str("malformed image"),
Self::UnexpectedEof => f.write_str("unexpected end of input"),
Self::Unsupported(UnsupportedImageKind::Type) => f.write_str("unsupported image type"),
Self::Unsupported(UnsupportedImageKind::Feature) => {
f.write_str("unsupported image feature")
}
}
}
}
impl core::fmt::Display for RequestError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Invalid(InvalidKind::Parameters) => f.write_str("invalid parameters"),
Self::Invalid(InvalidKind::Buffer) => f.write_str("invalid pixel buffer"),
Self::Invalid(InvalidKind::State) => f.write_str("invalid state"),
Self::Unsupported(op) => write!(f, "{op}"),
Self::CmsRequired => f.write_str("colour-management transform required"),
}
}
}
impl core::fmt::Display for ResourceError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Limits(kind) => write!(f, "resource limit exceeded ({kind:?})"),
Self::OutOfMemory => f.write_str("out of memory"),
}
}
}
#[cfg_attr(
feature = "std",
doc = "`std` consumers read the [`kind`](Self::kind) accessor when present."
)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct CodecIoKind {
#[cfg(feature = "std")]
kind: Option<std::io::ErrorKind>,
}
impl CodecIoKind {
#[inline]
pub const fn opaque() -> Self {
Self {
#[cfg(feature = "std")]
kind: None,
}
}
#[cfg(feature = "std")]
#[inline]
pub const fn kind(&self) -> Option<std::io::ErrorKind> {
self.kind
}
}
#[cfg(feature = "std")]
impl From<std::io::ErrorKind> for CodecIoKind {
#[inline]
fn from(kind: std::io::ErrorKind) -> Self {
Self { kind: Some(kind) }
}
}
#[cfg(feature = "std")]
impl From<&std::io::Error> for CodecIoKind {
#[inline]
fn from(e: &std::io::Error) -> Self {
Self {
kind: Some(e.kind()),
}
}
}
pub trait CategorizedError: core::any::Any {
fn codec_name(&self) -> Option<&'static str>;
fn category(&self) -> ErrorCategory;
}
impl<E: CategorizedError> CategorizedError for At<E> {
#[inline]
fn codec_name(&self) -> Option<&'static str> {
self.error().codec_name()
}
#[inline]
fn category(&self) -> ErrorCategory {
self.error().category()
}
}
impl CategorizedError for StopReason {
#[inline]
fn codec_name(&self) -> Option<&'static str> {
None
}
#[inline]
fn category(&self) -> ErrorCategory {
ErrorCategory::Stopped(*self)
}
}
impl CategorizedError for UnsupportedOperation {
#[inline]
fn codec_name(&self) -> Option<&'static str> {
None
}
#[inline]
fn category(&self) -> ErrorCategory {
ErrorCategory::Request(RequestError::Unsupported(*self))
}
}
impl CategorizedError for LimitExceeded {
#[inline]
fn codec_name(&self) -> Option<&'static str> {
None
}
#[inline]
fn category(&self) -> ErrorCategory {
ErrorCategory::Resource(ResourceError::Limits(self.kind()))
}
}
pub struct CodecError(Box<Repr>);
struct Repr {
category: ErrorCategory,
codec: Option<&'static str>,
detail: Option<Box<dyn core::error::Error + Send + Sync>>,
}
impl core::fmt::Debug for CodecError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("CodecError")
.field("category", &self.0.category)
.field("codec", &self.0.codec)
.field("detail", &self.0.detail)
.finish()
}
}
impl CodecError {
#[inline]
pub fn new(codec: Option<&'static str>, category: ErrorCategory) -> Self {
CodecError(Box::new(Repr {
category,
codec,
detail: None,
}))
}
#[inline]
pub fn from_native<E>(detail: E) -> Self
where
E: CategorizedError + core::error::Error + Send + Sync + 'static,
{
debug_assert!(
detail.codec_name().is_some(),
"CodecError built from a type with no codec_name ({}); wrap it in \
your codec's error type, or use from_parts with an explicit name",
core::any::type_name::<E>(),
);
CodecError(Box::new(Repr {
category: detail.category(),
codec: detail.codec_name(),
detail: Some(Box::new(detail)),
}))
}
#[inline]
pub fn of<E>(located: At<E>) -> At<CodecError>
where
E: CategorizedError + core::error::Error + Send + Sync + 'static,
{
located.map_error(CodecError::from_native::<E>)
}
#[inline]
pub fn from_parts(
codec: Option<&'static str>,
category: ErrorCategory,
detail: Box<dyn core::error::Error + Send + Sync>,
) -> Self {
CodecError(Box::new(Repr {
category,
codec,
detail: Some(detail),
}))
}
#[inline]
#[must_use]
pub fn with_codec(mut self, codec: Option<&'static str>) -> Self {
self.0.codec = codec;
self
}
#[inline]
pub fn category(&self) -> ErrorCategory {
self.0.category
}
#[inline]
pub fn codec(&self) -> Option<&'static str> {
self.0.codec
}
#[inline]
pub fn detail(&self) -> Option<&(dyn core::error::Error + 'static)> {
self.0
.detail
.as_deref()
.map(|d| d as &(dyn core::error::Error + 'static))
}
}
impl CategorizedError for CodecError {
#[inline]
fn codec_name(&self) -> Option<&'static str> {
self.0.codec
}
#[inline]
fn category(&self) -> ErrorCategory {
self.0.category
}
}
impl core::fmt::Display for CodecError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if let Some(codec) = self.0.codec {
write!(f, "{codec}: ")?;
}
match &self.0.detail {
Some(detail) => core::fmt::Display::fmt(detail, f),
None => core::fmt::Display::fmt(&self.0.category, f),
}
}
}
impl core::error::Error for CodecError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
self.0
.detail
.as_deref()
.map(|d| d as &(dyn core::error::Error + 'static))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct StreamOffset(pub u64);
impl core::fmt::Display for StreamOffset {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "byte offset {}", self.0)
}
}
pub trait CodecErrorExt {
fn unsupported_operation(&self) -> Option<&UnsupportedOperation>;
fn limit_exceeded(&self) -> Option<&LimitExceeded>;
fn codec_error(&self) -> Option<&CodecError> {
None
}
fn error_category(&self) -> Option<ErrorCategory> {
self.codec_error().map(CodecError::category)
}
fn find_cause<T: core::error::Error + 'static>(&self) -> Option<&T>;
}
fn recover_codec_error<'a>(err: &'a (dyn core::error::Error + 'static)) -> Option<&'a CodecError> {
if let Some(at) = find_cause::<At<CodecError>>(err) {
return Some(at.error());
}
if let Some(ce) = find_cause::<CodecError>(err) {
return Some(ce);
}
if let Some(b) = find_cause::<Box<At<CodecError>>>(err) {
return Some(b.error());
}
if let Some(at) = find_cause::<At<Box<CodecError>>>(err) {
return Some(&**at.error());
}
if let Some(b) = find_cause::<Box<CodecError>>(err) {
return Some(&**b);
}
None
}
impl<E: core::error::Error + 'static> CodecErrorExt for E {
fn unsupported_operation(&self) -> Option<&UnsupportedOperation> {
find_cause::<UnsupportedOperation>(self)
}
fn limit_exceeded(&self) -> Option<&LimitExceeded> {
find_cause::<LimitExceeded>(self)
}
fn codec_error(&self) -> Option<&CodecError> {
recover_codec_error(self)
}
fn find_cause<T: core::error::Error + 'static>(&self) -> Option<&T> {
find_cause::<T>(self)
}
}
impl CodecErrorExt for dyn core::error::Error + Send + Sync + 'static {
fn unsupported_operation(&self) -> Option<&UnsupportedOperation> {
find_cause::<UnsupportedOperation>(self)
}
fn limit_exceeded(&self) -> Option<&LimitExceeded> {
find_cause::<LimitExceeded>(self)
}
fn codec_error(&self) -> Option<&CodecError> {
recover_codec_error(self)
}
fn find_cause<T: core::error::Error + 'static>(&self) -> Option<&T> {
find_cause::<T>(self)
}
}
impl CodecErrorExt for dyn core::error::Error + Send + 'static {
fn unsupported_operation(&self) -> Option<&UnsupportedOperation> {
find_cause::<UnsupportedOperation>(self)
}
fn limit_exceeded(&self) -> Option<&LimitExceeded> {
find_cause::<LimitExceeded>(self)
}
fn codec_error(&self) -> Option<&CodecError> {
recover_codec_error(self)
}
fn find_cause<T: core::error::Error + 'static>(&self) -> Option<&T> {
find_cause::<T>(self)
}
}
impl CodecErrorExt for dyn core::error::Error + 'static {
fn unsupported_operation(&self) -> Option<&UnsupportedOperation> {
find_cause::<UnsupportedOperation>(self)
}
fn limit_exceeded(&self) -> Option<&LimitExceeded> {
find_cause::<LimitExceeded>(self)
}
fn codec_error(&self) -> Option<&CodecError> {
recover_codec_error(self)
}
fn find_cause<T: core::error::Error + 'static>(&self) -> Option<&T> {
find_cause::<T>(self)
}
}
pub fn find_cause<'a, T: core::error::Error + 'static>(
mut err: &'a (dyn core::error::Error + 'static),
) -> Option<&'a T> {
loop {
if let Some(t) = err.downcast_ref::<T>() {
return Some(t);
}
err = err.source()?;
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::boxed::Box;
use alloc::string::String;
use core::fmt;
#[derive(Debug)]
enum TestCodecError {
Limit(LimitExceeded),
Unsupported(UnsupportedOperation),
Cancelled(StopReason),
Malformed(String),
RejectedByPolicy,
BadBuffer,
WrongState,
}
impl fmt::Display for TestCodecError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Limit(e) => write!(f, "limit: {e}"),
Self::Unsupported(e) => write!(f, "unsupported: {e}"),
Self::Cancelled(r) => write!(f, "cancelled: {r}"),
Self::Malformed(s) => write!(f, "malformed: {s}"),
Self::RejectedByPolicy => write!(f, "rejected by policy"),
Self::BadBuffer => write!(f, "bad buffer"),
Self::WrongState => write!(f, "wrong state"),
}
}
}
impl core::error::Error for TestCodecError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::Limit(e) => Some(e),
Self::Unsupported(e) => Some(e),
Self::Cancelled(_)
| Self::Malformed(_)
| Self::RejectedByPolicy
| Self::BadBuffer
| Self::WrongState => None,
}
}
}
impl CategorizedError for TestCodecError {
fn codec_name(&self) -> Option<&'static str> {
Some("test-codec")
}
fn category(&self) -> ErrorCategory {
match self {
Self::Limit(e) => e.category(),
Self::Unsupported(e) => e.category(),
Self::Cancelled(r) => r.category(),
Self::Malformed(_) => ErrorCategory::Image(ImageError::Malformed),
Self::RejectedByPolicy => ErrorCategory::Policy(PolicyKind::Decode),
Self::BadBuffer => {
ErrorCategory::Request(RequestError::Invalid(InvalidKind::Buffer))
}
Self::WrongState => {
ErrorCategory::Request(RequestError::Invalid(InvalidKind::State))
}
}
}
}
#[test]
fn ext_limit_exceeded_direct() {
let err = LimitExceeded::Width {
actual: 5000,
max: 4096,
};
assert_eq!(err.limit_exceeded(), Some(&err));
}
#[test]
fn ext_limit_exceeded_through_source_chain() {
let inner = LimitExceeded::Pixels {
actual: 100_000_000,
max: 50_000_000,
};
let err = TestCodecError::Limit(inner.clone());
assert_eq!(err.limit_exceeded(), Some(&inner));
}
#[test]
fn ext_unsupported_through_source_chain() {
let err = TestCodecError::Unsupported(UnsupportedOperation::AnimationEncode);
assert_eq!(
err.unsupported_operation(),
Some(&UnsupportedOperation::AnimationEncode)
);
}
#[test]
fn ext_returns_none_when_absent() {
let err = TestCodecError::Malformed("something else".into());
assert!(err.limit_exceeded().is_none());
assert!(err.unsupported_operation().is_none());
}
#[test]
fn ext_through_boxed_error() {
let inner = LimitExceeded::Memory {
actual: 1_000_000_000,
max: 512_000_000,
};
let err = TestCodecError::Limit(inner.clone());
let boxed: Box<dyn core::error::Error + Send + Sync> = Box::new(err);
assert_eq!(boxed.limit_exceeded(), Some(&inner));
}
#[test]
fn ext_find_cause_generic() {
let err = TestCodecError::Unsupported(UnsupportedOperation::DecodeInto);
let found: Option<&UnsupportedOperation> = err.find_cause();
assert_eq!(found, Some(&UnsupportedOperation::DecodeInto));
}
#[test]
fn find_cause_free_fn() {
let err = LimitExceeded::Width {
actual: 5000,
max: 4096,
};
let found = find_cause::<LimitExceeded>(&err);
assert_eq!(found, Some(&err));
}
#[test]
fn category_maps_each_codec_variant() {
assert_eq!(
TestCodecError::Malformed("x".into()).category(),
ErrorCategory::Image(ImageError::Malformed)
);
assert_eq!(
TestCodecError::RejectedByPolicy.category(),
ErrorCategory::Policy(PolicyKind::Decode)
);
assert_eq!(
TestCodecError::BadBuffer.category(),
ErrorCategory::Request(RequestError::Invalid(InvalidKind::Buffer))
);
assert_eq!(
TestCodecError::WrongState.category(),
ErrorCategory::Request(RequestError::Invalid(InvalidKind::State))
);
assert_eq!(
TestCodecError::Unsupported(UnsupportedOperation::AnimationEncode).category(),
ErrorCategory::Request(RequestError::Unsupported(
UnsupportedOperation::AnimationEncode
))
);
assert_eq!(
TestCodecError::Cancelled(StopReason::Cancelled).category(),
ErrorCategory::Stopped(StopReason::Cancelled)
);
assert_eq!(
TestCodecError::Cancelled(StopReason::TimedOut).category(),
ErrorCategory::Stopped(StopReason::TimedOut)
);
assert_eq!(
TestCodecError::Limit(LimitExceeded::Pixels { actual: 9, max: 4 }).category(),
ErrorCategory::Resource(ResourceError::Limits(LimitKind::Pixels))
);
}
#[test]
fn zencodec_cause_types_categorize() {
assert_eq!(
StopReason::Cancelled.category(),
ErrorCategory::Stopped(StopReason::Cancelled)
);
assert_eq!(
StopReason::TimedOut.category(),
ErrorCategory::Stopped(StopReason::TimedOut)
);
assert_eq!(
UnsupportedOperation::AnimationDecode.category(),
ErrorCategory::Request(RequestError::Unsupported(
UnsupportedOperation::AnimationDecode
))
);
assert_eq!(
UnsupportedOperation::PixelFormat.category(),
ErrorCategory::Request(RequestError::Unsupported(UnsupportedOperation::PixelFormat))
);
assert_eq!(
LimitExceeded::Memory { actual: 2, max: 1 }.category(),
ErrorCategory::Resource(ResourceError::Limits(LimitKind::Memory))
);
}
#[test]
fn category_is_preserved_through_at() {
let located = At::wrap(TestCodecError::Cancelled(StopReason::TimedOut));
assert_eq!(
located.category(),
ErrorCategory::Stopped(StopReason::TimedOut)
);
assert!(matches!(
located.error(),
TestCodecError::Cancelled(StopReason::TimedOut)
));
}
#[test]
fn codec_error_from_native_and_of_capture_category_and_codec() {
let e = CodecError::from_native(TestCodecError::Malformed("bad".into()));
assert_eq!(e.category(), ErrorCategory::Image(ImageError::Malformed));
assert_eq!(e.codec(), Some("test-codec")); assert!(e.detail().is_some());
assert_eq!(alloc::format!("{e}"), "test-codec: malformed: bad");
let located: At<CodecError> =
CodecError::of(At::wrap(TestCodecError::Malformed("bad".into())));
assert_eq!(
located.category(),
ErrorCategory::Image(ImageError::Malformed)
);
assert_eq!(located.error().codec(), Some("test-codec"));
let e2 = CodecError::from_parts(
Some("zenjpeg"),
ErrorCategory::Io(CodecIoKind::opaque()),
Box::new(TestCodecError::Malformed("x".into())),
);
assert_eq!(e2.category(), ErrorCategory::Io(CodecIoKind::opaque()));
assert_eq!(e2.codec(), Some("zenjpeg"));
}
#[test]
fn codec_error_new_is_detail_free() {
let e = CodecError::new(Some("zenpng"), ErrorCategory::Image(ImageError::Malformed));
assert_eq!(e.category(), ErrorCategory::Image(ImageError::Malformed));
assert_eq!(e.codec(), Some("zenpng"));
assert!(e.detail().is_none());
assert_eq!(alloc::format!("{e}"), "zenpng: malformed image");
}
#[test]
fn codec_error_recovers_through_box_dyn_error() {
let located = CodecError::of(At::wrap(TestCodecError::Cancelled(StopReason::Cancelled)));
let boxed: Box<dyn core::error::Error + Send + Sync> = Box::new(located);
assert_eq!(
boxed.error_category(),
Some(ErrorCategory::Stopped(StopReason::Cancelled))
);
assert_eq!(
boxed.codec_error().and_then(CodecError::codec),
Some("test-codec")
);
let bare: Box<dyn core::error::Error + Send + Sync> =
Box::new(CodecError::from_native(TestCodecError::WrongState));
assert_eq!(
bare.error_category(),
Some(ErrorCategory::Request(RequestError::Invalid(
InvalidKind::State
)))
);
assert_eq!(
bare.codec_error().and_then(CodecError::codec),
Some("test-codec")
);
let other: Box<dyn core::error::Error + Send + Sync> =
Box::new(TestCodecError::Malformed("not wrapped".into()));
assert_eq!(other.error_category(), None);
assert!(other.codec_error().is_none());
}
#[test]
fn codec_error_recovers_through_a_wrapping_error() {
#[derive(Debug)]
struct Wrap(At<CodecError>);
impl fmt::Display for Wrap {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "wrap: {}", self.0)
}
}
impl core::error::Error for Wrap {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
Some(&self.0)
}
}
let wrapped = Wrap(CodecError::of(At::wrap(TestCodecError::Limit(
LimitExceeded::Pixels { actual: 9, max: 4 },
))));
assert_eq!(
wrapped.error_category(),
Some(ErrorCategory::Resource(ResourceError::Limits(
LimitKind::Pixels
)))
);
assert_eq!(
wrapped.codec_error().and_then(CodecError::codec),
Some("test-codec")
);
}
#[test]
fn codec_error_typed_cause_still_findable() {
let inner = LimitExceeded::Memory { actual: 9, max: 4 };
let located = CodecError::of(At::wrap(TestCodecError::Limit(inner.clone())));
assert_eq!(located.limit_exceeded(), Some(&inner));
assert_eq!(
located.error_category(),
Some(ErrorCategory::Resource(ResourceError::Limits(
LimitKind::Memory
)))
);
}
#[test]
fn codec_error_at_trace_is_present() {
let e = CodecError::of(At::wrap(TestCodecError::Malformed("x".into()))).at();
let dbg = alloc::format!("{e:?}");
assert!(
dbg.contains("at "),
"trace frame should render in Debug: {dbg}"
);
}
#[test]
fn stream_offset_rides_the_trace_through_erasure() {
let err = At::wrap(CodecError::new(
Some("zenjpeg"),
ErrorCategory::Image(ImageError::Malformed),
))
.at_data(|| StreamOffset(1234));
let boxed: Box<dyn core::error::Error + Send + Sync> = Box::new(err);
let at = boxed
.downcast_ref::<At<CodecError>>()
.expect("downcast to the concrete envelope");
let offset = at
.contexts()
.find_map(|c| c.downcast_ref::<StreamOffset>().copied());
assert_eq!(offset, Some(StreamOffset(1234)));
assert_eq!(
boxed.error_category(),
Some(ErrorCategory::Image(ImageError::Malformed))
);
}
#[test]
fn error_types_stay_small() {
use core::mem::size_of;
let word = size_of::<usize>();
assert!(
size_of::<CodecError>() <= word,
"CodecError = {} bytes (expected <= {})",
size_of::<CodecError>(),
word
);
assert!(
size_of::<At<CodecError>>() <= 2 * word,
"At<CodecError> = {} bytes (expected <= {})",
size_of::<At<CodecError>>(),
2 * word
);
assert!(
size_of::<Result<(), At<CodecError>>>() <= 2 * word,
"Result<(), At<CodecError>> = {} bytes (expected <= {})",
size_of::<Result<(), At<CodecError>>>(),
2 * word
);
}
#[test]
fn recovery_tolerates_a_single_box_layer() {
let mk = || CodecError::new(Some("zenjpeg"), ErrorCategory::Image(ImageError::Malformed));
let canonical: Box<dyn core::error::Error + Send + Sync> = Box::new(At::wrap(mk()));
assert_eq!(
canonical.error_category(),
Some(ErrorCategory::Image(ImageError::Malformed))
);
assert_eq!(
canonical.codec_error().and_then(CodecError::codec),
Some("zenjpeg")
);
let boxed_at: Box<dyn core::error::Error + Send + Sync> =
Box::new(Box::new(At::wrap(mk())) as Box<At<CodecError>>);
assert_eq!(
boxed_at.error_category(),
Some(ErrorCategory::Image(ImageError::Malformed))
);
assert_eq!(
boxed_at.codec_error().and_then(CodecError::codec),
Some("zenjpeg")
);
let at_boxed: Box<dyn core::error::Error + Send + Sync> =
Box::new(At::wrap(Box::new(mk()) as Box<CodecError>));
assert_eq!(
at_boxed.error_category(),
Some(ErrorCategory::Image(ImageError::Malformed))
);
let bare_boxed: Box<dyn core::error::Error + Send + Sync> =
Box::new(Box::new(mk()) as Box<CodecError>);
assert_eq!(
bare_boxed.error_category(),
Some(ErrorCategory::Image(ImageError::Malformed))
);
let double: Box<dyn core::error::Error + Send + Sync> =
Box::new(Box::new(Box::new(At::wrap(mk())) as Box<At<CodecError>>));
assert_eq!(double.error_category(), None);
}
#[test]
fn io_kind_variant_is_portable_and_carries_kind_under_std() {
let cat = ErrorCategory::Io(CodecIoKind::opaque());
assert!(matches!(cat, ErrorCategory::Io(_)));
#[cfg(feature = "std")]
{
let k: CodecIoKind = std::io::ErrorKind::UnexpectedEof.into();
assert_eq!(k.kind(), Some(std::io::ErrorKind::UnexpectedEof));
assert_eq!(CodecIoKind::opaque().kind(), None);
}
}
#[test]
fn categorized_error_is_dyn_downcastable_via_any() {
use core::any::Any;
let err = TestCodecError::WrongState;
let dynamic: &dyn CategorizedError = &err;
assert_eq!(
dynamic.category(),
ErrorCategory::Request(RequestError::Invalid(InvalidKind::State))
);
let any: &dyn Any = dynamic; assert!(any.downcast_ref::<TestCodecError>().is_some());
}
#[test]
fn with_codec_stamps_or_clears_the_name() {
let e = CodecError::from_parts(
None,
ErrorCategory::Internal(InternalKind::Bug),
Box::new(TestCodecError::Malformed("x".into())),
);
assert_eq!(e.codec(), None);
let stamped = e.with_codec(Some("zenjpeg"));
assert_eq!(stamped.codec(), Some("zenjpeg"));
assert_eq!(stamped.with_codec(None).codec(), None);
}
}