use std::fmt;
use std::io::{self, Read};
use super::{SeekRead, Source, SourceCaps};
#[derive(Debug)]
struct DecodeSideTag(io::Error);
impl fmt::Display for DecodeSideTag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl std::error::Error for DecodeSideTag {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.0)
}
}
pub fn mark_decode_side(e: io::Error) -> io::Error {
if is_decode_side(&e) {
return e;
}
let kind = e.kind();
io::Error::new(kind, DecodeSideTag(e))
}
pub fn is_decode_side(e: &io::Error) -> bool {
e.get_ref().is_some_and(|inner| inner.is::<DecodeSideTag>())
}
pub struct DecodeSideSource {
inner: Box<dyn Source>,
}
impl DecodeSideSource {
pub fn wrap(inner: Box<dyn Source>) -> Box<dyn Source> {
Box::new(Self { inner })
}
}
impl Read for DecodeSideSource {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.inner.read(buf).map_err(mark_decode_side)
}
}
impl Source for DecodeSideSource {
fn caps(&self) -> SourceCaps {
self.inner.caps()
}
fn as_seek(&mut self) -> Option<&mut dyn SeekRead> {
self.inner.as_seek()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::Error;
use crate::source::ReaderSource;
struct FailsAfter {
good: Vec<u8>,
pos: usize,
kind: io::ErrorKind,
}
impl Read for FailsAfter {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.pos < self.good.len() {
let n = (self.good.len() - self.pos).min(buf.len());
buf[..n].copy_from_slice(&self.good[self.pos..self.pos + n]);
self.pos += n;
return Ok(n);
}
Err(io::Error::new(self.kind, "the decoded bytes are malformed"))
}
}
impl Source for FailsAfter {
fn caps(&self) -> SourceCaps {
SourceCaps {
seekable: false,
len: None,
}
}
fn as_seek(&mut self) -> Option<&mut dyn SeekRead> {
None
}
}
fn failing(kind: io::ErrorKind) -> Box<dyn Source> {
Box::new(FailsAfter {
good: b"prefix".to_vec(),
pos: 0,
kind,
})
}
#[test]
fn a_tagged_error_keeps_its_kind_and_message() {
let raw = io::Error::new(io::ErrorKind::InvalidData, "block header is malformed");
let expected = raw.to_string();
let tagged = mark_decode_side(raw);
assert_eq!(tagged.kind(), io::ErrorKind::InvalidData);
assert_eq!(
tagged.to_string(),
expected,
"the marker must be invisible in every user-facing message"
);
assert!(is_decode_side(&tagged));
}
#[test]
fn marking_is_idempotent() {
let once = mark_decode_side(io::Error::new(io::ErrorKind::InvalidData, "boom"));
let twice = mark_decode_side(once);
let thrice = mark_decode_side(twice);
assert!(is_decode_side(&thrice));
assert_eq!(thrice.kind(), io::ErrorKind::InvalidData);
assert_eq!(thrice.to_string(), "boom");
let inner = thrice.get_ref().expect("tagged errors carry a payload");
let tag = inner
.downcast_ref::<DecodeSideTag>()
.expect("the payload is the tag");
assert!(
!is_decode_side(&tag.0),
"a second wrap must not have stacked a tag inside the first"
);
}
#[test]
fn an_untagged_error_still_becomes_error_io() {
let raw = io::Error::new(io::ErrorKind::InvalidData, "raw source said no");
assert!(!is_decode_side(&raw));
assert!(
matches!(Error::from(raw), Error::Io(_)),
"an unmarked InvalidData must stay Io; the marker, not the kind, is what decides"
);
}
#[test]
fn a_tagged_invalid_data_becomes_corrupt_through_a_bare_question_mark() {
fn drain(src: &mut dyn Source) -> crate::error::Result<usize> {
let mut sink = Vec::new();
let n = src.read_to_end(&mut sink)?;
Ok(n)
}
let mut wrapped = DecodeSideSource::wrap(failing(io::ErrorKind::InvalidData));
match drain(wrapped.as_mut()) {
Err(Error::Corrupt(msg)) => {
assert!(msg.contains("malformed"), "decoder's own wording: {msg}");
assert_eq!(Error::Corrupt(msg).exit_code(), 5);
}
other => panic!("expected Corrupt from a marked decode-side read, got {other:?}"),
}
}
#[test]
fn a_tagged_out_of_memory_becomes_resource_limit_through_a_bare_question_mark() {
fn drain(src: &mut dyn Source) -> crate::error::Result<usize> {
let mut sink = Vec::new();
let n = src.read_to_end(&mut sink)?;
Ok(n)
}
let mut wrapped = DecodeSideSource::wrap(failing(io::ErrorKind::OutOfMemory));
match drain(wrapped.as_mut()) {
Err(e @ Error::ResourceLimit(_)) => assert_eq!(e.exit_code(), 6),
other => panic!("expected ResourceLimit, got {other:?}"),
}
}
#[test]
fn a_marked_environment_failure_is_still_an_io_failure() {
let mut wrapped = DecodeSideSource::wrap(failing(io::ErrorKind::PermissionDenied));
let mut sink = Vec::new();
let err = Error::from(wrapped.read_to_end(&mut sink).unwrap_err());
assert!(
matches!(err, Error::Io(_)),
"a permission failure under a decoder is still stuffr's environment failing: {err:?}"
);
assert_eq!(err.exit_code(), 1);
}
#[test]
fn wrapping_passes_bytes_and_caps_through_unchanged() {
let mut wrapped = DecodeSideSource::wrap(Box::new(ReaderSource::new(io::Cursor::new(
b"payload".to_vec(),
))));
assert_eq!(wrapped.caps(), SourceCaps::default());
assert!(wrapped.as_seek().is_none());
let mut out = Vec::new();
wrapped.read_to_end(&mut out).unwrap();
assert_eq!(out, b"payload");
}
}