use core::fmt::Write;
use erra::{Error, ResultExt};
struct WriteBuf<const N: usize> {
buf: [u8; N],
pos: usize,
}
impl<const N: usize> WriteBuf<N> {
const fn new() -> Self {
Self {
buf: [0u8; N],
pos: 0,
}
}
fn as_str(&self) -> &str {
core::str::from_utf8(&self.buf[..self.pos])
.expect("WriteBuf contains invalid UTF-8")
}
}
impl<const N: usize> Write for WriteBuf<N> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
let bytes = s.as_bytes();
let end = self.pos + bytes.len();
if end > N {
return Err(core::fmt::Error);
}
self.buf[self.pos..end].copy_from_slice(bytes);
self.pos = end;
Ok(())
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
#[allow(dead_code)]
enum HalError {
Timeout,
BusError,
InvalidAddress,
}
impl core::fmt::Display for HalError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
HalError::Timeout => f.write_str("hardware timeout"),
HalError::BusError => f.write_str("bus error"),
HalError::InvalidAddress => f.write_str("invalid address"),
}
}
}
fn read_sensor_register() -> Result<u8, HalError> {
Err(HalError::Timeout)
}
fn calibrate_sensor() -> Result<u8, Error<HalError>> {
read_sensor_register().annotate("reading calibration register 0x42")
}
fn init_device() -> Result<u8, Error<Error<HalError>>> {
calibrate_sensor().annotate("device initialisation")
}
#[test]
fn static_annotate_ok_is_passthrough() {
let result: Result<u32, u32> = Ok(42);
let v = result.annotate("irrelevant").unwrap();
assert_eq!(v, 42);
}
#[test]
fn static_annotate_ok_unit_type() {
let result: Result<(), u32> = Ok(());
assert!(result.annotate("step").is_ok());
}
#[test]
fn static_annotate_ok_with_static_str_source() {
let result: Result<u8, &'static str> = Ok(0xFF);
let v = result.annotate("decode").unwrap();
assert_eq!(v, 0xFF);
}
#[test]
fn static_annotate_err_wraps_with_correct_context() {
let result: Result<(), u32> = Err(5);
let err = result.annotate("static message").unwrap_err();
assert_eq!(err.context(), "static message");
}
#[test]
fn static_annotate_err_preserves_source() {
let result: Result<(), u32> = Err(99);
let err = result.annotate("ctx").unwrap_err();
assert_eq!(err.source, 99u32);
}
#[test]
fn static_annotate_err_with_u8_source() {
let result: Result<(), u8> = Err(0xDE);
let err = result.annotate("parse byte").unwrap_err();
assert_eq!(err.source, 0xDE_u8);
assert_eq!(err.context(), "parse byte");
}
#[test]
fn static_annotate_err_with_static_str_source() {
let result: Result<(), &'static str> = Err("device not ready");
let err = result.annotate("init sequence").unwrap_err();
assert_eq!(err.source, "device not ready");
assert_eq!(err.context(), "init sequence");
}
#[test]
fn static_annotate_err_with_i32_source() {
let result: Result<(), i32> = Err(-1);
let err = result.annotate("syscall").unwrap_err();
assert_eq!(err.source, -1i32);
}
#[test]
fn new_constructor_sets_context() {
let e = Error::new("register write failed", 0u32);
assert_eq!(e.context(), "register write failed");
}
#[test]
fn new_constructor_sets_source() {
let e = Error::new("dma fault", 0xDEAD_BEEFu32);
assert_eq!(e.source, 0xDEAD_BEEFu32);
}
#[test]
fn display_formats_as_context_colon_source() {
let e = Error::new("step", 0u8);
let mut buf = WriteBuf::<64>::new();
write!(buf, "{e}").expect("WriteBuf must not overflow for this input");
assert_eq!(buf.as_str(), "step: 0");
}
#[test]
fn display_with_static_str_source() {
let e = Error::new("boot", "sensor offline");
let mut buf = WriteBuf::<64>::new();
write!(buf, "{e}").unwrap();
assert_eq!(buf.as_str(), "boot: sensor offline");
}
#[test]
fn display_with_u32_source() {
let e = Error::new("fault code", 0xCAFEu32);
let mut buf = WriteBuf::<64>::new();
write!(buf, "{e}").unwrap();
assert_eq!(buf.as_str(), "fault code: 51966");
}
#[test]
fn display_with_i32_negative_source() {
let e = Error::new("errno", -22i32);
let mut buf = WriteBuf::<64>::new();
write!(buf, "{e}").unwrap();
assert_eq!(buf.as_str(), "errno: -22");
}
#[test]
fn display_chained_two_layers_no_heap() {
let err = Err::<(), u8>(7)
.annotate("inner")
.annotate("outer")
.unwrap_err();
let mut buf = WriteBuf::<128>::new();
write!(buf, "{err}").unwrap();
let s = buf.as_str();
assert!(s.starts_with("outer:"), "outermost context must lead: {s}");
assert!(s.contains("inner"), "inner context must be present: {s}");
assert!(s.contains('7'), "root error value must appear: {s}");
}
#[test]
fn debug_contains_context_field() {
let e = Error::new("my context", 0u8);
let mut buf = WriteBuf::<128>::new();
write!(buf, "{e:?}").unwrap();
let s = buf.as_str();
assert!(s.contains("my context"), "Debug missing context: {s}");
}
#[test]
fn debug_contains_source_field() {
let e = Error::new("ctx", 42u32);
let mut buf = WriteBuf::<128>::new();
write!(buf, "{e:?}").unwrap();
let s = buf.as_str();
assert!(s.contains("42"), "Debug missing source value: {s}");
}
#[test]
fn debug_names_the_struct() {
let e = Error::new("ctx", 0u8);
let mut buf = WriteBuf::<128>::new();
write!(buf, "{e:?}").unwrap();
let s = buf.as_str();
assert!(s.contains("Error"), "Debug output missing struct name: {s}");
}
#[test]
fn context_accessor_returns_correct_str() {
let e = Error::new("interrupt handler", 0xFFu8);
assert_eq!(e.context(), "interrupt handler");
}
#[test]
fn context_accessor_on_annotated_result() {
let err = Err::<(), u32>(1)
.annotate("context string")
.unwrap_err();
assert_eq!(err.context(), "context string");
}
#[test]
fn into_source_recovers_original_value() {
let e = Error::new("ctx", 0xABu8);
assert_eq!(e.into_source(), 0xABu8);
}
#[test]
fn into_source_on_annotated_result() {
let source = Err::<(), u32>(404)
.annotate("not found")
.unwrap_err()
.into_source();
assert_eq!(source, 404u32);
}
#[test]
fn map_transforms_source_preserves_context_no_heap() {
let e = Error::new("transform", 2u32);
let mapped: Error<u64> = e.map(|v| v as u64 * 10);
assert_eq!(mapped.source, 20u64);
assert_eq!(mapped.context(), "transform");
}
#[test]
fn map_to_static_str_no_heap() {
let e = Error::new("classify", 0u8);
let mapped: Error<&'static str> = e.map(|v| if v == 0 { "zero" } else { "nonzero" });
assert_eq!(mapped.source, "zero");
assert_eq!(mapped.context(), "classify");
}
#[test]
fn clone_on_static_path_produces_equal_value() {
let e = Error::new("ctx", 7u8);
let cloned = e.clone();
assert_eq!(e, cloned);
}
#[test]
fn partial_eq_on_static_path() {
let a = Error::new("ctx", 1u32);
let b = Error::new("ctx", 1u32);
let c = Error::new("ctx", 2u32);
assert_eq!(a, b);
assert_ne!(a, c);
}
#[test]
fn question_mark_propagates_annotated_error() {
fn inner() -> Result<(), u32> {
Err(77)
}
fn outer() -> Result<(), Error<u32>> {
inner().annotate("outer step")?;
Ok(())
}
let err = outer().unwrap_err();
assert_eq!(err.context(), "outer step");
assert_eq!(err.source, 77u32);
}
#[test]
fn question_mark_passes_through_ok() {
fn inner() -> Result<u32, u32> {
Ok(42)
}
fn outer() -> Result<u32, Error<u32>> {
let v = inner().annotate("outer step")?;
Ok(v)
}
assert_eq!(outer().unwrap(), 42);
}
#[test]
fn embedded_pattern_context_is_correct_at_each_layer() {
let err = init_device().unwrap_err();
assert_eq!(err.context(), "device initialisation");
assert_eq!(err.source.context(), "reading calibration register 0x42");
assert_eq!(err.source.source, HalError::Timeout);
}
#[test]
fn embedded_pattern_match_on_source_without_downcast() {
let err = init_device().unwrap_err();
let root: HalError = err.into_source().into_source();
match root {
HalError::Timeout => { }
HalError::BusError => panic!("unexpected bus error"),
HalError::InvalidAddress => panic!("unexpected invalid address"),
}
}
#[test]
fn embedded_pattern_display_no_heap() {
let err = init_device().unwrap_err();
let mut buf = WriteBuf::<256>::new();
write!(buf, "{err}").unwrap();
let s = buf.as_str();
assert!(s.contains("device initialisation"), "{s}");
assert!(s.contains("reading calibration register 0x42"), "{s}");
assert!(s.contains("hardware timeout"), "{s}");
}
#[test]
fn embedded_pattern_clone_and_eq_on_hal_error() {
let err = calibrate_sensor().unwrap_err();
let cloned = err.clone();
assert_eq!(err, cloned);
}