use alloc::boxed::Box;
use crate::{Location, StackError};
use core::error::Error;
use core::fmt::{Debug, Display, Formatter, Result};
pub struct BoxedStackError {
inner: Box<dyn StackError + Send + Sync>,
}
impl BoxedStackError {
#[must_use]
pub fn new<T: StackError + Send + Sync + 'static>(inner: T) -> Self {
Self {
inner: Box::new(inner),
}
}
#[must_use]
pub fn inner(&self) -> &(dyn StackError + Send + Sync) {
&*self.inner
}
#[must_use]
pub fn into_inner(self) -> Box<dyn StackError + Send + Sync> {
self.inner
}
}
impl Display for BoxedStackError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "{}", self.inner)
}
}
impl Debug for BoxedStackError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "{:?}", self.inner)
}
}
impl Error for BoxedStackError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.inner.source()
}
}
impl StackError for BoxedStackError {
fn location(&self) -> &Location {
self.inner.location()
}
fn type_name(&self) -> &'static str {
self.inner.type_name()
}
fn stack_source(&self) -> Option<&dyn StackError> {
self.inner.stack_source()
}
}
impl From<Box<dyn StackError + Send + Sync>> for BoxedStackError {
fn from(inner: Box<dyn StackError + Send + Sync>) -> Self {
Self { inner }
}
}
impl From<BoxedStackError> for Box<dyn StackError + Send + Sync> {
fn from(inner: BoxedStackError) -> Self {
inner.into_inner()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Location;
use alloc::format;
use snafu::IntoError;
use snafu::prelude::*;
#[derive(Debug, Snafu)]
#[snafu(display("Test error: {}", message))]
struct TestError {
message: alloc::string::String,
#[snafu(implicit)]
location: Location,
}
impl StackError for TestError {
fn location(&self) -> &Location {
&self.location
}
fn type_name(&self) -> &'static str {
"TestError"
}
}
#[test]
fn test_basic_error() {
let test_error = TestSnafu {
message: "Test message",
}
.build();
let error = BoxedStackError::new(test_error);
assert!(format!("{}", error).contains("Test error"));
assert!(format!("{}", error).contains("Test message"));
assert!(format!("{:?}", error).contains("Test message"));
assert!(error.source().is_none());
handle_stack_error(error);
}
#[test]
fn test_error_location() {
let test_error = TestSnafu {
message: "Location test",
}
.build();
let original_line = test_error.location().line();
let error = BoxedStackError::new(test_error);
assert_eq!(error.location().file(), file!());
assert_eq!(error.location().line(), original_line);
handle_stack_error(error);
}
#[test]
fn test_error_conversion() {
let test_error = TestSnafu {
message: "Convert test",
}
.build();
let boxed: Box<dyn StackError + Send + Sync> = Box::new(test_error);
let generic: BoxedStackError = boxed.into();
let back_to_box: Box<dyn StackError + Send + Sync> = generic.into();
assert!(format!("{:?}", back_to_box).contains("Convert test"));
}
#[derive(Debug, Snafu)]
#[snafu(display("Wrapper: {}", message))]
struct WrapperTestError {
message: alloc::string::String,
source: BoxedStackError,
#[snafu(implicit)]
location: Location,
}
impl StackError for WrapperTestError {
fn location(&self) -> &Location {
&self.location
}
fn type_name(&self) -> &'static str {
"WrapperTestError"
}
fn stack_source(&self) -> Option<&dyn StackError> {
Some(&self.source)
}
}
#[test]
fn test_source_chain_delegation() {
let inner = TestSnafu { message: "root" }.build();
let boxed = BoxedStackError::new(inner);
let wrapper = WrapperTestSnafu { message: "wrap" }.into_error(boxed);
let outer = BoxedStackError::new(wrapper);
assert!(outer.source().is_some());
assert!(outer.stack_source().is_some());
}
#[test]
fn test_depth() {
let leaf = BoxedStackError::new(TestSnafu { message: "leaf" }.build());
assert_eq!(leaf.depth(), 0);
let inner = BoxedStackError::new(TestSnafu { message: "inner" }.build());
let wrapper = WrapperTestSnafu { message: "outer" }.into_error(inner);
let outer = BoxedStackError::new(wrapper);
assert_eq!(outer.depth(), 1);
}
fn handle_stack_error<T: StackError>(_: T) {}
#[test]
fn test_inner_ref() {
let test_error = TestSnafu {
message: "inner ref test",
}
.build();
let original_line = test_error.location().line();
let error = BoxedStackError::new(test_error);
let inner = error.inner();
assert_eq!(inner.location().line(), original_line);
assert_eq!(inner.type_name(), "TestError");
}
#[test]
fn test_into_inner_round_trip() {
let test_error = TestSnafu {
message: "round trip",
}
.build();
let original_line = test_error.location().line();
let boxed = BoxedStackError::new(test_error);
let inner: Box<dyn StackError + Send + Sync> = boxed.into_inner();
assert_eq!(inner.location().line(), original_line);
assert_eq!(inner.type_name(), "TestError");
assert!(format!("{inner}").contains("round trip"));
let boxed_again: BoxedStackError = inner.into();
assert_eq!(boxed_again.location().line(), original_line);
}
}